A Text-Adventure Game Engine

Published on . Updated on .
Tags: python, computing, games

Introduction

As a work sample for a job application, I spent some time writing a Python library which lets a game designer create basic text adventures. Though the library is a mere prototype, I was able to use it to implement a sample game called “Bread Master.”

My library and its documentation are available on my public GitLab account. The rest of this page reproduces some introductory sections from the library’s README, as of 2020-05-09.

Aims

Guiding principles for this project.

  1. Hit the mark.

    TA gave me free reign to submit any code sample I chose. Having developed interactive fiction but never made my own tool for such, I set out to build a small but complete text-adventure engine. I defined “complete” to mean I could hand the library to another programmer and expect them to be able to make a game, ideally after no more introduction than reading and playing the sample game, “Bread Master.”

  2. Do right by both the user and the user’s user, the game player.

    I wanted the user to be able to open this library, read the thirty or forty lines of code that specify “Bread Master”, play that game, and be ready to write his own little game. [For explicit user instructions, see “Instructions for Game Designers” below.]

    It seemed to me that, if I were a game designer thinking about using this game library, the most important thing would be an assurance that my players would be well-served out of the box. Thus, I tried to include quality-of-life features for players of games made with this library, including a help menu listing commands and their synonyms; parser special cases to allow more freedom in typing Commands; and prompting the player with choices of direct object if they failed to provide one for a targetable Command, rather than rejecting their command outright.

  3. Document design decisions.

    Having been a research programmer, I believe a long-term software project is most likely to succeed if one can make progress on it even if only able to return to it at intermittent intervals. The way to achieve that state is to ensure that the code is no trickier than it needs to be, and that any potential sources of confusion are patiently explained, in as much English as is necessary.

    My standard of achievement for Aim 3 was that, anywhere Future Me or Future You might have been confused or skeptical, that emotion would be dispelled in code commentary or this README. I couldn’t achieve lofty heights in five days, but I could make crystal-clear the reasons behind my choices and the next steps for continued work.

  4. Convince TA I’m the right man for the job.

    The role for which TA is considering me is a bit unusual. The team is small, and the work isn’t tied to the quarterly business cycle. I’d be assisting TA with long-term, grant-funded research by improving an existing codebase. While assisting with new features would be part of the job, the primary duties would be refactoring, testing, and above all thinking. In this repository, I hope I’ve shown that I can think long-term, keep track of what needs doing and what’s unimportant, justify my design choices, write clean code, explain my code to others, and put user needs first.

Features

A library user can:

Game players enjoy engine convenience features such as:

Scope

Many possible features were out of scope given time and quality constraints. In general, if I felt I wanted to add something but wouldn’t be able to do so without failing to achieve Aim 1, I erred on the side of documenting it in this README to show that I’d at least considered the problem. This prevented me from dancing to the seductive song of scope creep. Things I left out include:

Instructions for Game Designers

For an example file which shows all these steps, see breadmaster.py.

  1. In a Python file, import everything from runner.py.
  2. In a string variable, write a sentence or two which situates your player in the game, and introduces the identity of their player character. This will be printed when the game starts. You might consider including the game’s title, author, and other publication info within this string.
  3. Define any custom functions you need for your game. At minimum, you will need to define a win condition (see runner.GameRunner for details.) If you want items to have custom effects, you can define functions to pass in for their activate_fn parameter; this allows you to change game state in response to player commands, or provide different options for activating an item based on the current game state.
  4. Define the items your player will be able to interact with in your game. Each one needs a name, a description, and a list of the commands to which it can respond (aside from “look”, which is always available.)
  5. Define the rooms which constitute the game world’s structure. Each one needs a name, a description, a dictionary mapping from directions to other room names, and a list of the items which populate that room at the beginning of the game.
  6. Write a message to show the player when they win your game.
  7. Define the items, if any, which the player will have on their person when the game begins.
  8. In your main() function, pass all of the above to the GameRunner constructor, then ensure main() is called when your Python file is run.
  9. Test out your game!