~nromdotcom/gemif

abb8557c4e252a1cc32192b27bf8ecf51fcc6a0e — Norm MacLennan 11 months ago 2a3f890 0.3.0
Rewrite bundled stories in simplified .gemif format
21 files changed, 588 insertions(+), 503 deletions(-)

M README.md
M static/templates/index.gmi.tmpl
M static/templates/room.gmi.tmpl
D stories/compiled/conditions.yml
M stories/compiled/house_tour.yml
M stories/compiled/sample_story.yml
A stories/compiled/tutorial.yml
D stories/src/conditions/conditions.gemif
D stories/src/conditions/metadata.yml
M stories/src/house_tour/back_porch.gemif
M stories/src/house_tour/front_hall.gemif
M stories/src/house_tour/game_room.gemif
M stories/src/house_tour/kitchen.gemif
M stories/src/house_tour/living_room.gemif
M stories/src/house_tour/upstairs_hallway.gemif
D stories/src/sample_story/01-start.gemif
D stories/src/sample_story/02-middle.gemif
D stories/src/sample_story/03-end.gemif
A stories/src/sample_story/story.gemif
A stories/src/tutorial/01-basics.gemif
A stories/src/tutorial/metadata.yml
M README.md => README.md +3 -176
@@ 68,183 68,10 @@ GemIF loads stories from YAML files (from the directory named in `engine.stories
`.gemif` files.

### .gemif files and gemifc
`.gemif` files are a lighweight abstraction on top of the YAML files. Honestly it's still
not the most pleasant writing experience, but it's generally better than writing the YAML
by hand.
GemIF comes with a story called "Writing Tutorial" which provides guided documentation on writing GemIF stories.

You can see some examples in [stories/src/](stories/src/).

Each story directory will contain:
* `metadata.yml` - With story and author information
* An arbitrary number of `.gemif` files - These files contain the rooms and exits

Each `.gemif` contains one or more different rooms. Room metadata is presented as YAML 
frontmatter (or, uh midmatter in the case of multiple rooms in a file) while each
description template is presented as plaintext.

```
# metadata.yml
---
id: simple
name: Simple Demo
description: A simple demo
author: Norm MacLennan

# room.gemif
---
room_id: inside
room_name: Inside
exits:
- exit_id: leave
  exit_description: You're right, let's seize the day!
  destination_id: outside
---
You're inside, but it's such a lovely day out.

---
room_id: outside
room_name: Outside
---
You made it outside. Now you can get on with your life.

THE END
```

Once you're ready to test your story, you can use `gemifc` to "compile" it to YAML.

```
$ gemifc ./path/to/my/story/ ./my/compiled/stories/
Compiling story ./path/to/my/story/ to ./my/compiled/stories/

Finished loading story:
  Name: Simple Demo
  Author: Norm MacLennan
  Descriptions: A simple demo
  Number of Rooms: 2

Serializing and writing to disk...
Done!
```

Then you can load it up with `gemif` as usual.

## Object specification

Whether you're writing in gemif format or right in a YAML file, here are the object
specifications for the things you're building.

`metadata` comes from the `metadata.yml` file in gemif format or the `metadata` key in YAML:
* `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)

`rooms` objects are described by your `.gemif` files or as the `rooms` object in your YAML.

Each room has:
* `room_id`: a story-unique id for the room
* `room_name`: a human-readable name
* `room_description`: a description of the room 
  * in `.gemif` format this is the plaintext part of each room while the rest
    of the properties live in the frontmatter
* `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`: condition tag to attach to the game state if the user takes this exit
* `if_condition`: condition tag the user must posses to use this exit
* `not_condition`: conditoin tag to the user must NOT posses to use this exit

### Conditions

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
	CurrentRoom string
	Conditions  []string
}
```

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:

```
rooms:
- room_id: the_beginning
  room_name: The Beginning
  room_description: >
    This is the beginning of the story!
  exits:
  - 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.

    THE END
