CSCI 136
Fundamentals of Computer Science II
Spring 2018

Montana Tech of The University of Montana
Computer Science & Software Engineering



Assignment 7 - Ultima 0.1

In this assignment, you will be adding to existing code in order to 1) implement a recursive lighting algorithm, and 2) to write the run() method for the Monster threads.

Getting started. Start by downloading student.zip. This is the game of Ultima, in which there are monsters you (the avatar) must go around and kill. You attack monsters by running into them. Monsters attack you in the same way. You can also walk on lava if you want, but it costs you one hit point.

You will see several classes. You only need to add code to two of these, but you will need all of the other files, both Java and image files, for the game to work correctly. When done, you need only turn in the two files that you modify.

Probably the most difficult part of this assignment will be to look at the code that has been provided to you and figure out how you can use the methods provided, without modifying them, to support the code you need to write.

The two things you need to add to the game involve recursion and multi-threading. In the Monster class, you will see that it implements Runnable. That means you need to write a run() method. If you scroll toward the bottom of the code, you will see two places marked <YOUR CODE GOES HERE>. That is the only thing you should change in the Monster.java file - the addition of code that makes the monster sleep, and the code that makes the monster move in a random direction.

The monsterMove() in the World method should be called by a monster's run() method. The monsterMove() method checks for all the conditions that might be illegal, so your run() method does not need to do this. If you look at this method in the World class, you will see that if the proposed location is not valid or not passable, then nothing happens. If there is currently another monster at the proposed location, then nothing happens (monsters don't attack each other). If the Avatar is at the proposed location, then the monster gets to attack the Avatar and do 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. if the new location is lava). Note: since only the World object knows the outcome of the monster's call to monsterMove(), the World object updates the calling Monster object by calling setLocation() and/or incurDamage().

In the World.java file, you need to write the code that recursively lights the appropriate tiles. You will see a place marked <YOUR CODE GOES HERE> in this class also. Think about this carefully.

You will need to write the code for a recursive method that gets called by the public light method:
     private int light(int x, int y, int currentX, int currentY, double r)

The basic approach is to first set all the Tile objects to being unlit. Then start the recursive light at the Avatar's current position. The light method will call itself recursively for the positions to the north, south, east and west (these four directions only, do NOT recurse on the diagonals). This allows the algorithm to spread across the map. The light method needs to retain the initial (x,y) starting position so it can calculate how far the (currentX, currentY) position is from it. You must of course be careful to limit the recursion with appropriate base cases:

Opaque cells should still be lit, but they should not propagate the search. This is what causes certain parts of the map to appear black despite being within the radius of the torch.

Descriptions of the classes you will be using - no need to modify these, except for portions of Monster and World:

Avatar. The Avatar class has represents you as the player. The Avatar now has a number of hit points (life). Once your hit points reach 0, the game is over and you lose. The Avatar has a damage amount, this is how many hit points of damage our hero causes when attacking a monster. If the Avatar incurs damage (from a monster or from walking on lava), the new hit point value after the damage is displayed in yellow text over the Avatar image. The hit point display stay visible for approximately three seconds and then disappears.

Tile.The Tile class represents a single square in the Ultima 0.1 game world. Tile objects can report the damage they cause if they are walked on. All tiles cause 0 damage except for lava which causes 1 point of damage.

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 a monster is damaged it displays its remaining hit points in red text over the monster's image for approximately three seconds. If a monster's hit points are 0 or less, the monster has been killed and no longer is shown.

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 the randomly chosen direction they do, otherwise they skip their turn and remain in the same location. 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 damage just as it does for the Avatar.

World. The World class holds the objects in the game world, including all the tiles, the avatar, and an ArrayList of the monsters. The constructor parses a configuration file with information about the Avatar as well as information about the monsters. The World object creates and keeps track of the Monster objects. Each monster is a thread, so one thread is fired up per monster.

The avatarMove() method should be called when the handleKey() method tries to move the Avatar. Similar to the monster, if the proposed location is not 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. if the new location is lava).

Grading
Grade ItemPoints PossiblePoints Earned
Program Compiles
3
Program Runs
3
javadoc Comments on All Classes and Methods
4
Recursive Lighting Method
10
Monster run() Method
10
Total
30


Do I need to use the code as provided? Yes. For this assignment, you only modify the methods that were discussed above. If you want to make interesting changes to the other code, do this assignment first, submit it, and then submit the changes to the extra credit drop box.

Why does monsterMove() get passed a monster object? The monsterMove() method in the World class gets called by the monster's thread. In order for the method to do things like damage the monster for walking on lava or updating its location, it needs a reference to the object. While the monster run() method could do this, it could cause concurrency trouble.

But how do I pass monsterMove() the monster object? The run() method can use the this keyword which is a reference to the object running the method.

If a monster calls say Thread.sleep(1000) between moves, will the monster move exactly every second? No, in practice it might take a little longer than a second. Thread.sleep makes sure your thread isn't scheduled for execution for the specified period of time. Thereafter the thread must wait to be scheduled on the CPU which could take more time. For the purposes of our game, this is close enough.


Extra credit. Make the game better.


Submission. Submit your modified programs World.java and Monster.java, using Moodle. Be sure each submitted source file has the required javadoc header with your name and a description of the program. For extra-credit, please submit a single zip file containing all your programs/graphics/sounds via the special extra-credit drop box.

Page last updated: December 27, 2018