CSE 120 Spring 2008

Homework 2: Adventure Game

In this homework you will be creating the core functionality of an adventure game. The goals of this assignment are:

Announcements

A new version of Container.java is available. Please download this file to ensure proper functionality.

Overview

The ancestor of all adventure games is Bill Crowther's Adventure, developed originally in Fortran back in 1975.

The setting for this adventure game is a competition between two kingdoms, Wealthierland and Richerland. Both kingdoms value money over anything else, so they compete to accumulate as much money as possible. Recently, a mysterious cave was discovered that is suspected to hide untold treasures. Wealthierland and Richerland quickly sent their warriors to search the cave, which consists of a maze of rooms. Some rooms have one or more guards protecting the passages to adjacent rooms. A game player can assume a role as either a warrior from Wealthierland or from Richerland. While searching the maze, the player can interact with other characters in the game or with certain objects in the rooms that have various effects in the game.

We supply several classes that define useful game abstractions. You will implement the game by creating the classes that define the actual game. These fall into three main categories:

  1. Maze: classes to model rooms and mazes.
  2. Game Items: classes to model game items (coins, potions, ...).
  3. Game Characters: classes that represent characteristics and possible actions of a character in the game (warriors and guards).

Supplied Files

Do not change any of the above classes, except for Character.java. If you do, your classes may not work correctly with our tester.

We provide Javadoc pages for all of the classes needed in the game, both those supplied by us and those you will write, so that you can implement the desired game behaviors.
However, to achieve all of the game requirements or to help simplify some game functionality, you should feel free to implement other methods in addition to the ones found in the Javadocs.

Suggestions

Question 1 (10 Points): Maze (Files to submit: Room.java)

A Maze, which is supplied, is made of Rooms that are linked together. To keep things interesting and not have just one maze layout for the game, we use a layout of the Maze we want to construct and then create the Rooms associated with it. The Rooms must also be linked together properly.

We give the Maze layout in the form of an integer matrix of zeroes and ones. A one means a Room should be in the given location, and a zero means no Room should be created. Here is a sample 6x6 matrix:

0 1 1 1 1 0
0 0 0 1 0 0
0 0 0 1 1 0
0 0 1 1 1 0
0 0 1 1 0 0
0 0 1 0 0 0

Mazes can be of any width and height. Once a Maze is parsed, no new Rooms will be created or removed.

Rooms in Maze

The main features of a Room is that it is a container for game items and game characters (Room extends Container.). It can have exits in the four cardinal directions to other rooms. There is at most one exit in each direction. The javadocs give full details on the required Room methods. The linkRooms method is worth of special mention (see image at right): it creates a bidirectional connection in a given direction by setting the exit in that direction from one room to the other and the exit in the opposite direction from the other room back to the first room. The toString method, which is used when a Room object is printed, should return a string with some useful information about the room, for instance the room name and the room contents.

The name of each Room should be made from the row and column index for that room in the maze, although the exact format of the name is not important and is left up to you. The same coordinate system is also used to specify the starting (entrance) room for the maze. All adjacent rooms should have exits to each other (in cardinal directions North, South, East and West only, we are not using Northeast, Southwest, etc.). So in the example, the Room at (2, 3) (remember, we are using a 0-based indexing system) would have an exit to the EAST, an exit to the SOUTH and an exit to the NORTH. The Room at (3, 3) would have exits in all four directions.

Sample interactions with Maze, Room, and MazeGenerator (part1.hist).

> Maze m = MazeGenerator.makeSimpleMaze();
> Room r = m.getStartingRoom();
> r
room (0,1) containing nothing
> r.hasExitInDirection(Direction.WEST)
false
> r.hasExitInDirection(Direction.SOUTH)
true
> r.getExitRoom(Direction.WEST)
null
> r.getExitRoom(Direction.SOUTH)
room (1,1) containing nothing
> Room s = r.getExitRoom(Direction.SOUTH);
> s.getExitRoom(Direction.NORTH)
room (0,1) containing nothing

