In this assignment, you will be extending your previous Ultima 0 assignment. You will be implementing monsters using Java threads.

Getting started. You can should use your previous Ultima 0 assignment as starting point for this assignment. Start by downloading ultima0.1.zip. I added transparency to the monster tiles. I also changed the provided Ultima.java main program. The game text file format has also changed, so I've included a new set of text files. In your improved game, there are monsters you must go around and kill. You attack monsters by running into them. Monsters attack you in the same way. You can also now walk on lava if you want, but if you do it costs you one hit point.

Avatar. The Avatar class has gotten somewhat more advanced. The Avatar now has a number of hit points (life). Once the Avatar's hit points reach 0, the game is over an you lose. The Avatar has a damage amount, this is how many hit points of damage he causes when he attacks a monster. You will need to update your constructor to handle these new attributes.

If the Avatar incurs damage (from a monster or from walking on lava), his new hit point value after the damage is display in yellow text over the Avatar image. The hit point display stay visible for 3 seoncds and then disappears. This will require you to modify your old draw() method. Your Avatar data type must implement the following additional API methods:

public class Avatar
----------------------------------------------------------------------------------------
Avatar(int x, int y, int hp, int damage, double torch)
int getHitPoints() // Get the number of hit points left
void incurDamage(int damage) // Reduce the Avatar's hit points by the amount damage
int getDamage() // Get the amount of damage the Avatar causes monsters
void passTime(int timeMs) // Inform the Avatar that some time has passed

The Avatar's line in the game text file now has 5 numbers, for example:

11 2 20 3 100.0

The above means the Avatar starts at (11,2), has 20 hit points, causes 3 damage, and has an initial torch radius of 100.

Tile. A Tile class has only minor changes. The lava tile should be made passable (change your isPassable() method from last time. Tile objects can now report the damage they cause if they are walked on. All tiles have 0 damage except for lava which causes 1 point of damage.

Here is the new API method:

public class Tile
-----------------------------------------------------------------------------------------
int getDamage() // Return the damage caused by this type of Tile

Monster. The Monster class represents a monster that roams around the World randomly. A monster knows things like its location, its remaining hit points, the amount of damage it causes when it attacks, and the type of monster it is. Monster objects also keep a reference to the World object so they can call methods in World from their run() method (namely the monsterMove() method). Just like the Avatar, if damaged a monster displays its remaining hit points in red text over the monster image for 3 seconds. If a monster's hit points are 0 or less, the monster is longer drawn.

Each monster is its own Java thread that every so many milliseconds attempts to move itself around the World. They are not smart, they just choose north, south, east or west at random. If they can move in that direction they do, otherwise their turn is wasted. If the monster randomly moves into your Avatar, you will lose hit points according to the damage attribute of the monster. Just like the Avatar, monsters cannot walk through walls, on water, or through mountains. Monsters can walk on lava, but it causes them damage just like the Avatar.

See table: See image.

public class Monster implements Runnable
-----------------------------------------------------------------------------------------
Monster(World world, String code, int x, int y, int hp, int damage, int sleepMs)
void incurDamage(int damage) // Reduce the monster's hit points by the amount damage
void draw() // Draw the monster
int getHitPoints() // Get the number of remaining hit points of this monster
int getDamage() // Get how much damage the monster causes
int getX() // Get current x-position
int getY() // Get current y-position
void setLocation(int x, int y) // Move monster to a new location
void run() // Worker thread that periodically moves monster

Monsters are defined at the end of the game text file. You can assume the values in the file are valid starting locations, monsters won't start on top of each other, etc. Here is an example:

SK 3 3 10 3 1000
OR 6 19 8 2 1000
BA 20 10 4 1 500
SL 25 16 6 2 1500

This defines:

  • Skeleton at (3,3) with 10 hit points and which causes damage of 3. The skeleton attempts to move every 1000 ms.
  • Orc at (6,19) with 8 hit points and which causes damage of 2. The orc attempts to move every 1000 ms.
  • Bat at (20,10) with 4 hit points and which causes damage of 1. The bat attempts to move every 500 ms.
  • Slime at (25,16) with 6 hit points and which causes damage of 2. The slime attempts to move every 1500 ms.

World. The World class has a number of changes. The constructor must now parse a file with more information about the Avatar as well as information about the monsters. The World object must create and keep track of the Monster objects. Each monster is a thread, so you'll need to fire up one thread per monster. You will also need to implement two methods that handle attempting to move the Avatar or monster.

public class World
-----------------------------------------------------------------------------------------
boolean avatarAlive() // Is the Avatar still alive?
void monsterMove(int x, int y, Monster monster) // Attempt to move given monster to (x, y)
void avatarMove(int x, int y) // Attempt to move Avatar to (x, y)
int getNumMonsters() // Return number of alive monsters
void passTime(int timeMs) // Inform world that some time has passed

The monsterMove() should be called by a monster's run() method. If the proposed location is not valid or not passable, then there is nothing happens. If there is currently another monster at the proposed location, then nothing happen either (monsters don't attack each other). If the Avatar is at the proposed location, then the monster gets to attack the Avatar and go the appropriate damage. In this case, the monster stays at its current location (Avatar and monsters never overlap). Otherwise, the monster makes its move to the new location incurring any damage associated with the new location (i.e. lava).

The avatarMove() method should be called when the handleKey() method tries to move the Avatar. Similar to the monster, is the proposed location is no valid or passable, the Avatar stays put. If there is a monster at the location, the Avatar attacks it and the Avatar stays put. Otherwise, the Avatar moves to the new location incurring any damage associated with the new location (i.e. lava).

Academic Honesty!
It is not our intention to break the school's academic policy. Posted solutions are meant to be used as a reference and should not be submitted as is. We are not held liable for any misuse of the solutions. Please see the frequently asked questions page for further questions and inquiries.
Kindly complete the form. Please provide a valid email address and we will get back to you within 24 hours. Payment is through PayPal, Buy me a Coffee or Cryptocurrency. We are a nonprofit organization however we need funds to keep this organization operating and to be able to complete our research and development projects.