Interactive Fiction Over HTTP
Surprisingly few people have written about the possibilities of using conventions from HTTP and hypermedia API design to model interactive stories. This might be because a lot of these works are distributed as scripts designed to run in interpreters (like Inform), as single-page JS/HTML apps (like Twine), or binary executables.
The request–response message exchange pattern at the foundation of HTTP is an ideal fit for turn-based or choice-based stories, but there are some downsides to do with the necessary layers of mapping and indirection that are introduced whenever you use a standardised interface rather than an interface specifically designed for an application.
The main benefit of this architecture is that it’s distributed by default. Features like multi-player/co-op stories, sharing progress across multiple devices and centralised updates to story content follow directly from the basic layer of client–server interaction.
Here, I’ve attempted to describe some of the fundamental patterns, anti-patterns and tradeoffs that emerge when mapping the structures of IF and choice fiction to the uniform interface of HTTP. It’s rough and somewhat under-furnished, but hopefully serves as a starting point for further exploration.
Basic hypertext
Starting with the assumption that every distinct scene or passage in a story is represented by a unique URI, we can move through the story by linking from one URI to the next. This is the basic hypertext format of the web and can work with JSON data APIs as well as HTML documents.
GET /story/2
Unfortunately, this simple pattern won’t help us much if we want to model persistent state changes associated with the reader’s choices. The GET
verb isn’t appropriate for this situation as it’s stateless and idempotent by design. It represents the action of reading a document, not the action of choosing to go down a new path (a choice which may impact on the overall world state).
Post to current resource
To represent an update to the story/world memory when the reader moves to a new place in the story, a server-side state change is needed.
One way of expressing this in an HTTP API is to send a POST
to the current resource with the desired destination.
POST /story/1
{
"to": {
"href": "/story/2"
}
}
Post to target resource
Or the operation could be inverted, with the post going to the address of the target passage.
POST /story/2
{
"from": {
"href": "/story/1"
}
}
Modeling choices explicitly
Alternatively, the action of moving to a new place in the story could be represented as a resource itself.
POST /choice
{
"choice": {
"from": {
"href": "/story/1"
},
"to": {
"href": "/story/2"
}
}
}
This approach would work better where the story is designed to be a data API. If the client app is based around a traditional hypertext document interface, one of the previous approaches might be preferable.
One of the advantages of modeling choices as a resource is that the client no longer has to explicitly specify the passage they want to navigate to. Instead, the client can just send a chunk of data associated with the reader’s choice, and let the server figure out where to send them next.
POST /choice
{
"choice": {
"direction": "NORTH",
"from": {
"href": "/story/1"
}
}
}
This takes a little more effort to implement than using stateless hyperlinks, but most common branching story patterns can be modeled in this way.
RESTful world model
Traditionally, many people considered interactive fiction to be something different to the ‘gamebook’, ‘choice fiction’ or ‘choose your own adventure’ format. From this perspective, the basic premise of IF is to simulate an environment as a world model and allow commands to observe it and change it.
The emphasis on managing updates to a persistent world model is an especially good fit for a RESTful style API. Essentially, this pattern requires mapping a system of spaces and objects into set of resources, with each individual object represented by a unique URL. The major structuring decision here is how to divide up the story into scenes or spatial units, as these will form the fixed containers in the URL hierarchy.
For now, let’s assume that the primary unit of our story sections is a ‘room’, and each room has zero or more ‘exit’ nodes which connect to other rooms.
The basic resource hierarchy can be modeled using a unique identifier for each room. Sending a GET
to that room gives us the current information about the environment.
GET /rooms/1
The list of exits can be represented as part of the room hierarchy.
GET /rooms/1/exits
Individual exits could also have a unique address if needed.
GET /rooms/1/exits/1
Moving between rooms could be implemented using one of the patterns described above (Post to target resource or Modeling choices explicitly).
All other items in the environment can also be implemented as part of the URI hierarchy.
GET /rooms/1/items
Manipulating these items can be done using HTTP’s uniform verbs for modifying state.
For example, using an item in the room could be done with a POST
to the object ID (or to the room itself).
POST /rooms/1/items/1
Putting an item down from the inventory into the world state could be done with a PUT
.
PUT /rooms/1/items/2
{
"item": {
"id": 2
}
}
Without overloading the request method with a custom MOVE
verb (see below), picking up an item requires a separate collection resource that represents the inventory.
PUT /inventory/2
{
"item": {
"id": 2
}
}
The advantages of this style are that it’s well supported by widely available third party tools, is easy to understand and follows the principle of least surprise. There are several downsides though:
- Command-based interfaces are easier to model with a rich set of verbs. They don’t mesh well with the constrained set of verbs in REST. The accepted wisdom of RESTful design is to replace each unique verb with a noun that represents the concept as a resource (the introduction of the inventory resource above is a good example of this). This works really well for APIs that are widely used and have longevity well beyond their initial scope, but it’s overly restrictive and annoying in situations where the API explicitly models a set of commands which can be more succinctly expressed as verbs. Adding noun after noun leads to verbosity and dilution of the core concepts.
- There’s a subtle disjointedness between the uniform set of verbs in HTTP/REST and the standard set of verbs that model interactive fiction. For example,
GET
is an idempotent read operation in HTTP which maps toLOOK
orEXAMINE
in traditional parser games. Conversely, in traditional IF,GET
is an operation which changes the world state, generally a synonym for the verb to take. - HTTP APIs are designed around having consistent path references and data structures across the various resources. In most information management contexts, this consistency is a good thing. But games and stories are a very different domain, and often require a certain degree of obfuscation or orchestration in order to avoid leaking their narrative secrets too soon. Patterns for revealing hidden information or unlocking content in a sequence involve quite different motivations, forces and constraints from those underlying the principles of RESTful API design. Idempotence is an extremely important principle when building distributed systems. Is it a useful property for an interactive fiction API?
These issues might be minor stylistic niggles, or they might signal an overall lack of coherence and cohesion.
Different people will have different opinions over whether the RESTful style works well here, or whether it’s a case of trying to slam square pegs into round holes.
Move method
A weird but potentially interesting idea is to use the little-known MOVE
verb from RFC2518 to navigate through a story or game map. Instead of telling the server which part of the story we want to go to, we ‘move’ that part of the story to a ‘current page’ resource associated with the reader.
MOVE /story/2
Destination: /reader/current
Does this get closer to the traditional notion of movement in interactive fiction? At first I thought it might, but after experimenting with it a bit, I’m not convinced.
The most glaring problem is that the implementation described here is technically incorrect. The desired effect is to move each selected part of the story to the current reading context without altering the rest of the story, so the correct verb here is COPY
rather than MOVE
.
What led me to consider this at first was that the semantics of POST
, PUT
, DELETE
etc are all based around creating, updating and deleting documents, rather than moving around an interactive world. But the COPY
and MOVE
verbs from WebDAV are oriented towards operations on files and directories and don’t provide any useful semantics beyond this (whereas POST
and other generic verbs do).
Choice model with custom methods
The existence of a MOVE
verb suggests fascinating possibilities for modeling interactive fiction and turn-based games via HTTP APIs, but the reality is that this usage would be breaking the expected semantics of MOVE
and COPY
.
To get any utility out of this, we’d need to start overloading the basic HTTP protocol with a new set of custom verbs to support interactive fiction. This would be elegant and straightforward if it worked, but it’s very unconventional and could cause problems for interoperability (many common web servers and HTTP clients have fixed lists of supported HTTP methods, and we’d lose the benefits of caching/proxying/load balancing that HTTP provides).
But lets assume for the sake of argument, that interoperability isn’t a concern and we want to try to extend HTTP to support our desired story navigation.
In a story based on choices, we can navigate through the directed graph of the branching passages using a transitive verb construction with EXIT
or ENTER
. We just have to choose which direction we emphasise.
The EXIT
method would apply to the current selected passage in order to direct the reader to the next chosen passage (see: Post to current resource above).
EXIT /story/1
To: /story/2
We could also support the same structure of exiting the current passage in an undirected style by defining our own semantics for MOVE
.
MOVE /story/1
Destination: /story/2
The ENTER
method would apply to the next chosen passage and direct the reader there (see: Post to target resource above).
ENTER /story/2
From: /story/1
World model with custom methods
Custom methods could provide an intuitive (if iconoclastic) alternative to the RESTful world model pattern.
Instead of using the uniform HTTP verbs, and placing emphasis on the content body to provide semantics for POST
and PUT
operations, we can support the set of verbs for world exploration directly in HTTP:
To look around the room, we use the LOOK
verb (roughly equivalent to a standard GET
).
LOOK /story/1
To look at an item in more detail we use EXAMINE
(this could also be a synonym for LOOK
, depending on the story requirements).
EXAMINE /story/1/items/2
To pick up an item from the room and add it to inventory, we use TAKE
(this dodges the problem of disambiguating GET
).
TAKE /story/1/items/2
The advantages and disadvantages of this approach are the same as any other use of custom HTTP methods. Story-specific operations can be handled more efficiently with less redundancy than by using the uniform verbs, but the tradeoff is losing the layered architecture that allows different servers and clients to be interoperable by default.
The uniform interface isn’t something to throw away lightly. Many projects would be crippled if they were unable to rely on off-the-shelf software. So despite how interesting and tempting these patterns are, I’d recommend designing a custom RPC protocol before attempting to overload HTTP with application-specific verbs.