```

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.
You can see the live story at [gemini://gemif.fedi.farm/game/Cgh0dXRvcmlhbBIKdGhlX2Jhc2ljcw==](gemini://gemif.fedi.farm/game/Cgh0dXRvcmlhbBIKdGhlX2Jhc2ljcw==)
or the source of the story right in this repo at [stories/src/tutorial](stories/src/tutorial).

## License


M static/templates/index.gmi.tmpl => static/templates/index.gmi.tmpl +0 -2
@@ 16,11 16,9 @@ Wanna try it out?
{{- end}}

Or learn more?
=> /docs/ Yeah, tell me more!
=> https://git.sr.ht/~nromdotcom/gemif [https] source at sr.ht

Want me to host your story for you?
=> /docs/config.gmi Learn to write stories
=> mailto:~nromdotcom/gemif@lists.sr.ht [mailto] Send it to the project mailing list

For anything else, feel free to email the project mailing list `~nromdotcom/gemif@lists.sr.ht` or me personally at `norm@iwritethe.codes`.
\ No newline at end of file

M static/templates/room.gmi.tmpl => static/templates/room.gmi.tmpl +1 -1
@@ 4,7 4,7 @@

{{.Description}}

{{- range $key, $value := .Actions}}
{{range $key, $value := .Actions}}
  {{- if ne $value.StateToken ""}}
=> /game/{{$value.StateToken}} {{$value.Description}}
  {{- end}}

D stories/compiled/conditions.yml => stories/compiled/conditions.yml +0 -35
@@ 1,35 0,0 @@
metadata:
  id: conditions
  name: Conditions Demo
  description: A simple demo of using conditions
  author: Norm MacLennan
rooms:
- room_id: inside
  room_name: Inside
  room_description: |2+

    You are locked inside a room. Weird that the door locks from the outside, but it turns out it's a thing.

    {{if .ConditionMet "has_key"}}
    You've got the key in your hand.
    {{else}}
    Better find the key. It's probably around here somewhere.
    {{end}}

  exits:
  - exit_id: find_key
    exit_description: Oh, there's the key!
    destination_id: inside
    set_condition: has_key
    not_condition: has_key
  - exit_id: leave
    exit_description: Alright, let's get out of here.
    destination_id: outside
    if_condition: has_key
- room_id: outside
  room_name: Outside
  room_description: |2-

    You made it outside. Now you can get on with your life.

    THE END

M stories/compiled/house_tour.yml => stories/compiled/house_tour.yml +41 -60
@@ 4,86 4,67 @@ metadata:
  description: Take a walk through my house
  author: Norm MacLennan
rooms:
- room_id: 478f88b7-88fe-4f2d-8b76-35d5f190c4d1
- room_id: back_porch
  room_name: Back Porch
  room_description: |2

    There's a nice back yard here.
  room_description: |-
    There's a nice back yard here!

    On the porch there's a greenhouse with several young plants, as well as a couple outdoor chairs. Beyond the porch is an overgrown lawn and a big rock retaining wall. Everything past that is woods.
  exits:
  - exit_id: ff9d5ca2-14da-44ea-8a86-bd46dfdf2db4
    exit_description: You can go back inside into the living room.
    destination_id: 2824028c-d71c-49cd-9289-c349a640ed9a
- room_id: cb178734-dd94-4333-87bd-731f8e8ac1ed
  - exit_description: You can go back inside to the living room
    destination_id: living_room
- room_id: front_hall
  room_name: Front Hall
  room_description: |2-

  room_description: |-
    You're standing in the front hall. The door is open and you can see the yard.

    There's a cute little shed and a raised bed garden out there. The lawn is dying.
  exits:
  - exit_id: a0cf11d1-a651-4bd9-a731-ca549048a176
    exit_description: The living room is to the west
    destination_id: 2824028c-d71c-49cd-9289-c349a640ed9a
  - exit_id: a22d76c7-3d74-4a9f-a958-06e85d05b033
    exit_description: Oooh, looks like a bunch of fun stuff in the game room!
    destination_id: 52d7babe-8de3-4202-8e6c-097c73ecd918
  - exit_id: b4756738-2ec3-4a4a-b229-ac7f80816deb
    exit_description: You can go upstairs to the second floor hall.
    destination_id: ee8bdc39-c471-4cdd-9241-5f5cf43143d5
- room_id: 52d7babe-8de3-4202-8e6c-097c73ecd918
  - exit_description: The living room is to the west
    destination_id: living_room
  - exit_description: Ooh, it looks like there's a bunch of fun stuff in the game
      room!
    destination_id: game_room
  - exit_description: You can go upstairs to the second floor hall
    destination_id: upstairs_hall
- room_id: game_room
  room_name: Game Room
  room_description: |2-

  room_description: |-
    This room has a bunch of cool stuff in it.

    The walls are covered in bookshelves full of boardgames, lego set, and fun little knick-knacks. In the center of the room is a gaming table topped with green speed cloth and 6 chairs.
  exits:
  - exit_id: a197cc68-8830-452a-9e95-289031574951
    exit_description: There's a nice breeze blowing in from the open front door
    destination_id: cb178734-dd94-4333-87bd-731f8e8ac1ed
  - exit_id: cedb26f5-9760-4027-af24-eac92ddd71f7
    exit_description: You can see the kitchen through a doorway.
    destination_id: 9dabb186-6046-4570-a585-2fbaadfab3cc
- room_id: 9dabb186-6046-4570-a585-2fbaadfab3cc
  - exit_description: There's a nice breeze blowing in from the open front door
    destination_id: front_hall
  - exit_description: You can see the kitchen through a doorway
    destination_id: kitchen
- room_id: kitchen
  room_name: Kitchen
  room_description: |2-


    The kitchen is fairly well-appointed. It borders the Living Room with a peninsula and two stools. There is a kitchen and stove in the corner, with a fridge on the right and a sink on the left. There appears to be adequate cabinet space.
  room_description: The kitchen is fairly well-appointed. It borders the Living Room
    with a peninsula and two stools. There is a kitchen and stove in the corner, with
    a fridge on the right and a sink on the left. There appears to be adequate cabinet
    space.
  exits:
  - exit_id: 15c33397-b5ee-4d05-8d66-fdbf556f9c2d
    exit_description: You can see the living room from where you are.
    destination_id: 2824028c-d71c-49cd-9289-c349a640ed9a
  - exit_id: cc322095-46cf-401b-a5e5-3d17b767d634
    exit_description: There's a fun-looking game room through a doorway
    destination_id: 52d7babe-8de3-4202-8e6c-097c73ecd918
- room_id: 2824028c-d71c-49cd-9289-c349a640ed9a
  - exit_description: You can see the living room from where you are.
    destination_id: living_room
  - exit_description: There's a fun-looking game room through a doorway.
    destination_id: game_room
- room_id: living_room
  room_name: Living Room
  room_description: |2


  room_description: |-
    You are in the living room. It is a cozy area with a couch, loveseat, and big chair with ottoman. The seats are separated by end tables.

    There is a coffee table in the middle and an entertainment center with TV across from the seats.
  exits:
  - exit_id: 85a71759-5c70-4fc4-8178-8ad8a3bef2d6
    exit_description: The kitchen lies to the north.
    destination_id: 9dabb186-6046-4570-a585-2fbaadfab3cc
  - exit_id: 478f88b7-88fe-4f2d-8b76-35d5f190c4d1
    exit_description: To the west is a sliding door that leads to a porch
    destination_id: 478f88b7-88fe-4f2d-8b76-35d5f190c4d1
  - exit_id: e4e23c7e-3f54-4bb9-a76e-c899034bc327
    exit_description: To the east is the front door, leading to the yard.
    destination_id: cb178734-dd94-4333-87bd-731f8e8ac1ed
- room_id: ee8bdc39-c471-4cdd-9241-5f5cf43143d5
  - exit_description: The kitchen lies to the north.
    destination_id: kitchen
  - exit_description: To the west is a sliding door that leads to a porch
    destination_id: back_porch
  - exit_description: To the east is the front door, leading to the yard
    destination_id: front_hall
- room_id: upstairs_hall
  room_name: Upstairs Hallway
  room_description: |2-


    All the doors upstairs are closed. Weird.
  room_description: All the doors upstairs are closed. Weird.
  exits:
  - exit_id: 5e5b296e-68da-4e35-8da6-4a160e3435ee
    exit_description: Might as well go back downstairs
    destination_id: cb178734-dd94-4333-87bd-731f8e8ac1ed
  - exit_description: Might as well go back downstairs
    destination_id: front_hall

M stories/compiled/sample_story.yml => stories/compiled/sample_story.yml +18 -34
@@ 6,8 6,7 @@ metadata:
rooms:
- room_id: the_beginning
  room_name: The Beginning
  room_description: |2

  room_description: |-
    ┌─────────────╥─────────────┐
    │░░░░░╔═══╗░░░║░╔══╦═══╗░░░░╞╗
    │░░░░░║╔═╗║░░░║░╚╣╠╣╔══╝░░░░│║


@@ 21,58 20,46 @@ rooms:

    Here you are in the beginning of the story. Make a choice below!
  exits:
  - exit_id: make_choice_a
    exit_description: I want to make choice a
  - exit_description: I want to make choice a
    destination_id: made_choice_a
    set_condition: choice_a
  - exit_id: make_choice_b
    exit_description: I want to make choice b
  - exit_description: I want to make choice b
    destination_id: made_choice_b
    set_condition: choice_b
  - exit_id: make_no_choice
    exit_description: If you choose not to decide you still have made a choice
  - exit_description: Too cool to choose
    destination_id: made_no_choice
    set_condition: no_choice
- room_id: made_choice_a
  room_name: Doing Choice A
  room_description: |2+

  room_description: |-
    You made choice a!

    Something specific probably happens because of this.

  exits:
  - exit_id: go_to_end
    exit_description: Or maybe choice is an illusion
  - exit_description: Or maybe choice is an illusion
    destination_id: the_end
- room_id: made_choice_b
  room_name: Doing Choice B
  room_description: |2+

  room_description: |-
    You made choice b!

    Is anything really different than if you did choice a?

  exits:
  - exit_id: go_to_end
    exit_description: Or maybe choice is an illusion
  - exit_description: Or maybe choice is an illusion
    destination_id: the_end
- room_id: made_no_choice
  room_name: A coward who makes no choice
  room_description: |2-

  room_description: |-
    You didn't make any choice!

    But something still happens - that's how the world works.
  exits:
  - exit_id: go_to_end
    exit_description: Or maybe choice is an illusion
  - exit_description: I'll see about that...
    destination_id: the_end
- room_id: the_end
  room_name: The End
  room_description: |2+

    Though because this is a simple demo, you always end up in the same place.
  room_description: |-
    Though because this is a simple demo, yo always end up in the same place.

    {{- if .ConditionMet "choice_a"}}



@@ 80,23 67,20 @@ rooms:
    {{- end}}
    {{- if .ConditionMet "choice_b"}}

    oh you made choice b, that's too bad :(
    of you made choice b, that's too bad :(
    {{- end}}
    {{- if .ConditionMet "no_choice"}}

    wow you're soooo cool and mysterious, making no choice.
    you really thing that makes you interesting? well it doesn't.
    wow you're really cool and interesting, making no choice.
    {{- end}}

  exits:
  - exit_id: bonus_round
    exit_description: oh, what's this?
  - exit_description: Oh, what's this?
    destination_id: secret_level
    if_condition: no_choice
- room_id: secret_level
  room_name: Bonus Round!
  room_description: |2-

    We're all so proud of you for being so cool and awesome that you didn't make a choice.
  room_description: |-
    We're all so proud of you for being so cool and awesome that you
    didn't make a choice.

    Nobody else got this ending!

A stories/compiled/tutorial.yml => stories/compiled/tutorial.yml +197 -0
@@ 0,0 1,197 @@
metadata:
  id: tutorial
  name: Writing Tutorial
  description: Documentation on writing GemIF stories
  author: Norm MacLennan
rooms:
- room_id: the_basics
  room_name: The Basics
  room_description: |-
    Welcome to GemIF! Gemini's Interactive Fiction engine.

    In this tutorial, we'll walk through writing your own stories using the .gemif file format. While stories can technically be written directly in the "compiled" YAML format, gemif format provides a much more user-friendly experience and allows authors to write without worrying about a lot of semantic whitespace.
  exits:
  - exit_description: Cool, let's get started!
    destination_id: get_started
  - exit_description: Carry on with the tutorial
    destination_id: simple_demo
    if_condition: seen_demo
- room_id: get_started
  room_name: The Basics pt 2
  room_description: |-
    A GemIF story is made up of `Scenes` that you moved between through the use of `Transitions`.

    ## Scenes

    Scenes are where the action happens. Each scene has the following attributes:

    * ID: A scene's ID is its unique identifier within the story. The ID is used in Transitions to move the user from scene to scene.
    * Name: A scene's Name is a user-friendly identifier for the page. It is displayed at the top of the page. This scene's Name is `The Basics pt 2`.
    * Description: A scene's description is free-text describing what's happening in the scene. A description can optionally be rendered as a Golang `text/template`, which can be used along with advanced GemIF features to provide dynamic story-telling.
    * Transitions: Each scene has 0 or more transitions. These transitions are represented as reader choices and move the user to the next scene. Not all scenes need transitions, but these are "terminal" state of the story.

    A scene can, of course, be as many lines as you want and you can use Gemini text formatting freely.

    ## Transitions

    A transition's job is to move the reader from one scene to another. A transition can also point back at its own scene, using advanced features to change the content.

    Each transition has the following attributes:

    * Destination: A transition's destination must be the ID of the scene the reader should move to.
    * Description: A transition's description is the text presented to the reader for the given choice.
    * Condition Options: Conditions are an advanced feature of GemIF that we'll cover in detail later. Suffice it to say, transitions can add or remove conditions from the reader's game which can be used to build dynamic stories.

    Each transition must exist on a single line (that is, descriptions must not have newline characters).
  exits:
  - exit_description: What do scenes and transitions look like?
    destination_id: simple_demo
- room_id: simple_demo
  room_name: The Basics pt 3
  room_description: "So now that you're somewhat familiar with the building blocks
    of a story - scenes and transitions - we can discuss what they actually look like.\n\n##
    GemIF Format\n\nStories are best and most easily written in GemIF format. This
    is a plaintext format that looks a lot like Gemini format.\n\nEach story is made
    up of one or more `.gemif` file with whatever names you want. And each file can
    contain or more scene (with associated transitions).\n\n### Scenes\nLet's look
    back at the first page of this tutorial for a simple example. The \"source\" of
    that scene looks like:\n\n```\n  ---\n  # the_basics The Basics\n\n  Welcome to
    GemIF! Gemini's Interactive Fiction engine.\n\n  In this tutorial, we'll walk
    through writing your own stories using the .gemif\n  file format. While stories
    can technically be written directly in the \"compiled\"\n  YAML format, gemif
    format provides a much more user-friendly experience and allows \n  authors to
    write without worrying about a lot of semantic whitespace.\n\n  => get_started
    Cool, let's get started!\n```\n\nEach scene must start with a triple-dash (`---`).
    Within files, the triple-dash is used as a scene separator.\n\nThe first line
    of a scene contains the scene's ID and Name, prefixed with an `#`.\n\n```\n  #
    <SceneID> <SceneName>\n```\n\n### Transitions\nThe other piece of special syntax
    in a scene are the transitions. Transitions look a lot like Gemini links, but
    have a few advanced features we'll talk about later.\n\n```\n  => <DestinationID>
    <TransitionDescription>\n```\n\nAnd that's basically all there is to it (we'll
    get into advanced features shortly)."
  exits:
  - exit_description: If you want to see the first page again for reference, choose
      this.
    destination_id: the_basics
    set_condition: seen_demo
  - exit_description: Or continue on...
    destination_id: story_metadata
- room_id: story_metadata
  room_name: The Basics pt 4
  room_description: |-
    One last thing before we carry on to advanced features.

    ## Story Metadata
    In addition to the .gemif files, GemIF stories require a single file named `metadata.yml`. This file contains basic information (well, metadata) about the story for GemIF to display to the reader.

    The format is pretty simple and self-explanatory, but for completeness, your `metadata.yml` must contain:

    * id: a server-unique id of your story
    * name: a user-friendly name of your story
    * description: a brief description of what you're story is about
    * author: name or other identifier of the story author.

    Here's the `metadata.yml` of this story, for reference.

    ```
      ---
      id: sample_story
      name: Sample Story
      description: A sort of hello world
      author: Norm MacLennan
    ```
  exits:
  - exit_description: Alright, I get it, I get it. Bring on the advanced features!
    destination_id: conditions
- room_id: conditions
  room_name: Advanced Features pt 1
  room_description: |-
    Now you know the basics of creating stories. You should be able to use these basics to create plenty of linear or branching narratives. But what if you need just a _little_ more power?

    ## Conditions
    Conditions are "tags" that can be attached to the reader's game to denote that they have made (or not made) a particular choice.

    Conditions can be attached or removed from game state through transitions. You can also use conditions to decide whether or not to present a given transition to a user.

    ### With Transitions
    Conditions work with transitions through the use of condition directives on the transition itself. These directives are placed between curly-braces in between the destination and description.

    It looks like this:

    ```
      => <destination> {<condition directives>} <description>
    ```

    Did you notice in the previous scene, that if you moved back to the first scene you could suddenly jump right back to where you were? That was done with transitions.

    On the GemIF demo scene, the transition back to the intro scene looks like:

    ```
      => the_basics {+seen_demo} If you want to see the first page again for reference, choose this.
    ```

    That little `{+seen_demo}` attaches the `seen_demo` condition to the game state.

    Then, back in the first scene there is a conditionally-hidden transition that didn't show up until you had that condition:

    ```
      => simple_demo {~seen_demo} Carry on with the tutorial
    ```

    `{~seen_demo}` will only show the transition option if the reader's state has the `seen_demo` condition.

    There are a handful of condition directives you can use, and you can provide as many as you like for each transition, separated with a space.

    * `+condition`: adds `condition` to the user's state
    * `-condition`: removes `condition` from the user's state
    * `~condition`: only presents the transition to the user when they have `condition`
    * `!condition`: only presents the transition to the user when they DON'T have `condition`
  exits:
  - exit_description: What else can I do with conditions?
    destination_id: template
- room_id: template
  room_name: Advanced Features pt 2
  room_description: "In the previous scene, you learned how to use conditions to track
    information about the story state and conrol which choices the reader is presented
    with.\n\n## Templates\n\nYou can also use conditions to conditionally render different
    parts of room templates. Scene descriptions are rendered as Golang `text/template`s.
    \n\nTemplates have a `.Conditions` list in scope and some utility functions available.\n\nIf
    you returned to the first scene previously and gained the `seen_demo` condition,
    you should see `[seen_demo]` printed below, otherwise you may just see `[]:\n\n{{.Conditions}}\n\nThat
    was produced with the token {{\"`{{.Conditions}}`\"}}.\n\nA utility function,
    `ConditionMet` is in-scope to check if a reader has met a condition. Below you
    should see `true` or `false` based on whether or not you have the condition:\n\n{{.ConditionMet
    \"seen_demo\"}}\n\nThis was produced with the token `{{\"{{.ConditionMet \\\"seen_demo\\\"}}\"}}`.\n\nYou
    can check the Golang text/template documentation for ways you can use templates
    to write if statements and other advanced conditional rendering."
  exits:
  - exit_description: Cool, well what's next?
    destination_id: whats_next
- room_id: whats_next
  room_name: Compiling Your Story
  room_description: |-
    Once you've written your story (or probably more like as you write your story), you can use `gemifc` to compile your story to YAML format for use by the `gemif` server.

    ```
    $ gemifc [input directory] [output directory]
    ```

    So to compile this story:

    ```
    $ gemifc ./stories/src/tutorial ./stories/compiled
    Compiling story ./stories/src/tutorial to ./stories/compiled/

    Finished loading story:
      Name: Writing Tutorial
      Author: Norm MacLennan
      Descriptions: Documentation on writing GemIF stories
      Number of Rooms: 7

    Serializing and writing to disk...
    Done!
    ```

    If you want to publish your story, just drop it in the configured stories directory of your GemIF server. If you don't want to run it yourself, feel free to email it to the GemIF mailing list at `~nromdotcom/gemif@lists.sr.ht` and I'd almost certainly be happy to host it for you.

    THE END

D stories/src/conditions/conditions.gemif => stories/src/conditions/conditions.gemif +0 -29
@@ 1,29 0,0 @@
---
room_id: inside
room_name: Inside
exits:
- exit_id: find_key
  exit_description: Oh, there's the key!
  destination_id: inside
  set_condition: has_key
  not_condition: has_key
- exit_id: leave
  exit_description: Alright, let's get out of here.
  destination_id: outside
  if_condition: has_key
---
You are locked inside a room. Weird that the door locks from the outside, but it turns out it's a thing.

{{if .ConditionMet "has_key"}}
You've got the key in your hand.
{{else}}
Better find the key. It's probably around here somewhere.
{{end}}

---
room_id: outside
room_name: Outside
---
You made it outside. Now you can get on with your life.

THE END
\ No newline at end of file

D stories/src/conditions/metadata.yml => stories/src/conditions/metadata.yml +0 -5
@@ 1,5 0,0 @@
---
id: conditions
name: Conditions Demo
description: A simple demo of using conditions
author: Norm MacLennan
\ No newline at end of file

M stories/src/house_tour/back_porch.gemif => stories/src/house_tour/back_porch.gemif +5 -8
@@ 1,11 1,8 @@
---
room_id: 478f88b7-88fe-4f2d-8b76-35d5f190c4d1
room_name: Back Porch
exits:
- exit_description: You can go back inside into the living room.
  destination_id: 2824028c-d71c-49cd-9289-c349a640ed9a
  exit_id: ff9d5ca2-14da-44ea-8a86-bd46dfdf2db4
---
There's a nice back yard here.
# back_porch Back Porch

There's a nice back yard here!

On the porch there's a greenhouse with several young plants, as well as a couple outdoor chairs. Beyond the porch is an overgrown lawn and a big rock retaining wall. Everything past that is woods.

=> living_room You can go back inside to the living room
\ No newline at end of file

M stories/src/house_tour/front_hall.gemif => stories/src/house_tour/front_hall.gemif +7 -14
@@ 1,17 1,10 @@
---
room_id: cb178734-dd94-4333-87bd-731f8e8ac1ed
room_name: Front Hall
exits:
- exit_id: a0cf11d1-a651-4bd9-a731-ca549048a176
  destination_id: 2824028c-d71c-49cd-9289-c349a640ed9a
  exit_description: The living room is to the west
- exit_id: a22d76c7-3d74-4a9f-a958-06e85d05b033
  destination_id: 52d7babe-8de3-4202-8e6c-097c73ecd918
  exit_description: Oooh, looks like a bunch of fun stuff in the game room!
- exit_description: You can go upstairs to the second floor hall.
  destination_id: ee8bdc39-c471-4cdd-9241-5f5cf43143d5
  exit_id: b4756738-2ec3-4a4a-b229-ac7f80816deb
---
# front_hall Front Hall

You're standing in the front hall. The door is open and you can see the yard.

There's a cute little shed and a raised bed garden out there. The lawn is dying.
\ No newline at end of file
There's a cute little shed and a raised bed garden out there. The lawn is dying.

=> living_room The living room is to the west
=> game_room Ooh, it looks like there's a bunch of fun stuff in the game room!
=> upstairs_hall You can go upstairs to the second floor hall
\ No newline at end of file

M stories/src/house_tour/game_room.gemif => stories/src/house_tour/game_room.gemif +6 -11
@@ 1,14 1,9 @@
---
room_id: 52d7babe-8de3-4202-8e6c-097c73ecd918
room_name: Game Room
exits:
- exit_id: a197cc68-8830-452a-9e95-289031574951
  destination_id: cb178734-dd94-4333-87bd-731f8e8ac1ed
  exit_description: There's a nice breeze blowing in from the open front door
- exit_id: cedb26f5-9760-4027-af24-eac92ddd71f7
  destination_id: 9dabb186-6046-4570-a585-2fbaadfab3cc
  exit_description: You can see the kitchen through a doorway.
---
# game_room Game Room

This room has a bunch of cool stuff in it.

The walls are covered in bookshelves full of boardgames, lego set, and fun little knick-knacks. In the center of the room is a gaming table topped with green speed cloth and 6 chairs.
\ No newline at end of file
The walls are covered in bookshelves full of boardgames, lego set, and fun little knick-knacks. In the center of the room is a gaming table topped with green speed cloth and 6 chairs.

=> front_hall There's a nice breeze blowing in from the open front door
=> kitchen You can see the kitchen through a doorway
\ No newline at end of file

M stories/src/house_tour/kitchen.gemif => stories/src/house_tour/kitchen.gemif +5 -11
@@ 1,13 1,7 @@
---
room_id: 9dabb186-6046-4570-a585-2fbaadfab3cc
room_name: Kitchen
exits:
- exit_description: You can see the living room from where you are.
  destination_id: 2824028c-d71c-49cd-9289-c349a640ed9a
  exit_id: 15c33397-b5ee-4d05-8d66-fdbf556f9c2d
- exit_id: cc322095-46cf-401b-a5e5-3d17b767d634
  destination_id: 52d7babe-8de3-4202-8e6c-097c73ecd918
  exit_description: There's a fun-looking game room through a doorway
---
# kitchen Kitchen

The kitchen is fairly well-appointed. It borders the Living Room with a peninsula and two stools. There is a kitchen and stove in the corner, with a fridge on the right and a sink on the left. There appears to be adequate cabinet space.

The kitchen is fairly well-appointed. It borders the Living Room with a peninsula and two stools. There is a kitchen and stove in the corner, with a fridge on the right and a sink on the left. There appears to be adequate cabinet space.
\ No newline at end of file
=> living_room You can see the living room from where you are.
=> game_room There's a fun-looking game room through a doorway.
\ No newline at end of file

M stories/src/house_tour/living_room.gemif => stories/src/house_tour/living_room.gemif +5 -13
@@ 1,18 1,10 @@
---
room_id: 2824028c-d71c-49cd-9289-c349a640ed9a
room_name: Living Room
exits:
- exit_description: The kitchen lies to the north.
  destination_id: 9dabb186-6046-4570-a585-2fbaadfab3cc
  exit_id: 85a71759-5c70-4fc4-8178-8ad8a3bef2d6
- exit_description: To the west is a sliding door that leads to a porch
  destination_id: 478f88b7-88fe-4f2d-8b76-35d5f190c4d1
  exit_id: 478f88b7-88fe-4f2d-8b76-35d5f190c4d1
- exit_description: To the east is the front door, leading to the yard.
  destination_id: cb178734-dd94-4333-87bd-731f8e8ac1ed
  exit_id: e4e23c7e-3f54-4bb9-a76e-c899034bc327
---
# living_room Living Room

You are in the living room. It is a cozy area with a couch, loveseat, and big chair with ottoman. The seats are separated by end tables.

There is a coffee table in the middle and an entertainment center with TV across from the seats.

=> kitchen The kitchen lies to the north.
=> back_porch To the west is a sliding door that leads to a porch
=> front_hall To the east is the front door, leading to the yard
\ No newline at end of file

M stories/src/house_tour/upstairs_hallway.gemif => stories/src/house_tour/upstairs_hallway.gemif +4 -8
@@ 1,10 1,6 @@
---
room_id: ee8bdc39-c471-4cdd-9241-5f5cf43143d5
room_name: Upstairs Hallway
exits:
- exit_id: 5e5b296e-68da-4e35-8da6-4a160e3435ee
  destination_id: cb178734-dd94-4333-87bd-731f8e8ac1ed
  exit_description: Might as well go back downstairs
---
# upstairs_hall Upstairs Hallway

All the doors upstairs are closed. Weird.

All the doors upstairs are closed. Weird.
\ No newline at end of file
=> front_hall Might as well go back downstairs
\ No newline at end of file

D stories/src/sample_story/01-start.gemif => stories/src/sample_story/01-start.gemif +0 -29
@@ 1,29 0,0 @@
---
room_id: the_beginning
room_name: The Beginning
exits:
- exit_description: I want to make choice a
  destination_id: made_choice_a
  exit_id: make_choice_a
  set_condition: choice_a
- exit_description: I want to make choice b
  destination_id: made_choice_b
  exit_id: make_choice_b
  set_condition: choice_b
- exit_description: If you choose not to decide you still have made a choice
  destination_id: made_no_choice
  exit_id: make_no_choice
  set_condition: no_choice
---
┌─────────────╥─────────────┐
│░░░░░╔═══╗░░░║░╔══╦═══╗░░░░╞╗
│░░░░░║╔═╗║░░░║░╚╣╠╣╔══╝░░░░│║
│░░░░░║║░╚╬══╦╗╔╗║║║╚══╗░░░░│║
│░░░░░║║╔═╣║═╣╚╝║║║║╔══╝░░░░│║
│░░░░░║╚╩═║║═╣║║╠╣╠╣║░░░░░░░│║
│░░░░░╚═══╩══╩╩╩╩══╩╝░░░░░░░│║
└╥────────────╨─────────────┘║
 ╚═══════════════════════════╝
Welcome to GemIF - The Interactive Fiction Engine.

Here you are in the beginning of the story. Make a choice below!

D stories/src/sample_story/02-middle.gemif => stories/src/sample_story/02-middle.gemif +0 -35
@@ 1,35 0,0 @@
---
room_id: made_choice_a
room_name: Doing Choice A
exits:
- exit_id: go_to_end
  destination_id: the_end
  exit_description: Or maybe choice is an illusion
---
You made choice a!

Something specific probably happens because of this.

---
room_id: made_choice_b
room_name: Doing Choice B
exits:
- exit_id: go_to_end
  destination_id: the_end
  exit_description: Or maybe choice is an illusion
---
You made choice b!

Is anything really different than if you did choice a?

---
room_id: made_no_choice
room_name: A coward who makes no choice
exits:
- exit_id: go_to_end
  destination_id: the_end
  exit_description: Or maybe choice is an illusion
---
You didn't make any choice!

But something still happens - that's how the world works.
\ No newline at end of file

D stories/src/sample_story/03-end.gemif => stories/src/sample_story/03-end.gemif +0 -32
@@ 1,32 0,0 @@
---
room_id: the_end
room_name: The End
exits:
- exit_id: bonus_round
  destination_id: secret_level
  if_condition: no_choice
  exit_description: oh, what's this?
---
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}}
{{- if .ConditionMet "no_choice"}}

wow you're soooo cool and mysterious, making no choice.
you really thing that makes you interesting? well it doesn't.
{{- end}}

---
room_id: secret_level
room_name: Bonus Round!
---
We're all so proud of you for being so cool and awesome that you didn't make a choice.

Nobody else got this ending!
\ No newline at end of file

A stories/src/sample_story/story.gemif => stories/src/sample_story/story.gemif +74 -0
@@ 0,0 1,74 @@
---
# the_beginning The Beginning

┌─────────────╥─────────────┐
│░░░░░╔═══╗░░░║░╔══╦═══╗░░░░╞╗
│░░░░░║╔═╗║░░░║░╚╣╠╣╔══╝░░░░│║
│░░░░░║║░╚╬══╦╗╔╗║║║╚══╗░░░░│║
│░░░░░║║╔═╣║═╣╚╝║║║║╔══╝░░░░│║
│░░░░░║╚╩═║║═╣║║╠╣╠╣║░░░░░░░│║
│░░░░░╚═══╩══╩╩╩╩══╩╝░░░░░░░│║
└╥────────────╨─────────────┘║
 ╚═══════════════════════════╝
Welcome to GemIF - The Interactive Fiction Engine.

Here you are in the beginning of the story. Make a choice below!

=> made_choice_a {+choice_a} I want to make choice a
=> made_choice_b {+choice_b} I want to make choice b
=> made_no_choice {+no_choice} Too cool to choose

---
# made_choice_a Doing Choice A

You made choice a!

Something specific probably happens because of this.

=> the_end Or maybe choice is an illusion

---
# made_choice_b Doing Choice B

You made choice b!

Is anything really different than if you did choice a?

=> the_end Or maybe choice is an illusion

---
# made_no_choice A coward who makes no choice

You didn't make any choice!

But something still happens - that's how the world works.

=> the_end I'll see about that...

---
# the_end The End

Though because this is a simple demo, yo always end up in the same place.

{{- if .ConditionMet "choice_a"}}

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

of you made choice b, that's too bad :(
{{- end}}
{{- if .ConditionMet "no_choice"}}

wow you're really cool and interesting, making no choice.
{{- end}}

=> secret_level {~no_choice} Oh, what's this?

---
# secret_level Bonus Round!

We're all so proud of you for being so cool and awesome that you
didn't make a choice.

Nobody else got this ending!
\ No newline at end of file

A stories/src/tutorial/01-basics.gemif => stories/src/tutorial/01-basics.gemif +217 -0
@@ 0,0 1,217 @@
---
# the_basics The Basics

Welcome to GemIF! Gemini's Interactive Fiction engine.

In this tutorial, we'll walk through writing your own stories using the .gemif file format. While stories can technically be written directly in the "compiled" YAML format, gemif format provides a much more user-friendly experience and allows authors to write without worrying about a lot of semantic whitespace.

=> get_started Cool, let's get started!
=> simple_demo {~seen_demo} Carry on with the tutorial

---
# get_started The Basics pt 2

A GemIF story is made up of `Scenes` that you moved between through the use of `Transitions`.

## Scenes

Scenes are where the action happens. Each scene has the following attributes:

* ID: A scene's ID is its unique identifier within the story. The ID is used in Transitions to move the user from scene to scene.
* Name: A scene's Name is a user-friendly identifier for the page. It is displayed at the top of the page. This scene's Name is `The Basics pt 2`.
* Description: A scene's description is free-text describing what's happening in the scene. A description can optionally be rendered as a Golang `text/template`, which can be used along with advanced GemIF features to provide dynamic story-telling.
* Transitions: Each scene has 0 or more transitions. These transitions are represented as reader choices and move the user to the next scene. Not all scenes need transitions, but these are "terminal" state of the story.

A scene can, of course, be as many lines as you want and you can use Gemini text formatting freely.

## Transitions

A transition's job is to move the reader from one scene to another. A transition can also point back at its own scene, using advanced features to change the content.

Each transition has the following attributes:

* Destination: A transition's destination must be the ID of the scene the reader should move to.
* Description: A transition's description is the text presented to the reader for the given choice.
* Condition Options: Conditions are an advanced feature of GemIF that we'll cover in detail later. Suffice it to say, transitions can add or remove conditions from the reader's game which can be used to build dynamic stories.

Each transition must exist on a single line (that is, descriptions must not have newline characters).

=> simple_demo What do scenes and transitions look like?

---
# simple_demo The Basics pt 3

So now that you're somewhat familiar with the building blocks of a story - scenes and transitions - we can discuss what they actually look like.

## GemIF Format

Stories are best and most easily written in GemIF format. This is a plaintext format that looks a lot like Gemini format.

Each story is made up of one or more `.gemif` file with whatever names you want. And each file can contain or more scene (with associated transitions).

### Scenes
Let's look back at the first page of this tutorial for a simple example. The "source" of that scene looks like:

```
  ---
  # the_basics The Basics

  Welcome to GemIF! Gemini's Interactive Fiction engine.

  In this tutorial, we'll walk through writing your own stories using the .gemif
  file format. While stories can technically be written directly in the "compiled"
  YAML format, gemif format provides a much more user-friendly experience and allows 
  authors to write without worrying about a lot of semantic whitespace.

  => get_started Cool, let's get started!
```

Each scene must start with a triple-dash (`---`). Within files, the triple-dash is used as a scene separator.

The first line of a scene contains the scene's ID and Name, prefixed with an `#`.

```
  # <SceneID> <SceneName>
```

### Transitions
The other piece of special syntax in a scene are the transitions. Transitions look a lot like Gemini links, but have a few advanced features we'll talk about later.

```
  => <DestinationID> <TransitionDescription>
```

And that's basically all there is to it (we'll get into advanced features shortly).

=> the_basics {+seen_demo} If you want to see the first page again for reference, choose this.
=> story_metadata Or continue on...

---
# story_metadata The Basics pt 4

One last thing before we carry on to advanced features.

## Story Metadata
In addition to the .gemif files, GemIF stories require a single file named `metadata.yml`. This file contains basic information (well, metadata) about the story for GemIF to display to the reader.

The format is pretty simple and self-explanatory, but for completeness, your `metadata.yml` must contain:

* id: a server-unique id of your story
* name: a user-friendly name of your story
* description: a brief description of what you're story is about
* author: name or other identifier of the story author.

Here's the `metadata.yml` of this story, for reference.

```
  ---
  id: sample_story
  name: Sample Story
  description: A sort of hello world
  author: Norm MacLennan
```

=> conditions Alright, I get it, I get it. Bring on the advanced features!

---
# conditions Advanced Features pt 1

Now you know the basics of creating stories. You should be able to use these basics to create plenty of linear or branching narratives. But what if you need just a _little_ more power?

## Conditions
Conditions are "tags" that can be attached to the reader's game to denote that they have made (or not made) a particular choice.

Conditions can be attached or removed from game state through transitions. You can also use conditions to decide whether or not to present a given transition to a user.

### With Transitions
Conditions work with transitions through the use of condition directives on the transition itself. These directives are placed between curly-braces in between the destination and description.

It looks like this:

```
  => <destination> {<condition directives>} <description>
```

Did you notice in the previous scene, that if you moved back to the first scene you could suddenly jump right back to where you were? That was done with transitions.

On the GemIF demo scene, the transition back to the intro scene looks like:

```
  => the_basics {+seen_demo} If you want to see the first page again for reference, choose this.
```

That little `{+seen_demo}` attaches the `seen_demo` condition to the game state.

Then, back in the first scene there is a conditionally-hidden transition that didn't show up until you had that condition:

```
  => simple_demo {~seen_demo} Carry on with the tutorial
```

`{~seen_demo}` will only show the transition option if the reader's state has the `seen_demo` condition.

There are a handful of condition directives you can use, and you can provide as many as you like for each transition, separated with a space.

* `+condition`: adds `condition` to the user's state
* `-condition`: removes `condition` from the user's state
* `~condition`: only presents the transition to the user when they have `condition`
* `!condition`: only presents the transition to the user when they DON'T have `condition`

=> template What else can I do with conditions?

---
# template Advanced Features pt 2

In the previous scene, you learned how to use conditions to track information about the story state and conrol which choices the reader is presented with.

## Templates

You can also use conditions to conditionally render different parts of room templates. Scene descriptions are rendered as Golang `text/template`s. 

Templates have a `.Conditions` list in scope and some utility functions available.

If you returned to the first scene previously and gained the `seen_demo` condition, you should see `[seen_demo]` printed below, otherwise you may just see `[]:

{{.Conditions}}

That was produced with the token {{"`{{.Conditions}}`"}}.

A utility function, `ConditionMet` is in-scope to check if a reader has met a condition. Below you should see `true` or `false` based on whether or not you have the condition:

{{.ConditionMet "seen_demo"}}

This was produced with the token `{{"{{.ConditionMet \"seen_demo\"}}"}}`.

You can check the Golang text/template documentation for ways you can use templates to write if statements and other advanced conditional rendering.

=> whats_next Cool, well what's next?

---

# whats_next Compiling Your Story

Once you've written your story (or probably more like as you write your story), you can use `gemifc` to compile your story to YAML format for use by the `gemif` server.

```
$ gemifc [input directory] [output directory]
```

So to compile this story:

```
$ gemifc ./stories/src/tutorial ./stories/compiled
Compiling story ./stories/src/tutorial to ./stories/compiled/

Finished loading story:
  Name: Writing Tutorial
  Author: Norm MacLennan
  Descriptions: Documentation on writing GemIF stories
  Number of Rooms: 7

Serializing and writing to disk...
Done!
```

If you want to publish your story, just drop it in the configured stories directory of your GemIF server. If you don't want to run it yourself, feel free to email it to the GemIF mailing list at `~nromdotcom/gemif@lists.sr.ht` and I'd almost certainly be happy to host it for you.

THE END
\ No newline at end of file

A stories/src/tutorial/metadata.yml => stories/src/tutorial/metadata.yml +5 -0
@@ 0,0 1,5 @@
---
id: tutorial
name: Writing Tutorial
description: Documentation on writing GemIF stories
author: Norm MacLennan
\ No newline at end of file