ref: 80d6b9bd2f87ed9d75f8acfc20bc0dcb847facee gemif/README.md -rw-r--r-- 5.7 KiB
80d6b9bdNorm MacLennan Hide API token from job output 1 year, 5 months ago


builds.sr.ht status

GemIF is a simple Interactive Fiction engine which runs as a Gemini server.


There's no simple installation process at the moment.

For now, you'll need to clone this repo and build or run cmd/gemif with something like make build-all, go build gemif/cmd/gemif, or whatever.


Once you have the binary compiled, you need a few files to make it run.

#Step One: Generate your TLS Cert

The first thing to do is generate your TLS cert in whatever way makes sense for you.

In production, you might use certbot in standalone mode to generate a LetsEncrypt cert.

Otherwise, since most (all?) Gemini clients use TOFU for TLS certificates, you can also feel free to generate a self-signed cer in whatever way is appropriate for you.

#Step Two: Gather up your stories

This repo comes with a couple of sample stories in ./stories. If you want to add, remove, or change them, go ahead and do that now.

#Step Three: Make a config file

GemIF configures itself based on the contents of ./config.toml. Take sample.config.toml and rename it. Change the configuration to match your desired values.

#Run it

That's it. Go ahead and run the binary.

#Managing Your Stories

By default, stories live in the ./stories directory in your working directory. You can change this with engine.stories_dir in your config.toml.

Each *.yml file in that directory represents a separate story to be loaded up by the engine. The top-level index page will allow the user to choose which of these stories they'd like to play, so you have have as many as you want (within other technical constraints).

The file must contain two top-level properties: metadata and rooms.

metadata provides information about the story:

  • id: is a unique id (within your instance) for the story
  • name: is a human-friendly title of the story
  • description: is a brief description of the story
  • author: a name or other identifier of the author(s)

The rooms object holds a list of your rooms/scenes.

Each room has:

  • room_id: a unique id for the room, I recommend a GUID
  • room_name: a human-readable name
  • room_description: a description of the room (what is printed to the screen when the user is there)
  • exits: an array of exit objects

Each exit contains:

  • exit_description: a description of the exit (the text of the exit link)
  • desination_id: the id of the room this exit takes you to
  • exit_id: a unique id for the exit, I recommend a GUID
  • set_condition: conditional tag to attach to the game state if the user takes this exit
  • if_condition: conditional tag the user must posses to use this exit


Conditions are simple tags attached to the user's state. Condition usage will vary from story to story, and some will not require conditions at all, but you might use them to:

  • Track something that has (or has not) happened in the story
  • Track if a user has (or has not) picked up an object
  • Track that the user has said a certain thing to somebody

Conditions are set as part of using exits and can be used to control which exits a user can use in the future as well as used within room descriptions to offer conditional passages.

#Description Templates

Speaking of which, room descriptions are rendered as golang text/template templates with the active gamestate in scope. This allows you to perform a little bit of logic with regards to what you display to the user, how, and when.

The following struct is in scope within your template:

type GameState struct {
	StoryID     string   `json:"si"`
	CurrentRoom string   `json:"cr"`
	Conditions  []string `json:"cn"`

StoryID and CurrentRoom (RoomID) probably aren't very interesting, but Conditions can be used to perform conditional rendering.

GameState offers some (currently one) convenience function to help simplify your rendering when it comes to conditions.

ConditionMet(condition string) bool can help you quickly tell if a user meets a specific condition:

- room_id: the_end
  room_name: The End
  room_description: |
    Though because this is a simple demo, you always end up in the same place.

    {{- if .ConditionMet "choice_a"}}

    oh cool, you made choice a! nice work.
    {{- end}}
    {{- if .ConditionMet "choice_b"}}

    oh you made choice b, that's too bad :(
    {{- end}}

#Quick Sample

The sample in the repo contains a walking tour of my home and another contains some examples of the use of conditions. But here's a simpler sample:

- room_id: the_beginning
  room_name: The Beginning
  room_description: >
    This is the beginning of the story!
  - exit_description: This is boring, flip to the end
    destination_id: the_end
    exit_id: exit_a
- room_id: the_end
  room_name: The End
  room_description: >
    And they lived happily ever after.


Hopefully between these two examples, you can get an idea of how you can build simple linear or branching stories.

I plan to continue to extend the features available to offers to add a little more flexibility, but part of the idea is that it's meant to be simple so I'm trying to be careful of how much I add.

Currently focused on refactoring and solidifying the foundation of the application so it is performant and maintainable.


MIT © Norm MacLennan