In this homework you will be creating the core functionality of an adventure game. The goals of this assignment are:
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:
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.
Thing and Room extend Container,
Item and Character extend Thing, etc.
Container
_____________________|____________________
| |
Thing Room
_______|_________
| |
Item Character
__________|______ ___|___
| | | | | |
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.
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.
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
Coin.java, Vending.java, Beer.java, Potion.java)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).
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.
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.
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).
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.
Beer and Potion classes, try completing Question 3 first and then coming back.
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
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.
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.
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.
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
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
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