Question 2 (40 points): Game Items (Files to submit: Coin.java, Vending.java, Beer.java, Potion.java)

Skeleton Code:

The supplied abstract class Item specifies the main functionality of game items. Item extends another supplied abstract class, Thing, which contains the functionality that every object in the game, whether it is an item or a person, should have in common.

Each subclass of Item has a separate ThingType, returned by the method getThingType.

The canPickUp method specifies whether an Item can be picked up from a room by a game character.

The useItem method implement the action of using an Item on its own. Some items can also be used on another item through a useItem method that takes arguments. The method returns an Outcome object. This object contains a boolean representing the success or failure of the use, and a String describing what happened. The exact form of the string is not essential, but it should be informative (It could also be funny if you choose to exercise your creativity).

Coins

A Coin object can be picked up. It has no use by itself, but it can be used on a vending machine to buy an item. When a Coin is used on a vending machine successfully, it is gone from the game.

Vending machines

A Vending object sells two kinds of items: Potion and Beer. When a Coin is used on a vending machine, the specified drink item is added to the contents of the vending machine's room and the Coin is taken out of the game.

A Vending object is created with a given stock, and each time an item is dispensed, the corresponding item count decreases. Once it reaches zero, no more items of that type will be dispensed. The buy method should implement this vending action, and also remove the Coin from its container so that it leaves the game. Calling useItem on a Vending object with no arguments always fails: you always need a coin to use one.

Potions

A Potion object represents a bottle of magic potion, which increases the strength level of the person who drinks it. Each Potion can have a different power, specified when the object is created. Its power determines how much a person's strength level will go up by after drinking it. If power is not specified, the Potion has a default power of 1. A Potion can be picked up. It can be used on its own; this just means it is drunk. Once a Potion is used, it is discarded (removed from its container).

Beer

A Beer object represents a keg of beer. Similar to a Potion, each Beer object can have a different alcohol strength, specified when it is created. If alcohol strength is not given, it has a default alcohol strength of 1. Beer has different effects on the person drinking it depending on what game character that person is. In particular, if a warrior drinks beer, his strength level will be reduced by the beer's alcohol strength. This is obvious because the warrior will get drunk and thus not be able to function or fight properly. However, guards were born to be different! They are so strong that drinking beer will not affect their strength level at all. In fact, they really enjoy drinking and they always have a craving for beer. If they are given a keg of beer, they will be overly happy and will let anybody pass to the passage they are guarding.

Just like a Potion, Beer can be picked up and it can be used (drunk) on its own. When it is used, it is removed from the game.

Sanity Note

If you are having trouble understanding the functionality of the Beer and Potion classes, try completing Question 3 first and then coming back.

Sample interactions with Items (part2.hist).

> Maze m = MazeGenerator.makeSimpleMaze();
> Room r = m.getStartingRoom();
> Coin c = new Coin();
> Vending v = new Vending(1, 2);
> r.addThing(c);
> r.addThing(v);
> c
coin
> v
vending containing one potion, 2 beers
> r
room (0,1) containing one coin, one vending
> c.useItem()
Failure: What do you want to do with the coin?
> v.useItem()
Failure: What do you want to do with the vending machine?
> c.useItem(v, ThingType.POTION, 2)
Success: A potion dropped on the room floor
> Item potion = r.findThing(ThingType.POTION, 0)
> potion.useItem()
Success: A potion of power 2 has been drunk.
> r
room (0,1) containing one vending
> Coin c2 = new Coin();
> r.addThing(c2);
> c2.useItem(v, ThingType.BEER, 3)
Success: A beer dropped on the room floor
> c2.getContainer()
null
> Item beer = r.findThing(ThingType.BEER, 0)
> beer.useItem()
Success: A keg of beer with alcohol strength of 3 has been drunk.
> beer.getContainer()
null 

Question 3 (50 points): Game Characters (File to submit: Warrior.java, Guard.java, Character.java)

The supplied abstract class, Character, represents a person in the game. There are only two types of Character, Warrior and Guard.

A Character interacts with the rest of the game objects. In essence, a Warrior can do two things: move around and interact with Things. A Guard, however, is assigned to guard an exit in a room all the time, so he is limited to only certian actions. Guards cannot move around, but they can still interact with some Things in the game.

The state of a Character consists of the current location, which is a Room, and a strength level, which should always be greater than 0 for all active Characters. As soon as a Character's strength level is reduced to 0 or negative, the Character is dead and should be removed from the game completely.

As you are writing the Warrior and Guard classes, if you find yourself writing duplicate code, consider adding it to the abstract class Character.

Warriors

A Warrior is created with a name, the kingdom he is from, a starting room, and a strength level. In this game setting, a Warrior can only come from one of the two kingdoms, Wealthierland or Richerland. A Warrior can move in a given direction. To change location to another room, there must be an exit in that direction, and no active (not drunk) Guards in that same direction, in the current room. It is possible to have multiple instances of Guard guarding the same exit in the same room. The method activeGuards counts the number of guards who are still guarding the exit in a given direction. As mentioned earlier, a Guard can be bought off with a keg of Beer. Beer can be bought using the buy method given that the warrior has a coin and the room has a vending machine with some beer for sale. Once the warrior has beer, the makeDrunk method should do the work of allowing a warrior to pass the guard.

By making Warrior a subclass of Container, the action of Item collection is easily achieved. While navigating around the maze, a Warrior can pick items in the current room up if the items can be picked. All picked items are stored in the inventory of the warrior's kingdom, not in the warrior himself. In other words, all warriors of the same kingdom share an inventory. Each of them can use or drop any items in the shared inventory at anytime. (Do the words "same" and "shared" remind you of any Java concept learned in class?) To use an item in the inventory, a warrior uses the useCarriedItem method by specifying the type of the item and the ordinal position of the item among items in that type in the inventory. A Warrior can also use any item found in the current room through the useRoomItem method in the same way. Both methods invoke the item's useItem method. If a warrior uses a Potion or a Beer, he drinks it and must receive the drink's effect.

It is important to note that drinking beer can be lethal! (This is the case where the beer's alcohol strength is greater than or equal to the warrior's strength level) Hence, it is a war strategy to trying to make the warriors of the other kingdom drink to death to reduce the number of warriors competing over the coins. If a kingdom loses all of its warriors, it literally loses to the other kingdom. As a result, all items in its inventory must be dropped in the current room where its last warrior just died.

Guards

A Guard is created with a name, a room assignment, the direction of the exit to guard, and a strength level. As a guard, he cannot carry any items or move around the maze. The only action a Guard can do, is to use an item in the room he is in through the method useRoomItem. Essentially, the only meaningful action he can do is to use (drink) a Potion or a Beer if found in the room.

When not drunk, guards never let anybody pass to their exits. But once drunk, they let anybody pass through.

Sample interactions with Characters (part3.hist).

> Maze m = MazeGenerator2.makeComplexMaze();
> Room r = m.getStartingRoom();
> r
room (3,0) containing nothing
> Warrior w1 = new Warrior("Harry", "Wealthierland", r, 5);
> Warrior w2 = new Warrior("Ron", "Richerland", m.getRoomAt(3, 1), 4);
> Guard g1 = new Guard("Hagrid", m.getRoomAt(3, 0), Direction.EAST, 10);
> r
room (3,0) containing one warrior, one guard
> w1.move(Direction.EAST)
Failure: one guard guarding exit to the east
> Vending v = new Vending(5, 5);
> Coin c = new Coin();
> r.addThing(v);
> r.addThing(c);
> r.addThing(new Coin());
> r
room (3,0) containing 2 coins, one vending, one warrior, one guard
> w1.pick(ThingType.COIN, 1)
Success: Picked a coin
> w1.pick(ThingType.COIN, 0)
Success: Picked a coin
> w1.buy(ThingType.BEER, 3)
Success: A beer dropped on the room floor
> v
vending containing 5 potions, 4 beers
> w1.pick(ThingType.BEER, 0);
> w1.makeDrunk(g1, 0)
Success: A keg of beer with alcohol strength of 3 has been drunk.
Guard Hagrid is now drunk!
> w1.move(Direction.EAST)
Success: Moved to room room (3,1) containing one coin, 2 warriors
> w2.pick(ThingType.COIN, 0)
Success: Picked a coin
> w2.move(Direction.WEST)
Success: Moved to room room (3,0) containing one vending, one warrior, one guard
> w2.buy(ThingType.POTION, 2);
> w2.pick(ThingType.POTION, 0);
> w2.useCarriedItem(ThingType.POTION, 0)
Success: A potion of power 2 has been drunk.
Warrior Ron's strength level is increased to 6!
> r.addThing(new Coin());
> w2.pick(ThingType.COIN, 0);
> w2.buy(ThingType.BEER, 6);
> w2.pick(ThingType.BEER, 0);
> w2.makeDrunk(w1, 0)
Failure: Harry is in another room, (3,1)
> w2.move(Direction.EAST);
> m.getRoomAt(3,1)
room (3,1) containing 2 warriors
> w1
Warrior Harry of Wealthierland, strength: 5, carrying shared stash of one coin
> w2.makeDrunk(w1, 0)
Success: Success: A keg of beer with alcohol strength of 6 has been drunk.
Warrior Harry's strength level is reduced to -1!
> m.getRoomAt(3,1)
room (3,1) containing one coin, one warrior

Extra credit : Fight! (Files to submit: Warrior.java and Guard.java with additional functionality)

So far, the only action that can have a negative effect on a character's strength level is drinking beer. To make the game more interesting and more realistic, we would like to implement a new method, fight, which allows a Character to challenge and fight with another. This method is to be added to Warrior only since Guards are not allowed to initiate a fight (although they can be involved in a fight if challenged).

Rules regarding fights:

The fight method returns an Outcome as follow:

 - Success: if the warrior who started the fight wins
 - Failure: if the fight does not take place, if the fight turns out to be a tie, or if the warrior starting the fight loses 

Sample interactions with fight (extracredit.hist).

> Maze m = MazeGenerator2.makeComplexMaze();
> Warrior w1 = new Warrior("Harry", "Wealthierland", m.getRoomAt(2,1), 10);
> Warrior w2 = new Warrior("Ron", "Richerland", m.getRoomAt(3, 1), 9);
> Guard g1 = new Guard("Hagrid", m.getRoomAt(3, 1), Direction.EAST, 9);
> w1.fight(w2)
Failure: Ron is in another room, (3,1)
> w1.move(Direction.SOUTH);
> w1.fight(w2)
Success: Warrior Harry of Wealthierland won over Warrior Ron of Richerland and Warrior Harry's current strength level is 10
> w2
Warrior Ron of Richerland, strength: 8, carrying shared stash of nothing
> w1.move(Direction.EAST)
Failure: one guard guarding exit to the east
> w1.fight(g1)
Success: Warrior Harry won over Guard Hagrid and Warrior Harry's current strength level is 10
> w1.move(Direction.EAST)
Success: Moved to room room (3,2) containing one coin, one warrior
> w2.fight(g1)
Failure: This fight is a tie.
> w2.move(Direction.EAST)
Failure: one guard guarding exit to the east
> w1.move(Direction.WEST);
> w1.move(Direction.EAST)
Success: Moved to room room (3,2) containing one coin, one warrior

Congratulations! You now have your own Adventure Game to play :)