Wednesday July 26, 2006
How The Game Is Played
Game Progrmaming 100: A text adventure, part 1
On JavaGaming.org recently the issue came up of how to write a text
adventure.
A text adventure is a nice first game coding project that is simple,
small and well contained. I thought thus it might make a nice
blog topic. So here is part one of JADVENTURE: The
Java Adventure Game Engine.
Part 1: The Fundementals
Fundementally, a single player text adventure is just a Finite State Automaton (FSA). A good complete definition of a FiniteStateAutomaton can be found on wikipedia here. For our purposes though a simpler definition will suffice.A Finite State Automata (FSA), also called a "state machine" is a machine that exists in one and only one state at a time. It takes as input an event. An event causes a transition to a new state. Part of that transition may be to generate output.
FSA transitions can be described by a 2D array where the X coordinate is the state, the Y coordinate is the event and contents of the cell is the transition which would be a new X coordinate to change to plus output or other action information. This array is often called a "transition table".
Lets imagine for a moment that you have a world of two rooms. NorthRoom and SouthRoom and 2 events-- "Go North" and "Go South".
Your state transition table might look like this:
| North Room | South Room | |
| Go North | do nothing | set state to North Room |
| Go South | set state to South Room | do nothing |
Now this by itself is not enough to be the adventure game ebcause there is no room description yet. No output.
Typically, an assumed part of every transition to a new state in such a game is "print descritpion of new state." You can keep that descritpion in another table like this:
| Room | Description |
| North Room | A big spacious cavern with a door to the south |
| South Room | A tiny little hole with a door to the north. |
Thats all you need to walk between two rooms. But in practice using tags like "North Room" and "Go North" in your data structures is difficult in most language. In most languages it is more natural to assign ordinal values to such things. So in the above tables "North Room" is room 0 and "South Room" room 1. As all that is trypically part of the data for the game the conversion to ordinals can be done at game-build time and all you need at runtime are the actual ordinal numbers in their respective tables like this:
| 0 | 1 | |
| Go North | do nothing | set state to 0 |
| Go South | set state to 1 | do nothing |
| Room Number | Description |
| 0 | A big spacious cavern with a door to the south |
| 0 | A tiny little hole with a door to the north. |
That gets us most of the way but we also want to convert "go north" to event 0 and "go south" to event 1 so our final table looks like this:
| 0 | 1 | |
| 0 | do nothing | set state to 0 |
| 1 | set state to 1 | do nothing |
The events however are going to be text input from the user. How will we identify the event numbers. The answer is that we use a parser that returns an event number. Modern text adventure parsers can be quite complex. For a first cut though we will just use a parser that recognizes only "go north" and "go south". That will get us up and running and then next blog I will discuss a more capable parser.
The code for everything we've done so far is below.
/**
*
* <p>Title: SimpleTextAdventure.java</p>
* @author Jeff Kesselman
* @version 1.0
*/
package com.worldwizards;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
/**
*
* <p>Title: SimpleTextAdventure.java</p>
* <p>Description: </p>
* <p>Copyright: Copyright (c) 2004 Sun Microsystems, Inc.</p>
* <p>Company: Sun Microsystems, Inc</p>
* @author Jeff Kesselman
* @version 1.0
*/
public class SimpleTextAdventure {
static final String[] commands = {
"go north","go south"
};
static final String[] descriptions = {
"a big spacious cavern with a door to the south.",
"a tiny little hole with a door to the north."
};
static final int INVALID_EVENT = Integer.MIN_VALUE;
static final int[][] transitionTable = {
{INVALID_EVENT,1},
{0,INVALID_EVENT}
};
private int currentState=0;
/**
* @param args
*/
public SimpleTextAdventure() {
printStateDescription();
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
while(true){
try {
String input = consoleReader.readLine();
int event = parseInput(input);
if (event>=0) { // recognized
if (doTransition(event)){ // chnaged state
printStateDescription();
} else {
System.out.println("I can't do that right now.");
}
} else {
System.out.println("I don't know how to do that.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @param event
* @return
*/
private boolean doTransition(int event) {
int newstate = transitionTable[currentState][event];
if (newstate >= 0){
currentState = newstate;
return true;
}
return false;
}
/**
* @param input
* @return
*/
private int parseInput(String input) {
for(int i=0;i<commands.length;i++){
if (input.equalsIgnoreCase(commands[i])){
return i;
}
}
return -1;
}
/**
*
*/
private void printStateDescription() {
System.out.println("You are in "+descriptions[currentState]);
}
public static void main(String[] args) {
new SimpleTextAdventure();
}
}
Posted at 07:27PM Jul 26, 2006 by gameguy in General |
Comments: