For this assignment, youll implement a simulation/game (gamulation?) of drivers picking up passengers on a map, and bringing them to their destination. There will be a user-controlled driver and a number of computer-controlled drivers, all of which you will implement.
You will design and implement classes for:
You will also:
See the following screenshot, mid-game. see image.
At any given moment in the simulation, we need to represent the grid and all the components on it. That is, we need data that stores
You should represent all the locations and directions with a Coord class, that stores a row and column for representing a position on the grid.
This is a long and complicated assignment, but is quite doable if a) start in the first week, and b) follow a reasonable plan to decompose the assignment into development stages. A goal of this assignment is to compel you to develop in stages -- this is complicated enough that attempting to write all classes and then begin testing is a sure recipe for a non-working program. So take a deep breath, and read each of these stages. You need to completely understand and implement the very first stage before you start the second one. Finish the second and go on to the third.
The very first class that we need is a uniform way to represent where objects are located on a grid. Once weve decided on how to represent where, it will be handy to have a number of operations on the objects. For this assignment, you will be building a coordinate class called Coord. There are two reasonable ways to think about coordinates on a plane: (x,y) and (row,column). This coordinate class is implementing the (row,column) version. There is a pdf that is the complete javadoc for our implementation of Coord.
As you look at the javadoc, you will notice that row and col are declared public final and that there are no setters or getters. This is one of those cases when direct field access makes code more readable and usable. Also notice that these fields are declared final. That means that there values can only set at construction time and can never be changed.
As you look further at the javadoc, there are a number of operations that need to be defined. They include:
Requirements of Coord (you will turn in Coord.java)
Testing Coord.java
You should test Coord.java as you see fit. Sometime during week9 we will give you access to an automated testing suite to validate your Coord class.
You are being supplied GridObject.java, which is a class that represents one of the game entities on the grid (a car, rider, obstacle, or blank space). You should read this class definition. For example, UP is defined to logically mean facing in the up direction.
Obstacle extends GridObject . The only difference between a GridObject and an Obstacle is that default color. Obstacles must be some color other than java.awt.Color.WHITE.
You will turn in Obstacle.java. GridObject should remain unchanged.
The interface GridInfo and CoordInfo interfaces are being supplied to you. You should look at them while reading this section. There isnt anything to Code for this section, this is to start you down the path of thinking logically about different pieces of information that are needed to successfully perform a simulation. (You might look at the diagram in the FAQ)
You should not change either of these interfaces. And, you have nothing to turn in for this stage.
Both of these files are being supplied to you, but you must take the time to understand them A CarController is an object that returns directions that a car should go based upon its own rules. You will eventually implement several CarControllers. CarController is an abstract class. see image.
Notice that CarController creates constants that are NORTH, SOUTH, EAST, and WEST and their types are Coord. These constants are chosen so that they map to a 2D array, where moving South increases your row index by 1, moving north decreases your row index by one.
In particular if your are at Coord (row, col) and you want the Coord to the WEST of the coord you simply invoke Coord.add(WEST). This will return to you the coordinate (row, col-1). In other words, NORTH, SOUTH, EAST, and WEST are (unit) direction vectors.
The key methods in CarController are drive() and roam().
Notice that the constructor of CarController takes an object that implements the CoordInfo interface. When you subclass CarController, you need to make sure to invoke this constructor in your subclasss constructor using super.
SharedCar.java
SharedCar.java is being supplied to you and you should not change it. But you need to understand its contents. SharedCar.java is a subclass of GridObject. Just like Obstacle is a subclass of GridObject.
Constructor: SharedCar (CarController, GridInfo) - to Construct an instance of SharedCar you will need to implement a non-abstract subclass of CarController. You will also need a class that implements GridInfo (more on these two in just a few lines).
Lets look at some of the other methods of SharedCar
At this point in development, you should now write a couple of stand-in classes so that you could create a SharedCar instance. You might create two simple classes
And finally, you should create a simple main program that creates a SharedCar instance, tells it to drive several times, and then prints out the SharedCars location after every move (How can you get the SharedCars location?). These kinds of small programs allow you to become familiar with some new classes. You could then modify DummyController to move NORTH the first 5 times it was called, then EAST the next 5, then WEST the next 5, then WEST the next 5, printing the Cars location after every move. Where should your car be after this?
Another thing to note, some your coordinates had negative components if you started at (0,0). Notice that Coords only store (row,col) with no constraints on positive/negative. SharedCars also have no idea how their Coordinates would be constrained. In other words, SharedCars need to know very little about the world in which they are driving
You only need to look at Rider.java (notice that it is also a GridObject). Riders are can be constructed, they can return if they are still waiting, that they are being picked up, and can return which SharedCar picked them up.
There is nothing to code in this section. But look at the methods in Rider. Try to make sense of how they might be used.
OK, its time to get to some real work. Grid.java is where the state of our simulation is kept. (By the way, have seen anything about graphics, so far? Theres a reason why the answer is no). Grid.java must be developed in stages. Were not giving you any code for this, but there will be plenty of hints. This part is going to take a while, and as you add more capability, you will add functionality. Lets write down the critical things that Grid.java must do
1. In the constructor, in needs to be told the dimensions of its grid (row x col)
2. Cars, Obstacles, and Riders all must be able to be added to the Grid. The Grid must also keep track of all the Cars, Obstacles, and Riders. (You might use several ArrayLists internally to keep track of these)
a. You might build methods like addRider(..), addCar(..), addObstacle(..)
b. You might want to overload these methods to have different signatures
c. Its convenient for these to return booleans for success or failure. For example adding a Car at either and out-of-bounds coordinate or an already-taken coordinate should return false (and not add the car)
3. Grids need to implement both the GridInfo and CoordInfo interfaces. In the FAQ is a picture of some of the inter-class messaging/invocation. Mostly, cars will need to claim spaces on the grid. CarControllers use the CoordInfo to determine if a space is free
4. Some external source of input (maybe a user typing a command, maybe a timer) may want to tell a Grid to tell all the SharedCars to drive(). So a method that causes all cars to drive() is important.
5. A toString() method that pretty prints whats on the grid. You can use your own format, but note that all GridObjects have the getSymbol() method.
6. Our Grid constructor only needed to be told how big it should be (rows x cols)
Some recommended steps for developing Grid (and using the GridSetup class)
0. You should create Grid.java and a main method in your DummyGrid.java() file that creates a Grid Object and then prints it. Dont add anything, just make sure that you can create and print a grid. That is % java DummyGrid at the command line should create some sort of grid and print it.
A. Create a text file with the following contents (call it gridtest1)
dimension 10 10
robocar 5 6 east
rider 1 1
obstacle 7 2
B. Look at GridSetup.java and create an instance of GridSetup to read this information from the file. When you construct an instance of GridSetup, it opens the file passed into its constructor, and parses the lines. Any format that is doesnt recognize is just skipped. After the file is read, getters can be called to return arrays of particular Coord references.
Using the methods available on the GridSetup instance, create a grid, add a car, add a rider, add an obstacle. In order to add those, you will need to modify Grid.java to have your add methods defined. You will also need to decide how to store the information you need to keep track. You might use a 2D array, you might use ArrayLists. You might need some other internal information. This part is up to you. When doing this for our testing purposes, this is the kind of output we achieved (you should have similar results at this stage) see image.
Once you achieved this level of success, you should make a copy of all your files and store them for safekeeping. At this point, youve learned how some of your classes interact, created a test file that added objects to a grid and then printed a grid.
You should try other setups with multiple cars and/or multiple obstacles so that you feel comfortable with the state of your program at this stage
Recall that you created a DummyController in a previous stage. The next thing to do is to see if you get a Car to move on your grid. If you first set your DummyController to always return NORTH as a direction, you can do some preliminary testing:
1. Implement claim() in Grid.java
2. Modify your DummyGrid.main file to read lines from standard in, each time your code reads a new line, tell your SharedCar to drive, then print the grid. You will know things are working, when you see the position of the Car change each time you print the grid. You can try different setups - make sure that the car doesnt claim a space that is already taken by an obstacle. Make sure the car does not go out of bounds. Dont just test NORTH, test all the directions. Iterate and test until you are satisfied that claim() is working correctly.
3. Once you are past step 2, its time to tell all SharedCars indirectly to drive. Add several cars to your grid, and then modify Grid.java to have a drive() method. When you invoke Grid.drive() all of the cars should move forward.
OK, youve made it this far. Now you have two simultaneous tasks that need to be developed together. You need to implement coordFree() in Grid.java and build your first version of EastWestController. This first version should only fully implement the roam() algorithm. In the controller, it should use coordFree to determine if the coordinate in its current direction is free, if it is free, roam() should keep going in the same direction. If it is not free, roam() should reverse direction. Modify DummyGrid to create car(s) with EastWestControllers and verify that when running into an object, or the end of the grid, the car reverses direction. Remember, all of this is still under keyboard control. You should be able to check things step by step. Use print statements as you see fit to debug. Also, really check with multiple cars that all start in different columns, you should create a different controller instance for each car and verify that they reverse direction independently.
Now its time to get your controller to seek a specific location. You will need to have a way to inform your car(s) that a rider is available. This should change SharedCars to use the drive() mode of their controller instead roam() mode (See SharedCar.java).
Your grid needs to implement a riderLoaded() method. Think about riderLoaded() when a particular car has successfully loaded a rider, the rider is picked up (look at Rider.java -- for the methods to tell the rider which car he/she is in). riderLoaded() needs to also tell all the other cars to go back to roam() mode. Only the Grid knows about all the cars, cars dont know about each other.
EastWestController drive() method: EastWestControllers first try to reduce the distance in the east-west direction and then reduce the north-south direction. Your car needs to react when an obstacle is in the way and attempt to go around it. Really good controllers almost never get stuck, good controllers get stuck in one particular case, and bad controllers get stuck often. Were aiming for at least a good controller. If you want to try to do a really good controller, finish the assignment and then come back this. Talk to us in office hours for this particular case.
At this stage, you have a real controller (EastWest) and youve been testing individual pieces. The next step is to write a small command interpreter that reads commands from the keyboard and does things to the grid. Some commands that you might support are
After each command print the state of grid. You might shorten the commands to accept just the first letter (saves on typing). The idea is the following, you should be able to execute any of the commands in any order and the right things should happen. You shouldnt be able to add objects out of bounds or on top of existing objects. If you hit d
The first thing is to look at GraphicsGrid.java. We are supplying you this class to simplify the graphics part of the assignment. GraphicsGrid is a subclass of JPanel, which allows it to participate in swing graphical layouts and take up some space on the screen.
When you construct a GraphicsGrid instance, you tell it how many rows and columns (this is not pixels, it is logical just like Grid). The default is that each block of the GraphicsGrid is 10x10 pixels.
The class will resize these blocks if you resize the window.
Next, how does GraphicsGrid know what to display? The method addGridObject allows you to add GridObject to be displayed. GraphicsGrid doesnt do any error checking, you can give a GraphicsGrid object with negative coordinates -- it might or might not crash. It is expecting the Coords of the GridObjects to already be in range.
Write a small program that creates and displays a GraphicsGrid. Just get to know the class and build the start of the interface.
Now, where you were at the end of Stage 10, whenever you add an object to the Grid (Car, Rider, Obstacle) add the very same object the GraphicsGrid. By very same, we mean the same object reference. You can have text and graphics at the same time. Everytime you hit d < eturn> in your stage 10 version, tell your grid to update() itself and tell your GraphicsGrid to repaint(). If you have it right, your Graphics display should be a graphical version of your grid.
Notice that GraphicsGrid knows nothing about the particular game, drivers, cars. It only knows how to display GridObjects. The class hierarchy is that drivers, cars, obstacles are all GridObjects.
At this stage you have some choices about what you do. It gets tiring to hit d
Think about what the Simulation needs to track. In the abstract, it needs to know about the driver, and the rider. It needs to create cars and obstacles. Its needs to know when to create cars, obstacles and riders. It doesnt need to know the details of exactly where GridObjects are located on the Grid (it leaves that detail to Grid).
If you get to where you can use GridSetup to create a simulation with cars,obstacles, and a rider, and then TimeTick moves the simulations forward (picking up a single rider). You are in great shape. Ideally, you would be at this state several days before the due date.
Make copies of all your work.
Here is a screenshot of a 30 x 30 opening screenshot see image.
Lets go through the components
At this stage, you should be able to load a configuration using GridSetup, click new Game and watch your cars animate, seeking the single Rider (if any). Until loaded. The following is a graphical picture of the original test setup in Stage 6 (picture includes a red manual driver, which you have not added yet) see image.
Notice that there are no arrows, because directions havent been set. Now, after a couple of steps of the simulation (and the pause button has been hit). We get the following display see image.
Notice that Blue (robocar) is running the east/west controller. At this point the car is first reducing the east west direction. The Red triangle is a manually-controlled (player) car. The black block is an obstacle.
You need to create your second controller -- ManualController.java. The ManualController Should implement the KeyListener interface to respond immediately to key presses. When your game starts up, the manual player should be in the middle of the grid, colored differently than the Robot Cars, Riders, and Obstacles. To be clear, you will create single SharedCar with a ManualController. That particular SharedCar is the player of the game.
Keys:
You need to think about which object deals with the spacebar. CarControllers have no reference to their SharedCar There are several possible solutions. You and your partner should talk about how you want hitting the spacebar to end up being translated into invoking drive() on the players car (and only the players car).
The following are requirements of the Game/Interface
Command-line arguments. Simulation can be invoked in two ways
Start-up
Play
Buttons
Slider
You need implement two more controllers