Home

Awesome

zmachine-api

Score: 0 Moves: 0

You are standing in a repository. There is a README here.

Overview

This project aims to provide a simple web service that can bring up and run games in a z-machine. It was originally written to be interacted with by a hubotscript, but could presumably be used by anything you like, which is the fun of APIs.

The API uses the dfrotz z-machine interpreter. Each game you start is run in a different dfrotz process, and APIs for managing these processes are included.

This was thrown together by a few developers, and could almost certainly be better than it is. Please feel free to contribute improvements!

Dependencies

Building

Really just the dependencies need to be built. It should just be a matter of:

make all

Frotz 2.44 will be downloaded and built, and a symlink created so zmachine-api can find it. All node dependencies will be installed as well.

Configuration

You should copy the existing .env.example to .env and customize it to set the PORT (e.g. 3000) and LOG_LEVEL (e.g. warn or debug) options.

We support writing save files to Amazon S3. This is configured in the .env file with your own AWS credentials to use S3 saves. If S3 is not configured, games will be saved to disk only.

Bringing up a server

npm start

What about the games?

Create a zcode directory to store your games. We currently only suport .z3.z8 format games.

We don't provide you with any games, but feel free to drop any zcode files you come across in the zcode directory. There are lots of good public domain games out there, why not try a bunch?

There is a great selection of zcodes games here as well as many other places. if-archive/games/zcode

There is a Dockerfile here

You can also skip most of this and use the provided Dockerfile, if it suits you. It'll take care of the dependencies and build process and give you just a container listening for HTTP requests.

If you're using Docker, and you don't have S3 configured, take steps to ensure that /root/saves is preserved (use a Volume, mount a network drive, etc) or your saved games will be lost when the container is redeployed.

The API

Here's how the API works.

GET /titles

Returns a list of all installed z-code games. These are what you can use with POST /games

Response:

[
  {
      "zFile": "ZORK1.z5"
  },
  {
      "zFile": "ZORK2.z5"
  }
]

GET /games

Returns a list of all active games.

Response:

[
  {
    "pid": 12345,
    "name": "foo",
    "zFile": "foo.z5",
    "label": "foo game"
  }
]

POST /games

Spawns a new zmachine with a specific game.

Request body:

{
  "game": "foo",
  "label": "foo game"
}

Response:

{
  "pid": 12345,
  "data": "Startup text from the game"
}

DELETE /games/:pid

Where :pid is the value of pid returned by POST /games

Stops a running zmachine process and deletes from the list of active games

Response:

Game for :pid terminated.

POST /games/:pid/action

Where :pid is the value of pid returned by POST /games

Send a game action to a running zmachine

Request body:

{
  "action": "go west",
}

Response:

{
  "pid": 12345,
  "data": "You go west. It's okay."
}

POST /games/:pid/save

Where :pid is the value of pid returned by POST /games

Saves the game's current state to a file and uploads to S3. The filename that is written to S3 is saves/:label-:game-:file.sav where :label is the label you used when spawning the zmachine instance, :game is the game file that is running (without the file extension), and :file is the filename specified in the request body of this API call. If S3 is not set up, the game will be saved locally under the same path.

Request body:

{
  "file": "somefile",
}

Response:

{
  "pid": 12345,
  "data": "Whatever the game says in response to the save action"
}

POST /games/:pid/restore

Where :pid is the value of pid returned by POST /games

Restore a saved file into a zmachine process.

Request body:

{
  "file": "somefile",
}

Response:

{
  "pid": 12345,
  "data": "Whatever the game says in response to the load action"
}

Tutorial

This tutorial will walk through the steps to create a long-running game, one where you don't want the zmachine process to stay up and running. In this tutorial the game will be saved and restored between every single command.

dfrotz is extremely lightweight (Zork was originally run on a TRS-80, after all), and keeping the processes around generally won't hurt anything. But if your machine is very ephemeral (yay, cloud!), you might not be able to rely on the processes being alive if a player comes back to the game after months. Or you might just be a neat freak who can't stand to have all those processes lying around. Regardless, this tutorial will be helpful in understanding how the API interacts with the dfrotz process and how saved games work.

A session ID provided by your application is used to keep track of the game for each player. The session ID can be anything you like, it just needs to be something you can keep track of for the entire length of the game.

  1. POST /games with a zmachine game name and your session ID.
{
  "game": "zork",
  "label": "player1234"
}

zmachine-api will respond with the OS process id of the instance it spawned, and the startup text from the game.

{
  "pid": 12345,
  "data": "welcome to the game"
}

Return the data to the player.

  1. Send a game command to the PID of the game
POST /games/12345/action

{
  "action": "go west",
}

zmachine-api responds with

{
  "pid": 12345,
  "data": "You go west. It's okay."
}

Return the data to the player.

  1. Pick a file name you wish to save this game under. We're going to use the same name for every saved game. Saved games automatically have the session ID appended to them, so we don't need to use something unique for the name.

Send POST /games/12345/save using the saved game name you selected:

{
  "file": "save"
}

The response will contain a data element with whatever the game responds with when you save. Normally, a player would see this, but since they didn't ask for the save, telling them you just saved after every game action would be pretty confusing.

  1. Quit the zmachine process with a DELETE /games/12345

  2. When you get a new command, it's time to restore the game. POST /games using the same session ID label and game as you used in the first step:

{
  "game": "zork",
  "label": "player1234"
}

zmachine-api responds with a new process ID, and the initial game text.

{
  "pid": 67890,
  "data": "Startup text from the game"
}

Because we're restoring the game automatically based on the label you provided, don't send the data element back to the player. They don't know we're saving and restoring repeatedly, game save and restore messages would be confusing to them.

  1. Using POST /games/67890/restore restore the game in the background to the new zmachine process, using the same file name as was used to save it.
{
  "file": "somefile",
}
  1. Start over at step 2, but now with the pid you received in step 5.

Bugs?

Yes, probably.

By all means, open an issue on GitHub. Or, better yet, submit a pull request!