Sharpee's Text Emission Strategy

Sharpee's Text Emission Strategy

One of the foundational aspects I decided for Sharpee was to use modern architecture. The world model is stored in a bi-directional graph and that is fully working and tested.

The next "layer" is the IF "things" and then Grammar, Parser, Language, Game Runner, and the author's Story.

But one innovation is to never emit text as a turn is being processed. Instead, Sharpee traps every event that happens during a turn and writes these events to an Event Source, while also updating any changes to the world model.

Once the turn's activities have been completed, the event source will contain a list of things that happened.

The last thing the Game Runner does is call the Text Service which will iterate through the event source, examine the state of the world, and build a templated set of output.

That output can take on any form, but for the first iteration I'll just have what we'd call a "standard IF screen" with a title, scrolling location-based text with reports for actions, exits, and objects that can be seen. In some later iteration of the Text Service, there will definitely be a web emitter, but I haven't taken any time to design that at this time.

This is just a rough picture of the start of an event source. You can imagine a very long list of everything that happens in a story, where multiple actions may get fired within a single turn.

One major benefit of an event source is to be able to "replay" from various states, including the start state. Outside of "undo", there are other things we might do with this capability.

For Sharpee's v1 purposes, we just have a central place to gather all of the information from the results of a turn and emit something to the player.

The elimination of streaming text and managing spaces, punctuation, newlines, tabs, matching quotations will likely reduce the amount of code an author needs to concern themselves with by a lot.

The other aspect of this is querying the world model for its state. Since I've built this in C# we get to use LINQ queries. Here's a bit of code to show you what that looks like:

using System;
using System.Collections.Generic;
using System.Linq;
using DataStore;

namespace IFWorldModel
{
    public static class IFQueries
    {
        public static IEnumerable<Thing> GetVisibleThingsInRoom(this WorldModel worldModel, Room room)
        {
            return worldModel.GetRoomContents(room).Where(thing => thing.IsVisible());
        }

        public static IEnumerable<Thing> GetTakeableThingsInRoom(this WorldModel worldModel, Room room)
        {
            return worldModel.GetRoomContents(room).Where(thing => thing.CanBeTaken());
        }

        public static IEnumerable<Container> GetOpenContainersInRoom(this WorldModel worldModel, Room room)
        {
            return worldModel.GetRoomContents(room)
                .OfType<Container>()
                .Where(container => container.IsOpen);
        }

        public static IEnumerable<Thing> GetContentsOfAllOpenContainers(this WorldModel worldModel, Room room)
        {
            return worldModel.GetOpenContainersInRoom(room)
                .SelectMany(container => container.Contents);
        }

        public static IEnumerable<Person> GetCharactersInRoom(this WorldModel worldModel, Room room)
        {
            return worldModel.GetRoomContents(room).OfType<Person>();
        }

        public static IEnumerable<Door> GetDoorsInRoom(this WorldModel worldModel, Room room)
        {
            return room.GetDoors().Select(d => d.Door);
        }

        public static IEnumerable<Thing> GetAllAccessibleThings(this WorldModel worldModel, Room room)
        {
            return worldModel.GetVisibleThingsInRoom(room)
                .Concat(worldModel.GetContentsOfAllOpenContainers(room))
                .Distinct();
        }

        public static IEnumerable<Thing> GetItemsMatchingDescription(this WorldModel worldModel, Room room, string description)
        {
            return worldModel.GetAllAccessibleThings(room)
                .Where(thing => thing.MatchesDescription(description));
        }

        public static bool IsItemInInventory(this WorldModel worldModel, Guid playerId, string itemName)
        {
            return worldModel.GetPlayerItems(playerId)
                .Any(item => item.Name.Equals(itemName, StringComparison.OrdinalIgnoreCase));
        }

        public static IEnumerable<Room> GetConnectedRooms(this WorldModel worldModel, Room currentRoom)
        {
            return worldModel.GetRoomExits(currentRoom)
                .Select(exit => exit.Destination);
        }

        public static IEnumerable<Thing> GetLightSourcesInRoom(this WorldModel worldModel, Room room)
        {
            return worldModel.GetAllAccessibleThings(room)
                .Where(thing => thing.GetPropertyValue<bool>("IsLightSource", false));
        }

        public static bool IsRoomDark(this WorldModel worldModel, Room room)
        {
            return room.IsDark && !worldModel.GetLightSourcesInRoom(room).Any();
        }

        public static IEnumerable<Thing> GetThingsPlayerCanInteractWith(this WorldModel worldModel, Guid playerId)
        {
            var playerLocation = worldModel.GetPlayerLocation(playerId);
            return worldModel.GetAllAccessibleThings(playerLocation)
                .Concat(worldModel.GetPlayerItems(playerId));
        }

        public static IEnumerable<Thing> FindPathBetweenRooms(this WorldModel worldModel, Room start, Room end)
        {
            // This could be implemented using a breadth-first search or A* algorithm
            // For simplicity, we'll just return an empty list for now
            return new List<Thing>();
        }
    }
}

Because our underlying data store is a graph, we can easily use LINQ to find one thing or lists of things based on types and properties. You get a list of all treasures, a list of treasures in a trophy case, list of treasures the player has not seen, and so on. The path finder is basic graph logic, though that's something I'd need to research since I'm a newbie with graph data.

The code above is just an example I asked Claude to create. A working IFQueries class will be honed to the kinds of things setup in a Standard Library.

That's my update for today. Event sourcing and LINQ queries into the underlying graph.

Subscribe to My So Called Interactive Fiction Life

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe