Java Arena

A text adventure

In this project you build a text adventure: a game that describes where you are, reads what you want to do (go north, take key), and answers in text. The world isn't written into the program. It comes from a file, world.txt, so the same program can play any world that file describes.

When it's finished, this is a whole world: an old lighthouse with five rooms, two things to find, a locked door and a goal.

room;Shore;Cold waves break on gray pebbles.
room;Cottage;A keeper's cottage. The stove is cold.
room;Boathouse;An old rowboat lies upside down. It smells of tar.
room;Tower;Stone stairs climb into the dark.
room;Lamp Room;A great glass lens waits for a flame.
exit;Shore;north;Cottage
exit;Shore;east;Boathouse
exit;Cottage;south;Shore
exit;Cottage;east;Tower
exit;Boathouse;west;Shore
exit;Tower;west;Cottage
exit;Tower;up;Lamp Room
exit;Lamp Room;down;Tower
item;oil;Cottage
item;key;Boathouse
lock;Cottage;east;key
goal;Lamp Room;oil;You fill the lamp with oil and light it. Its beam sweeps across the sea.

A player who types these commands, one per line:

go north
take oil
go east
go south
go east
take key
go west
go north
use key
go east
go up

sees this:

[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south, east
You see: oil
You take the oil.
The way east is locked.
[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Boathouse]
An old rowboat lies upside down. It smells of tar.
Exits: west
You see: key
You take the key.
[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south, east
You unlock the way east with the key.
[Tower]
Stone stairs climb into the dark.
Exits: west, up
[Lamp Room]
A great glass lens waits for a flame.
Exits: down
You fill the lamp with oil and light it. Its beam sweeps across the sea.
You won in 7 moves.

What you need

Modules 16 to 24, especially: string methods such as split, startsWith and substring (module 16), classes with constructors, getters and toString (modules 17 and 18), lists of objects (module 19), reading a file with a Scanner, try and catch (module 20), objects as method parameters and as instance variables, like the player's Room and addExit(Exit exit) (module 23), and a list as an instance variable created in the constructor, with methods that add to it, go through it, empty it and remove from it (module 24). The last milestone also writes a file, with a class called PrintWriter; its task shows you how.

How you build it

The program has a class for each kind of thing in the game: Room, Exit and Player, with Main reading the world and the commands. Each line of world.txt starts with its kind (room, exit, item, lock, goal), and each milestone teaches the program one more kind, together with the commands that go with it:

  1. Rooms and commands: rooms from the file, and a loop that reads commands.
  2. Exits and moves: go north takes you to the next room.
  3. Things to carry: take, drop and inventory.
  4. A locked door: an exit that opens with the right item, and use.
  5. A goal: the game ends when you reach it, and counts your moves.
  6. Saving and loading: save writes the game to a file, and load reads it back.

Lines of a kind the program doesn't know yet are skipped, so a world file written for a later milestone still works with an earlier program.

Milestones

  1. Rooms and commands
  2. Exits and moves
  3. Things to carry
  4. A locked door
  5. A goal
  6. Saving and loading

Milestone 1: Rooms and commands

The starting code reads world.txt line by line and splits each line at ;. A line such as room;Shore;Cold waves break on gray pebbles. is a room: its name, then its description. Player.java is ready: a player is always in one room.

  1. Write the class Room, with a constructor Room(String name, String description), a getter getName(), and a toString() that returns the name in square brackets, a line break (\n), and the description:

    [Shore]
    Cold waves break on gray pebbles.
  2. In main, create a Room from each room line and add it to rooms.

  3. Create a Player in the first room of the list and print that room.

  4. Then read commands, one per line, until the player types quit:

    • look prints the room again.

    • quit prints Goodbye. and ends the game.

    • Anything else prints I don't understand "dance"., with the command in the quotes.

      Inside quotes, \" is a quote character: "say \"hi\"" prints say "hi".

With this world.txt

room;Shore;Cold waves break on gray pebbles.
room;Cottage;A keeper's cottage. The stove is cold.

and the commands look, dance and quit, the program prints:

[Shore]
Cold waves break on gray pebbles.
[Shore]
Cold waves break on gray pebbles.
I don't understand "dance".
Goodbye.

Milestone 2: Exits and moves

Now the rooms get exits. A line such as exit;Shore;north;Cottage means: from the Shore, the way north leads to the Cottage. A world file lists its room lines first, so every room an exit line names already exists when the line is read. A new file, Exit.java, has been added to your program.

  1. Write the class Exit, with a constructor Exit(String direction, String target) and the getters getDirection() and getTarget(). The target is the name of the room the exit leads to.
  2. Give Room a list of its exits, an ArrayList<Exit> created in the constructor, and two methods:
    • addExit(Exit exit) adds an exit to the list.
    • getTarget(String direction) returns the name of the room the exit in that direction leads to, or "" when the room has no exit that way.
  3. When a room has exits, its toString() ends with one more line that lists their directions in the order they were added: Exits: north, east. A room without exits prints as before.
  4. In Main, write a method findRoom(ArrayList<Room> rooms, String name) that returns the room of that name, and use it to add an Exit to the right room for each exit line.
  5. A new command, go and a direction such as go north, moves the player through that exit and prints the new room. When there's no exit that way, it prints You can't go west from here. (with the direction the player typed). Only a command that starts with go (with the space) is a move; a bare go is a command the game doesn't understand.

With this world.txt

room;Shore;Cold waves break on gray pebbles.
room;Cottage;A keeper's cottage. The stove is cold.
room;Boathouse;An old rowboat lies upside down. It smells of tar.
exit;Shore;north;Cottage
exit;Shore;east;Boathouse
exit;Cottage;south;Shore
exit;Boathouse;west;Shore

and the commands go north, go west, go south and quit, the program prints:

[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south
You can't go west from here.
[Shore]
Cold waves break on gray pebbles.
Exits: north, east
Goodbye.

Milestone 3: Things to carry

Time for things to pick up. A line such as item;oil;Cottage puts the item oil in the room called Cottage. An item is just its name, a String.

  1. Give Room a list of the items in it, an ArrayList<String>, with the methods addItem(String item), hasItem(String item) (true when the item is in the room) and removeItem(String item).
  2. When a room has items, its toString() ends with one more line, after the exits: You see: oil, rope, in the order they were added. A room without items prints as before.
  3. Give Player a list of the items they carry, with the methods take(String item) (adds it), has(String item), drop(String item) (removes it) and inventory(), which returns You carry: shell, oil in the order they were taken, or You carry nothing. when the list is empty.
  4. In Main, put each item line's item into its room, and add three commands:
    • take oil moves the oil from the room to the player and prints You take the oil. When the room has no oil, it prints There is no oil here.
    • drop oil moves it from the player to the end of the room's items and prints You drop the oil. When the player doesn't carry it, it prints You aren't carrying the oil.
    • inventory prints what inventory() returns.

With this world.txt

room;Shore;Cold waves break on gray pebbles.
room;Cottage;A keeper's cottage. The stove is cold.
room;Boathouse;An old rowboat lies upside down. It smells of tar.
exit;Shore;north;Cottage
exit;Shore;east;Boathouse
exit;Cottage;south;Shore
exit;Boathouse;west;Shore
item;shell;Shore
item;oil;Cottage
item;rope;Cottage

and the commands inventory, take shell, take shell, go north, take oil, inventory, drop shell, look and quit, the program prints:

[Shore]
Cold waves break on gray pebbles.
Exits: north, east
You see: shell
You carry nothing.
You take the shell.
There is no shell here.
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south
You see: oil, rope
You take the oil.
You carry: shell, oil
You drop the shell.
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south
You see: rope, shell
Goodbye.

Milestone 4: A locked door

Some ways are locked. A line such as lock;Cottage;east;key locks the exit east from the Cottage, and the item key opens it. A lock line comes after the exit line it locks.

  1. Give Exit a third instance variable, key: the item that opens it, or "" when the exit isn't locked. The constructor stays as it is and sets key to "". Add the methods getKey(), setKey(String key) and isLocked(), which is true when the key isn't "".
  2. Give Room three methods:
    • lock(String direction, String key) sets the key of the exit in that direction.
    • isLocked(String direction) is true when the exit in that direction is locked.
    • unlock(String item) looks for a locked exit whose key is that item. It opens the first one it finds (its key becomes "") and returns its direction, or returns "" when the item opens nothing here.
  3. In Main, lock the exit of each lock line. go through a locked exit prints The way east is locked. and the player stays.
  4. A new command, use key:
    • When the player doesn't carry the key, it prints You aren't carrying the key.
    • When the key opens a locked exit of the room, it prints You unlock the way east with the key. The player keeps the key.
    • Otherwise it prints Nothing happens.

With the lighthouse world of the project's introduction, without its goal line, and the commands go north, go east, use key, go south, go east, take key, use key, go west, go north, use key, go east and quit, the program prints:

[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south, east
You see: oil
The way east is locked.
You aren't carrying the key.
[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Boathouse]
An old rowboat lies upside down. It smells of tar.
Exits: west
You see: key
You take the key.
Nothing happens.
[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south, east
You see: oil
You unlock the way east with the key.
[Tower]
Stone stairs climb into the dark.
Exits: west, up
Goodbye.

Milestone 5: A goal

A game needs a way to win. A line such as goal;Lamp Room;oil;You fill the lamp with oil and light it. Its beam sweeps across the sea. says: the player wins when they're in the Lamp Room and carry the oil. The last part is the text the game prints then. A world has at most one goal line, and a world without one can't be won.

  1. Give Player an instance variable moves that starts at 0, and a getter getMoves(). moveTo adds 1 to it, so it counts the moves the player has made. A go that fails (no exit, or a locked one) isn't a move.
  2. In Main, keep the goal's room, item and text in three String variables made before the try (empty strings at first), and set them from the goal line.
  3. After every command except quit, check whether the player is in the goal's room and carries its item. If so, print the goal's text and then You won in 7 moves. (with the player's count), and end the game.

With the whole lighthouse world of the project's introduction, goal line and all, and the commands of the sample run there, the program prints:

[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south, east
You see: oil
You take the oil.
The way east is locked.
[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Boathouse]
An old rowboat lies upside down. It smells of tar.
Exits: west
You see: key
You take the key.
[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south, east
You unlock the way east with the key.
[Tower]
Stone stairs climb into the dark.
Exits: west, up
[Lamp Room]
A great glass lens waits for a flame.
Exits: down
You fill the lamp with oil and light it. Its beam sweeps across the sea.
You won in 7 moves.

Milestone 6: Saving and loading

Last, the player can stop and come back later. save writes the state of the game to a file, save.txt, and load reads it back. The file uses the same style of lines as world.txt:

at;Boathouse
moves;3
carry;oil
carry;key
lock;Cottage;east;key
  • at;Boathouse: the room the player is in. Always the first line.
  • moves;3: the player's count of moves. Always the second line.
  • carry;oil: one line for each item the player carries, in their order.
  • Then, for each room in the order of the world file: an item;... line for each item in it, as in world.txt (item;rope;Shore), and a lock;... line for each exit of it that is still locked (as above).

Writing a file

Module 43 covers writing files in depth; here's the part you need. The class PrintWriter (import java.io.PrintWriter;) writes text into a file, with the same println you know from System.out. Open it in a try (...) as you open a Scanner for a file: the try also closes it at the end of the block, and only then is all the text in the file. Creating it makes the file, or empties it when it's already there.

try (PrintWriter writer = new PrintWriter("save.txt")) {
    writer.println("at;" + player.getRoom().getName());
} catch (Exception e) {
    System.out.println("Error: " + e.getMessage());
}

What to do

  1. Give Player the methods getItems() (returns the list), clearItems() and setMoves(int moves). Give Room the methods getItems(), getExits(), clearItems() and openAll() (every exit's key becomes "").
  2. The command save writes save.txt as described above and prints Game saved.
  3. The command load reads save.txt. It first empties the player's and every room's items and opens every exit, then goes through the lines: at moves the player there, moves sets the count with setMoves (the file's at line comes first, and moving the player there with moveTo adds 1, which this line then overwrites), carry gives the player the item, and item and lock lines work as in world.txt. Then it prints Game loaded. and the room the player is in. When there's no save.txt, it prints There is no saved game. and the game goes on as it was.

With the whole lighthouse world and the commands go north, take oil, go south, go east, take key, save and quit, the program writes the file above and prints:

[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Cottage]
A keeper's cottage. The stove is cold.
Exits: south, east
You see: oil
You take the oil.
[Shore]
Cold waves break on gray pebbles.
Exits: north, east
[Boathouse]
An old rowboat lies upside down. It smells of tar.
Exits: west
You see: key
You take the key.
Game saved.
Goodbye.