TCSS 143, Autumn 2004 Homework 3: Easy Street (Inheritance) Assigned: Due: Mon 2004/10/25 Sun 2004/10/31, 11:59pm This assignment is designed to test your understanding of inheritance, polymorphism, interfaces, and abstract classes. In this assignment, you will write a simulator of a city and its streets, along with various vehicles and obstacles that travel them. This simulator will be called Easy Street. Your task is to write the classes to model the various vehicles on the streets. The classes will have many similarities, so you should use inheritance to design the classes effectively. The graphical user interface (GUI) has already been written for you. This interface displays the Easy Street as a grid with the same coordinate system used in Rectangle-Rama: grid square (0, 0) is at top-left. The GUI shows pictures for the map terrain and vehicles. The GUI begins by loading the city map from the provided file city.map and placing several vehicles on it. There are buttons to start and stop the city's animation, as well as step through it one frame at a time, or clear or reset the vehicles. Each time the GUI redraws, it tells each vehicle to move itself. If any two vehicles end up on the same square, the GUI tells the vehicles that they have collided with each other, which may "kill" them temporarily. "Dead" vehicles appear upside down on the screen. The rate of city animation is controllable via a slider at the bottom of the interface. Also, debug information can also be shown using a checkbox. If debug information is enabled, all (x, y) grid positions on the map are labeled, and every vehicle's toString output is shown next to the vehicle. You can put any information you like into your vehicle's toString method to help you debug your vehicle behavior. You must write at least four classes for the Easy Street project. Each of the four classes must implement the provided Movable interface described below. The following UML class diagram shows the classes and their methods: 1 of 4 Implementation: The following are the methods in the Movable interface that your vehicle classes must implement. public void collide(Movable other) Notifies this vehicle that it has collided with the given other movable object. Different vehicles respond to this event in different ways, as described later. Collisions should only have an effect when they occur between two vehicles that are alive. When the GUI notices that two vehicles have collided, it calls this method on each vehicle. Because the first collide call may kill the vehicle involved, collisions between two vehicles of the same type may only kill one of the two vehicles. public int getX() public int getY() Returns the x or y coordinate of this vehicle. The x or y coordinate may change when move is called. public String getImage() Returns the name of the image file that the GUI will use to draw this movable object on the screen. For example, a living Car object should return the string "car.gif" and a dead hobo should return the string "hobo_dead.gif". Descriptions of the images for each vehicle follow later in this document. (Implementation hint: calling getClass().getName().toLowerCase() from a class named Car would yield the string "car". public boolean isAlive() Returns true if this movable object is alive; that is, if it has not collided with a more powerful movable object and gotten killed. Killed vehicles revive themselves after a certain number of calls to move, as described later. public void move(char[] neighbors, int light) Instructs this movable object to move itself. Different vehicles have different movement behavior, as described later. The neighbors argument is an array telling what squares neighbor this movable object. The neighbor squares are characters whose values are constants in the Movable interface such as MAP_STREET, MAP_GRASS, and so on. The elements are indexed according to constants in the Movable interface such as DIR_LEFT, DIR_UP, and so on. If you implement this interface, you inherit these constants and can use them. So for example, to see if the square to the left contains a street, one could write code such as the following: if (neighbors[DIR_LEFT] == MAP_STREET) { /* ... do something ... */ } The neighbors element at index DIR_NONE represents the square that this movable object is currently standing on. The light argument is an integer whose value is a constant in the Movable interface such as LIGHT_GREEN or LIGHT_RED. So for example, to see if the city streetlights currently are yellow, one could write code such as the following: if (light == LIGHT_YELLOW) { /* ... do something ... */ } public void reset() Instructs this movable object to return to its initial state (including position and direction, and reviving if dead) from when it was constructed. 2 of 4 Vehicles: The following is an explanation of the unique behavior of each vehicle. Bicycle Constructor: public Bicycle(int x, int y, int dir) Alive image: bicycle.gif Dead image: bicycle_dead.gif Movement behavior: Bicycles can ride on streets (squares of type MAP_STREET; lights (MAP_LIGHT) also count as streets), but they prefer to travel on trails (MAP_TRAIL). If the bicycle is already on a trail, it simply goes straight, traveling one square in the direction it is facing. Trails are guaranteed to be straight lines that end in streets. If the bicycle is not on a trail but there is a trail next to the bike's current position, the bicycle turns to face the trail and moves one square in that direction. (If several trails are next to the bike at the same time, any trail may be chosen.) However, if the bicycle is on the street and no trail is nearby, it will go straight on the street if it can do so, otherwise it will turn left if possible, otherwise right if possible, otherwise turn around. If it cannot move in any direction, it will sit still. Bicycles obey traffic lights; if a traffic light (MAP_LIGHT) is immediately ahead of the bicycle and the light status is not LIGHT_GREEN (for example, if it is LIGHT_YELLOW or LIGHT_RED), the bicycle will stay still and not move. On a subsequent call to move, if the light value is LIGHT_GREEN, the bicycle may resume moving. Collision behavior: A bicycle dies if it collides with a living bicycle, car or truck. After 20 moves, it comes back to life and resumes moving. Car Constructor: public Car(int x, int y, int dir) Alive image: car.gif Dead image: car_dead.gif Movement behavior: Cars can only travel on streets. If possible, the car drives one square in the direction it is facing. Otherwise, it tries to turn left if possible, or if that fails, it tries to turn right if possible. If the car cannot move any of these three directions, it turns around. Cars stop for red lights, not moving at all if a red light is directly ahead of them; but they drive through yellow lights. Collision behavior: A car dies if it collides with a car or truck that is alive. A car stays dead for 10 moves. Hobo Constructor: public Hobo(int x, int y) Alive image: hobo.gif Dead image: hobo_dead.gif Movement behavior: A Hobo moves randomly in any direction that he can legally move. Hobos always stay on the same type of territory that they are currently standing on: If a Hobo starts out on the street, he randomly roams the streets; if he starts out on the grass, he roams that body of grass; and so on. Hobos ignore traffic lights. Collision behavior: A hobo dies if he collides with anything (including other hobos) and stays dead for 50 moves. Truck Constructor: public Truck(int x, int y, int dir) Alive image: truck.gif Dead image: truck_dead.gif Movement behavior: Trucks travel only on streets. Their preferred movement is to randomly select to go straight, turn left, or turn right. If none of these three directions is legal (all not streets), the truck turns around. Trucks drive through traffic lights without stopping, even if the light is red! Collision behavior: A truck survives a collision with any vehicle except another truck. When a truck collides with another truck, it dies for 5 moves. 3 of 4 Extra Credit: For +3 extra credit points, you may turn in an additional vehicle. This vehicle must be a class that implements the Movable interface and has a constructor that takes arguments (int x, int y, int dir). You should also provide images for the new vehicle type when it is living and dead (place the images in the same folder as your other files). For full credit, the behavior of your new vehicle should be nontrivial and unique from the four provided vehicle types. You can test your new vehicle by clicking the Custom... button in the GUI and using it to construct instances of the new vehicle type. Submission and Grading: Submit this assignment online, as with all programming assignments. Turn in your vehicle code only; this should include Bicycle.java, Car.java, Hobo.java, and Truck.java. A sample solution will be posted to the course web site. You can run this sample solution to test how your program should behave. When in doubt or when behavior is otherwise unspecified, match the behavior of the sample solution exactly. The correctness of your program will be graded on the vehicles' behavior, which is observed by running the GUI and examining the result. Exceptions should not occur under normal usage. You should not produce any console output, such as System.out.println statements in your code that were not specified. The design of your program will be graded on whether you follow the program specification above, whether you implement the interface given, whether you use inheritance as requested, and the reasonableness of your code and algorithms. You should put any common code or behavior into a common superclass to avoid redundancy, and you should strive to make as much code common as is sensible. Your style will be graded on the presence of reasonable brief comments in your source code (a short header with your name and course on each class, a header on each method, and a comment on particularly complex code sections), the use of meaningful identifier names, appropriate use of modifiers such as public, private, protected, and static; avoiding redundancy, and general spacing and indentation of your code. This program must be completed individually. It is subject to the plagiarism and conduct rules specified in the course syllabus. Hints: You may wish to refer to the classes java.lang.Math and java.util.Random to help with implementing this assignment. The following might be useful common behaviors for you to implement: getDX(direction) --> getDY(direction) --> turnLeft(direction) --> turnRight(direction) --> turnAround(direction) --> returns -1 if going left, +1 if going right, 0 otherwise returns -1 if going up, +1 if going down, 0 otherwise returns left if going up, down if going left, right if going down, up if going right returns right if going up, down if going right, left if going down, up if going left returns the opposite of the given direction: up <-> down, left <-> right 4 of 4