Summary

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:

  • Cars
  • Riders
  • Obstacles (e.g. buildings)
  • A grid-shaped map that holds the above three kinds of objects
  • Coordinates on the map used to represent the locations of cars, riders, and obstacles
  • Strategies for cars moving around automatically

You will also:

  • Produce both graphical and a text-based output for a given state of the simulation, displaying the riders
  • Implement a manual controller for the users car that reacts to keyboard input
  • Make the simulation update based on a timer to produce an animation

See the following screenshot, mid-game. see image.

Simulation State

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

  • The location of each car
  • The direction each car is travelling in
  • The location of the rider (if there is one)
  • The location of the obstacles

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.

Getting Started

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.

Stage I - The Coord Class

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:

  • Determine if two Coord instances are the same (row,column) coordinate. Override equals()
  • Add two coordinates together, return a new coordinate
  • Define the difference between coordinates
  • Define the distance between two coordinates.
  • toString with format Coord:(row=%d,col=%d)

Requirements of Coord (you will turn in Coord.java)

  • You should not add any other public methods or fields, change any signatures or modify any return types for Coord.
  • Your Coord.java does NOT have to reproduce the javadoc, please put your name(s) in a javadoc comment at the top. You may leave out all the other comments.

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.

Stage 2 - GridObject.java and Obstacle.java

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.

Stage 3 - The GridInfo and CoordInfo interfaces

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)

  • coordFree(Coord c) - an object that implements a CoordInfo interface can respond true/false if the argument (c) is free. A free coordinate is one that is on the grid, and doesnt contain a car or obstacle. In the class implementing coordFree, an invalid coordinate c should logically return false. This tells the caller whether the coordinate could have something placed there, for example whether a car could move into that space or not.
  • claim(SharedCar car, Coord loc) - A SharedCar (detailed in Stage 4) car is attempting to claim the coordinate location loc. If successful, claim returns true and the car should set its location to loc. If not successful, claim returns false and the car should not change its location. The class implementing claim may, optionally, set the location of the SharedCar to a particular location during the claim.
  • riderLoaded(SharedCar car) -- returns true if the rider was loaded into the SharedCar car.

You should not change either of these interfaces. And, you have nothing to turn in for this stage.

Stage 4 - CarController.java and SharedCar.java

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().

  • roam(Coord current) - the CarController is to return the direction (NORTH,SOUTH,EAST,WEST) when there is no particular destination.
  • drive(Coord current, Coord goal) - the CarController is to return the direction when it is attempting to get to a particular goal coordinate. The particular implementation of a controller determines what it means to try to get to a particular coordinate (discussed more below).

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

  • drive() - based upon the boolean wantRider, drive either invokes roam() of its controller to get a direction or invokes drive() of its controller to get a direction. When a direction is given by the controller, it attempts to claim the space in that direction. If successful, the SharedCar updates its location.
  • newRider() - tells this sharedCar that there is a new rider to seek
  • roam() - tells this car that is should roam (there is no rider).

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

  • DummyController - does nothing of any substance internally, but is a subclass of CarController. For example, DummyControllers drive() and roam() methods could always return NORTH.
  • DummyGrid - implements the GridInfo interface. Its claim() method could always return true. Its riderLoaded() method could always return false.

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

Stage 5 - Rider.java

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.

Stage 6 - Grid.java - Part 1. Add Objects and Print the Grid.

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

Stage 7 - Grid.java - claim()

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.

Stage 8 - coordFree() and EastWestController.java (part 1)

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.

Stage 9 - riderLoaded() and EastWestController.java (part 2)

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.

Stage 10 - Standard input driven testing

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

  • Drive
  • Car < row> < col>
  • Rider < row> < col>
  • Obstacle < row> < col>

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 successively, your grid should animate in a flip-book style animation. If all this feels like its working well. Make a copy of your files. Pat yourself on the back. Youve over the first (and biggest) hump of this assignment.

Stage 11 - First Graphics Display

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.

Stage 12 - Getting serious about Graphics - Simulation.java and the TimeTick Thread

At this stage you have some choices about what you do. It gets tiring to hit d to make your cars advance on the grid. Your graphics program needs to called Simulation.java. Now look at TimeTick.java. This is intended to run in a standalone thread (e.g. use Thread). When you create an instance of TimeTick, you need to pass it your Simulation instance and your Grid instance. The TimeTicker sleeps for 10ms, wakes up, and invokes update() on its Simulation instance (this is to keep the graphics lively when we get to manually controlled car). Every ticks intervals it invokes update() on the grid instance. Start with ticks at 100 (1 second) ticks. Basically, this TimeTick replaces pressing a key repeatedly to move your simulation forward.

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.

Stage 13 - Completing the Graphics Interface

Here is a screenshot of a 30 x 30 opening screenshot see image.

Lets go through the components

  • Top part, track the number riders that manual player has loaded and the robots have loaded
  • Center, the GraphicsGrid representation of the Grid
  • New Game - Button. Start a new Game
  • Pause Button - Pause the game
  • Speed Slider - Left is slow, right is fast. Adjusts as the game is being played.

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.

Stage 14 - Adding a player

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:

  • h - turn the car left (meaning counter-clockwise)
  • l - turn the car right (meaning clock-wise)
  • < space> - invoke drive() on the players SharedCar

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).

Stage 15 - Requirements of the Game/Interface

The following are requirements of the Game/Interface

Command-line arguments. Simulation can be invoked in two ways

  • java simulation < row> < col>
    • Create a simulation < row> x < col>. Default setup. Manual driver at the center
  • java simulation < configfile>
    • Create a simulation using the configfile (readable by the GridSetup class)
  • You do NOT have to check for valid arguments. We will only check with integers for < row> < col>. We will only check with existing configfiles.

Start-up

  • The program is not start immediately. It should show the initial setup (similar to what is above)
  • The Game should only start when < New Game> is pressed.
  • The default speed should be to move cars once/second (100 ticks)
  • If in a default setup, there are is no robocar unless defined in the configfile

Play

  • The manual controller should respond to the key presses as above. The space bar means that the manual car has an accelerator. It can go faster than the robo cars
  • Robocars seek out riders or roam
  • When a rider is successfully picked up, the program should create a new rider at a random location. Ideally there would be a random delay of 0 - 5 seconds between pickup and the next rider being created. This is not a strict requirement, but makes the game more interesting
  • When a rider is picked up, the counts of player pickup/robot pickups should be updated.
  • Starting with the second rider, a new robocar should be created every 10th pickup. Every 10th pickup a randomly-placed obstacle should be added to the grid
  • Every 10th pickup, the number of ticks that the time waits should be reduced by 10 ticks. The minimum number of ticks is 10. (100ms).
  • The player may increase the speed, but may not reduce the speed below what would be calculated. For example, At 55 pickups means that the ticks must not be larger than 50 (.5 seconds)

Buttons

  • When the New Game is pressed, the game should return to its startup state, but using the current speed as selected by the slider.
  • When the Pause button is pressed, the game should stop moving cars forward. It should change its label to resume. When resume is pressed, the game should continue

Slider

  • The slider should have effect of changing the speed of the update in the range of [0.1s -- 1.0s] . 0.1s is FAST, 1.0 is SLOW.

Stage 16 - Other Controllers

You need implement two more controllers

  • NorthSouthController - equivalent to east/west but roams north/south, and reduces north/south before east/west when in drive mode
  • RandomController - picks a random direction in roam mode. In drive mode, reduces the larger of row,col when computing dist(goal). That is, when close enough, it tries to follow a diagonal line to the Rider.
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.