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.
Guiding principles for this project.
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."
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.
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.
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.
A library user can:
- implement custom
Rooms) with synonyms
- choose the player's starting
- set an introductory message for the game
- set the game's win conditions
- set a win message for the game
- define custom functions for activatable
Items, which change game-world state or give
Items additional functionality
Game players enjoy engine convenience features such as:
- sequential entry of multiple commands with "." (if you enter "go north. take jelly", the engine will run "go north" and "take jelly" in sequence)
- being prompted to enter the "help" command if your command is unrecognized
- command synonyms (example: "pick up", "take", "get" are all interpreted as the "take" command)
- parseable articles (example: "take the doll", "take a doll", "take teh doll" are all interpreted as "take doll")
- being prompted for a direct object when you forget one (example: if you just enter "take", the engine will prompt you to choose a currently-visible object)
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:
- ability for user to implement custom
- the possibility of losing the game or displaying a losing message
- scoring or other measurement (other than
- turn count and/or timed actions
- many typical adventure-game commands (notably, "take" is implemented but counterpart "drop" is not)
Instructions for Game Designers
For an example file which shows all these steps, see
- In a Python file, import everything from
- 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.
- Define any custom functions you need for your game. At minimum, you will need to define a win condition (see
runner.GameRunnerfor details.) If you want items to have custom effects, you can define functions to pass in for their
activate_fnparameter; 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.
- 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.)
- 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.
- Write a message to show the player when they win your game.
- Define the items, if any, which the player will have on their person when the game begins.
- In your
main()function, pass all of the above to the
GameRunnerconstructor, then ensure
main()is called when your Python file is run.
- Test out your game!