████████ ███████ ██ ██ ████████
██ ██ ██ ██ ██
██ █████ ███ ██ █████
██ ██ ██ ██ ██
██ ███████ ██ ██ ██
███████ ███ ██ ██████ ██ ███ ██ ███████
██ ████ ██ ██ ██ ████ ██ ██
█████ ██ ██ ██ ██ ███ ██ ██ ██ ██ █████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
███████ ██ ████ ██████ ██ ██ ████ ███████
An HTML-based text adventure game engine. Small and easy to use with no dependencies. Highly customizable.
Very little programming is required, but several JavaScript hooks are provided if you are inclined to use them!
To create your own adventure, you can use one of the files in the game-disks folder as a template. For example, take a look at the disk called newDiskTemplate.
Include your "game disk" (a function returning JSON data) in index.html and load it with loadDisk(myGameData)
. (Look at index.html in the repo for an example.)
The end product will be your very own text adventure game, similar to this one. It's a good idea to give that game a try to get introduced to the engine.
text-engine
uses a disk metaphor for the data which represents your game, like the floppy disks of yore.
Including index.js from this repository in your index.html <script>
adds a several functions to the global namespace. One of these is called loadDisk
. loadDisk
accepts a single argument, which is your disk -- a function returning a JavaScript object (JSON).
A disk is a function which returns a JavaScript object that describes your game. At minimum, that object must have these two top-level properties:
Property | Type | Description |
---|---|---|
roomId |
String | This is a reference to the room the player currently occupies. Set this to the ID of the room the player should start in. |
rooms |
Array | List of rooms in the game. |
There are other properties you can choose to include if you like:
Property | Type | Description |
---|---|---|
inventory |
Array | List of items in the player's inventory. |
characters |
Array | List of characters in the game. |
You can also attach any arbitrary data you wish. For instance, you could have a number called "health" that you use to keep track of your player's condition.
A room is a JavaScript object. You usually want a room to have the following properties:
Property | Type | Description |
---|---|---|
name |
String | The name of the room will be displayed each time it is entered. |
id |
String | Unique identifier for this room. Can be anything. |
desc |
String | Description of the room, displayed when it is first entered, and also when the player issues the look command. |
exits |
Array | List of paths from this room. |
Rooms can have these other optional properties as well:
Property | Type | Description |
---|---|---|
img |
String | Graphic to be displayed each time the room is entered. (This is intended to be ASCII art.) |
items |
Array | List of items in this room. Items can be interacted with by the player. |
onEnter |
Function | Function to be called when the player enters this room. |
onLook |
Function | Function to be called when the player issues the look command in this room. |
An exit is an object with the following properties:
Property | Type | Description |
---|---|---|
dir |
String | The direction the player must go to leave via this exit (e.g. "north". |
id |
String | The ID of the room this exit leads to. |
An exit can optionally have a block
as well:
Property | Type | Description |
---|---|---|
block |
String | Line to be printed if the player tries to use this exit. If this property exists, the player cannot use the exit. |
An item is an object with a name:
Property | Type | Description |
---|---|---|
name |
String or Array | How the item is referred to by the game and the player. Using an array allows you to define multiple string names for the item. You might do this if you expect the player may call it by more than one name. For instance ['basketball', 'ball']. When listing items in a room, the engine will always use the first name in the list. |
Items can have these other optional properties as well:
Property | Type | Description |
---|---|---|
desc |
String or Array | Description displayed when the player looks at the item. If multiple descriptions are provided, one will be chosen at random. |
isTakeable |
Boolean | Whether the player can pick up this item (if it's in a room). Defaults to false . |
onUse |
Function | Function to be called when the player uses the item. |
onLook |
Function | Function to be called when the player looks at the item. |
onTake |
Function | Function to be called when the player takes the item. |
A character is an object with the following properties:
Property | Type | Description |
---|---|---|
name |
String or Array | How the character is referred to by the game and the player. Using an array allows you to define multiple string names for the character. You might do this if you expect the player may call them by more than one name. For instance ['Steve', 'waiter', 'garçon']. When listing characters in a room, the engine will always use the first name in the list. |
roomId |
String | The ID of the room the character is currently in. The player can only talk to characters in the room with them. |
Characters can have these other optional properties as well:
Property | Type | Description |
---|---|---|
desc |
String or Array | Description. Text displayed when the player looks at the character. If multiple descriptions are provided, one will be chosen at random. |
topics |
String or Array | If a string is provided, it will be printed when the player talks to this character. Otherwise, this should be a list of topics for use in the conversation with the character. |
onTalk |
Function | Function to be called when the player talks to the character. |
onLook |
Function | Function to be called when the player looks at the character. |
A topic is something you can talk to a character about, and as you may have guessed, is a JavaScript object. A topic requires an option
, and either a line
or an onSelected
function, or both:
Property | Type | Description |
---|---|---|
option |
String | The choice presented to the player, with a KEYWORD the player can type to select it. If the keyword is written in uppercase, the engine can identify it automatically. (Otherwise, you'll need to specify the keyword in a separate property.) The option can be just the keyword itself, or any string containing the keyword. |
line |
String | The text to display when the user types the keyword to select the option. |
onSelected |
Function | Function to be called when the player types the keyword to select the option. |
Topics can have these other optional properties as well:
Property | Type | Description |
---|---|---|
removeOnRead |
Boolean | Whether this option should no longer be available to the player after it has been selected once. |
prereqs |
Array | Array of keyword strings representing the prerequisite topics a player must have selected before this one will appear. (When topics are selected, their keywords go into an array on the character called "chatLog".) |
keyword |
String | The word the player must type to select this option. This property is only required if the option itself does not contain a keyword written in uppercase. |
That's everything! If you've made a JSON object with a roomId
and a list of rooms
-- that is, a disk -- you've got a playable game!
Just pass a reference to your disk to the loadDisk function. Take a look at index.html to see an example.
I've saved my disk to a const
variable called demoDisk
in game-disks/demo-disk.js. I've included that file and index.js
in my HTML file, and added a script tag with a single line to call loadDisk(demoDisk)
. The game boots when index.html is loaded in a web browser.
You can use the included index.html file in your own project, or you can create your own.
Sometimes you just want to start from scratch. If you wish to make your own HTML file, just be sure it contains the following two elements:
- A
div
with IDoutput
. This is where the game text will appear.
<div id="output"></div>
- An
input
with IDinput
. This is where the player will enter commands.
<input id="input" autofocus>
Once your game is running, the player can use the following commands:
LOOK: 'look at key'
TAKE: 'take book'
GO: 'go north'
USE: 'use door'
TALK: 'talk to mary'
ITEMS: list items in the room
INV: list inventory items
SAVE: save the current game
LOAD: load the last saved game
IMPORT: save to a file
EXPORT: load from a save file
HELP: this help menu
Functions are reuseable bits of JavaScript code. text-engine provides several of these which you can use, for instance in callbacks like onUse
, onLook
, onEnter
, etc.
Writing and using functions is optional, but they give you a great deal more flexibility with the sort of game you can make.
Print a line of text to the console. It takes up to two arguments:
Argument | Type | Description |
---|---|---|
line |
String | The text to be printed. |
className |
String | Optional. The name of a CSS class to apply to the line. You can use this to style the text. |
Get a random item from an array. It takes one argument:
Argument | Type | Description |
---|---|---|
arr |
Array | The array with the items to pick from. |
Get a reference to a room by its ID. It takes one argument:
Argument | Type | Description |
---|---|---|
id |
String | The unique identifier for the room. |
Move the player to particular room. It takes one argument:
Argument | Type | Description |
---|---|---|
id |
String | The unique identifier for the room. |
Get a reference to an exit by its direction name from a list of exits. It takes two argument:
Argument | Type | Description |
---|---|---|
dir |
String | The name of the exit's dir (direction) property, e.g. "north". |
exits |
Array | The list of exits to search. (Usually you would get a reference to a room and pass room.exits .) |
Get a reference to a character. It takes up to two arguments:
Argument | Type | Description |
---|---|---|
name |
String | The character's name. |
chars |
Array | Optional. The array of characters to search. Defaults to searching all characters on the disk. |
Get an array containing references to each character in a particular room. It takes one argument:
Argument | Type | Description |
---|---|---|
roomId |
String | The unique identifier for the room. |
Get a reference to an item, first looking in inventory, then in the current room. It takes one argument:
Argument | Type | Description |
---|---|---|
name |
String | The name of the item. |
Get a reference to an item in a particular room. It takes two arguments:
Argument | Type | Description |
---|---|---|
itemName |
String | The name of the item. |
roomId |
String | The unique identifier for the room. |
Get a reference to an item in the player's inventory. It takes one argument:
Argument | Type | Description |
---|---|---|
name |
String | The name of the item. |
Every command a player can issue in the game has a corresponding function in text-engine.
For instance, there's a function called "go" that gets called when the player types GO.
You can add your own custom commands as well. Take a look at the "unlock" command in game-disks/demo-disk.js for an example.
If existing commands don't work how you want them to, you can override them by reassigning them to your own function code.
For instance, you may wish to implement your own versions of the "save" and "load" commands. Or you may not wish to include save
or load
at all.
Commands are stored on a global array called commands
. Each element in the array is a JavaScript object with methods attached. The index of the element indicates how many arguments it accepts. So, for instance, all methods attached to commands[0]
take zero arguments.
Methods are named according to what the player types to issue them. For instance, the player can type "go" with no arguments to see available exits in the room. This command is found at commands[0].go
.
Here are a few examples of ways to override the default commands:
// Add a command which takes no arguments.
// In this example, the command is called "play", and the user would type "play" to use the command.
const play = () => println(`You’re already playing a game!`);
commands[0] = Object.assign(commands[0], {play});
// Override a command's function.
// In this example, we're overriding the "save" command.
save = () => println(`Sorry, saving is not supported in this game.`);
// Remove an existing command.
// In this example, we're removing the "save" command.
delete commands[0].save;
// Completely replace existing commands.
// In this example, the only two commands available in the entire game will be "walk" and "talk".
commands = [{walk: () => println(‘you walk’), talk: () => println(‘you talk’)}];
If you do remove some or all of the default commands, you'll want to override the help
function as well so that it doesn't list commands which are not supported by your game.
There are several other functions available in the engine! Feel free to take a peek at the source code. It's designed to be open and simple to use and to customize.
The default implementation of saving and loading games in text-engine is quite simple. All the commands a player has entered are stored in the save to be "played back" into the game in the same order on load. It's something like the game playing itself back to the point where you left off, instantaneously.
This simplicity comes at a cost. Asynchronous (e.g. time-delayed) or non-deterministic (e.g. RNG) code can cause issues.
As an example, consider the coin toss in the demo disk. If you pick up the dime and use it, it will land on either heads or tails (at random). Say you flip the dime and it lands on heads, then you save the game. When you come back to load the game later, it may appear that the dime landed on tails instead. Because the command use dime
is all that was stored in the save file, the dime is re-flipped each time you load the game.
If you choose to use asynchronous or non-deterministic code in your game and you're unhappy with the result when loading saves, remember that these commands can be removed from your game, or you can write your own save/load functions. (See Overriding the default command set above.)
Although text-engine's disks are now (as of version 3.0) expected to be functions which return a JavaScript object, prior versions expected disks to just be objects. The reason for this change was to support the ability to save and load games with the command-replay save system without forcing the user to reload the browser every time they loaded a save. (Returning an object from a function gives the engine a convenient way to reset the game state before reapplying the player's input history.)
That said, the older format is still supported. If your disk is a JavaScript object, the player will be instructed to reload the browser when they load. You can avoid this by wrapping your disk in a function. For example, if your disk looks like this:
const myDisk = {
roomId: 'myFirstRoom',
rooms: [...],
};
You can 'upgrade' it to the new format by changing it to this:
const myDisk = () => ({
roomId: 'myFirstRoom',
rooms: [...],
});
By the way, if you preferred the old save system (which serialized the game state), you can always grab its code here.
- REXPaint - Makes creating ASCII art super easy.
- ASCII-Code.com - Convenient for copying and pasting ASCII characters.
- Text to ASCII Art Generator - Make ASCII logos from text.
- Engine inspired in part by TextAdventure.js.
- Unlimited Adventure demo inspired by Forgotten by Sophia Park, Arielle Grimes, and Emilie Sovis and also this screenshot, whatever it is.
- "Ultimate Apple II Font" from KreativeKorp.
- Some ASCII art adapted from ASCII Art Archive.
- Special thanks to Caleb Creed for helping me flesh out the features for text-engine 2.0 and for designing and writing the auto-complete functionality.
- 3.0.0: Switched to command-replay save system; added support for saving to/loading from files; added
getItem
function. - 2.0.0: Added characters, conversations, auto-complete,
items
command,save
&load
commands, navigation shortcuts, global methods for utility or overriding, support for custom commands,onLook
&onTalk
callbacks, upgradedgo
command, support forblocks
on exits, support for providing class name toprintln
function, support for randomizing printed lines, bold/italic/underline text, various bug fixes & improvements. - 1.3.0: Rooms can define
onEnter
methods. - 1.2.0: New orange default theme.
- 1.1.1: Now supports use in other operating environments besides the DOM. See text-engine-node for example usage. (Planning to add documentation later.)