[Solved] River Crossing Project Code Solution

$25

File Name: River_Crossing_Project_Code_Solution.zip
File Size: 339.12 KB

SKU: [Solved] River Crossing Project Code Solution Category: Tag:
5/5 - (1 vote)

Overview

In this project, we will redesign an existing RiverCrossing application. The application includes a test, which currently passes. The application also includes a GUI component, which does not work correctly with the existing game engine. It would be nice if we were able to implement different games like those found here:

https://www.transum.org/software/River_Crossing/ (Links to an external site.)Links to an external site.

In order to do this, we have to tackle a few problems.

  • First, although the code works for a specific case (it passes the test case), the code is not in very good shape. There is a lot of duplicate code in the game-engine class and there is some unnecessary code in the game-object class. The GUI is also in need of some serious refactoring.
  • Second, the code (game-engine) does not work properly with the GUI. We need to get those two components to communicate with one another.
  • Third, how do we generalize the code so that we can not only handle the farmer, goose, and beans, but also handle the other scenarios, like the big and small robots, and the monsters and munchkins?

Part 1 Get Familiar with the Current Code

Getting the GitHub Project

Go to the RiverCrossing project (Links to an external site.)Links to an external site. on my GitHub site and download the ZIP file of the current code. Move it to the workspace of your favorite IDE and create a project using that code. For example, in Eclipse you just create a new Java project and give it the name RiverCrossing and as long as the RiverCrossing folder is in your workspace, Eclipse will detect that and use it for the project.

Once you have the files working in your IDE, do the following (note: you do not need to turn this in):

  • Sketch out a class diagram for the application that includes all the classes in the river package and the relationships between them.
  • Look at the game-engine test class and try to understand how the engine gets to a winning state in the test-winning-game test case.
  • Look at Wikipedias entry on Model-View-Controller (Links to an external site.)Links to an external site.. Can you identify each of the 3 components in the current application?

Try to answer the following questions about the project and how we need to redesign it:

  • What is the relationship between the farmers location and the current location? How does the farmers location differ from other game object locations?
  • How can we get the GUI to communicate better with the game engine? Why doesnt the GUI work with the game engine?
  • What do we need to do if we want to create 3 different scenarios like they did in the web link above?
  • What are the implications of creating multiple scenarios on game-object and its subclasses?

Part 2 Refactor the Code

Refactor GameObject

  • Get rid of the GameObject subclasses we dont want to limit ourselves to the farmer-oriented river-crossing game. Therefore, it doesnt make much sense to have specific subclasses devoted to these objects. Furthermore, the subclasses dont serve a lot of purpose they are essentially just there to initialize fields in the game-object.
  • Change the getter method getSound in GameObject so that it is based on a field, just like the getName and getLocation getter methods.
  • We are not going to modify the name field of a game object after it is created. Therefore, get rid of the setter method for it. Remember to check that there are no references to a method (or field or constructor) before deleting it.
  • Clean up the class. Get rid of any unused constructors. Make all fields private. Make the fields that are never assigned to final.

Refactor GameEngine

  • Put the Location class into its own file. Note that there is an Eclipse refactoring that will do this for you.
  • Change all variables named top, mid, bottom, and player to wolf, goose, beans, and farmer. This includes the fields in GameEngine and the constants in Item. Make sure you keep the proper style (for example, constants are always capitalized).
  • Declare a instance variable of type Map that maps Items to GameObjects. Initialize the map in the constructor using a HashMap or an EnumMap (your choice) and put the four game-objects in it. Use your map to simplify the methods getName, getLocation, getItem, and any other methods that can be simplified.
  • Once you have created the map and simplified the code, you should no longer need the fields wolf, goose, beans, and farmer (since you can easily access them using the map). Make sure there are no references to them and then delete them. Note that you will still have to create appropriate game objects in the constructor so that you can add them to the map, but fix things so that you wont need the fields anymore.
  • Change the method names getName, getLocation, and getSound to getItemName, getItemLocation, and getItemSound. This will make it clear that these methods just call through to the game objects getters.
  • The current location is basically just the boat location. Rename the field and the getter to use boat-location rather than current-location.
  • Once we extract the GameEngine interface in Part 6, the passenger field will no longer be needed, so feel free to skip this refactoring.
  • Clean up the class. Make sure all fields and helper methods have private access. And make sure all fields that can be declared final are declared final.

Refactor GameEngineTest

  • The class under test is GameEngine, so declare a private field engine of type GameEngine and initialize it in the @Before method (setUp). Use it to replace the local instances of the game engine.
  • Rename the testObject method to testObjectCallThroughs. Instead of creating game-objects, test the call through methods in the game-engine class. For example, instead of testing whether farmer.getName is Farmer, test whether engine.getItemName(Item.FARMER) is Farmer.
  • Write a helper method called transport that takes an item. The method should transport the item from one side of the river to the other. Use it to simplify some of the test cases in which an item is transported.
  • Write a helper method called goBackAlone that just calls engine.rowBoat, and use it whenever the farmer is going back alone in the test cases.

Remember that through all of the above refactorings, you are only changing the design of the code, not its functionality. Therefore, your test cases should continue to pass after each refactoring and if you do them carefully during each step of your refactoring.

Part 3 Modify the Functionality

In the original GameEngine class, the farmers location is never BOAT. This is not what the GUI class expects. Were going to enhance the GameEngine class so that the farmer is explicitly loaded onto the boat. But before we do this, were going to make some changes to GameEngineTest so that we still pass the tests.

  • Modify the test class by explicitly loading and unloading the farmer in methods transport and goBackAlone.

Recall the helper methods for the test class that you created in Part 2. These methods transport the farmer and (possibly) a passenger to the other side of the river. Make sure both methods explicitly load the farmer into the boat before rowing the boat, and make sure both methods unload the boat after rowing the boat (the transport method already will). Run the tests. Notice that the test cases still pass! Thats because currently, calling the loadBoat method with the farmer item does nothing, and unloading the boat when there is no passenger also does nothing.

Now lets update the game-engine class.

  • Change loadBoat so that if called with the farmer, the farmer is loaded onto the boat.
  • Change the implementation of rowBoat, so that the farmer stays on the boat.
  • Change the implementation of unloadBoat so that the farmer is unloaded from the boat.

Run the tests, they should still work because of the modifications you made. Now try running the application, it should work also. However, notice that (1) the application does not tell you if you win or lose, it just lets you keep playing, and (2) the application unloads the farmer and the passenger at the same time. Were not going to worry about (1) yet, but we do need to fix (2). We want to unload the boat one passenger at a time.

  • Overload the unloadBoat method in game-engine with a method by the same name that takes an Item object as a parameter. It should only unload the specified item and keep the other items where they are.
  • Modify your tests so that they use the new unloadBoat method instead of the original. Run your tests to make sure they work.

Finally, once you have added the new unloadBoat method to GameEngine, tweaked the test cases, and made sure they work, you will have to modify the RiverGUI class. Look for calls to unloadBoat() in the RiverGUI class and figure out how to change the code with the help of unloadBoat(Item) in such a way that one item is unloaded at a time when you run the application.

  • Modify RiverGUI to use unloadBoat(Item) instead of unloadBoat() so that one item at a time is unloaded when you run the application.

Part 4 Prepare to Create the GameEngine Interface

The refactorings in this section have to do with the fact that we dont want the GUI to depend on anything that has to do with this particular implementation of the game engine. Recall that the web site we looked at had three different river-crossing puzzles: one with the farmer (like the one we have here), one with robots, and one with monsters. Now take a look at the GUI. We see:

  • Lots of direct references to the farmer, wolf, goose, and beans through the use of Item.FARMER, Item.WOLF, etc.
  • Lots of indirect references to farmer, wolf, goose, and beans through the colors we choose for the rectangles and the letters we place in the rectangles.
  • Indirect references to farmer, wolf, goose, and beans through the variables names we choose.

Unfortunately, all of this will have to change. Fortunately, we can do much of this through fairly straightforward refactorings.

  • Get rid of references to farmer, wolf, goose, and beans game-objects. The only place these are used anymore are in the constructor and in the gameIsWon and gameIsLost methods. For the constructor, you can use local variables to help you create the game objects, or just inline them. For the gameIsLost/Won methods, replace goose.getLocation() with getItemLocation(Item.GOOSE) and do the same for the other game objects. Run your tests to make sure they work. Check that there are no other references to wolf, goose, beans, and farmer, and then delete those fields.
  • Move the Item class out of GameEngine and refactor the names as follows:

o BEANS => ITEM_0

o GOOSE => ITEM_1

o WOLF => ITEM_2

o FARMER => ITEM_3

  • After you do this, the constants in the Item class will not be in numerical order. Move them around so that they are this will become important when refactoring the GUI. In the game-engine class, extract constants for Item.ITEM_0, Item.ITEM_1, etc. Give them their former names of BEANS, GOOSE, etc. Do the same for the GameEngineTest. Run you tests and the app to make sure everything still works.
  • Replace properties name and sound in GameObject (which are not used except in tests) with label and color. The label property is a string and the color property is a color. These properties will be used by the GUI to draw the rectangles. In the GameEngine, make the labels and colors the same colors that currently appear in the GUI. Much of this can be done with the renaming of fields and methods, but at some point you are going to have to change a String type to a Color and this will break things. Make sure you have an idea of what it will break before you do it. Then fix those items. Run the test and the GUI again to see if they still work.

As always, tidy up your code and make sure it is well-formatted.

Part 5 Modify RiverGUI

Were going to completely redesign RiverGUI so that the following are true:

  • There are no signs of beans, goose, wolf, or farmer anywhere!
  • In the View portion of the class, were going to map items to their new rectangles, and then well draw the rectangles.
  • In the Controller portion of the class, were going to check if a click occurred in an items rectangle, and handle it if it did.
  • Were going to use offsets and maps so that we can handle different items using the same method.

If you have a different idea for refactoring and updating the RiverGUI *and* youre very comfortable with Java, let me know on Piazza and I will let you try it. But consider working with the outline I give here since I tested it and I know it works (and I know *how* it works). First, lets look at some of the fields you are going to need.

private final Rectangle leftBoatRectangle = private final Rectangle rightBoatRectangle = private final Rectangle baseLeftRectangle = private final Rectangle baseRightRectangle = private final int[] dx = { .., .., .., .. };private final int[] dy = { .., .., .., .. };

private GameEngine engine; // Modelprivate Map<Item, Rectangle> rectMap;private Rectangle boatRectangle;

The left boat rectangle and right boat rectangle have not changed. The base left rectangle is the old left goose rectangle and the base right rectangle is the old right goose rectangle. Note that in the old GUI, the goose was always at the bottom left of the item group, while in this GUI, the beans will always be at the bottom left (we can change this depending on how we implement the game engine). We do not need the other rectangles because were going to use offsets. The dx and dy array represents the offsets of the four items. ITEM_0 is the bottom-left item, so its offset is 0, 0. ITEM_3 is the top-right item so its offset is 60, -60 (thats 60 pixels right along the x-axis and 60 pixel up along the y-axis). Were also going to change the way items are loaded onto the boat. We will only allow at most two items, but those items will be in their offset positions. This doesnt look good, but well do it this way for now and try to get things looking better later. The image below shows how offsets are used.

We also introduce a map field that maps items to rectangles. This is going to help us decide if a click event takes place inside an item or boat. The boatRectangle field will hold what you expect the rectangle for the boat.

The code below indicates how the view will change.

@Overridepublic void paintComponent(Graphics g) {g.setColor(Color.GRAY);g.fillRect(0, 0, this.getWidth(), this.getHeight());paintItem(g, Item.ITEM_0);paintItem(g, Item.ITEM_1);paintItem(g, Item.ITEM_2);paintItem(g, Item.ITEM_3);paintBoat(g);}

private void paintItem(Graphics g, Item item) { }private void paintBoat(Graphics g) { }private Rectangle getItemRectangle(Item item) {// associate the item with its new value (rectangle) in the mapreturn rectMap.get(item);}private Rectangle getBoatRectangle() {// set boatRectangle to its new valuereturn boatRectangle;}

private Rectangle getRectangleOffsetBy(Rectangle rect, int dx, int dy) { }

private void paintRectangle(Graphics g, Color color, String label, Rectangle rect) {// similar to paintStringInRectangle but now it creates// the rectangle with the specified color first// use rect.x, rect.y, rect.width, rect.height to get those values if needed}

The main method simply paints 4 items and the boat. To paint a rectangular object, you will need (1) the color, (2) the label, and (3) the rectangle. Color and label are easy to get from the game engine. For the rectangle of an item, you have to change where the item is (start, finish, or boat) and then determine what the rectangle is. Once you have the rectangle, use it to update the map. The rectangle in the map is what you should return. For the item rectangles, you will need to work with offsets, thats why we included the method getRectangleOffsetBy(). It is very important that when you implement this method the first thing you do is make a *copy* of the rectangle that is passed in using the copy constructor. Unfortunately (in my opinion), Java rectangles are *mutable*, which means its easy to inadvertently modify their values even if you have declared them to be *final*. The body of the paint-rectangle method can be fairly easily deduced from the paint-string-in-rectangle method from the original code.

Finally, because we mapped items to rectangles in the View, the Controller is not that difficult:

@Overridepublic void mouseClicked(MouseEvent e) {if (rectMap.get(Item.ITEM_0).contains(e.getPoint())) {respondToItemClick(Item.ITEM_0);} else if (rectMap.get(Item.ITEM_1).contains(e.getPoint())) {respondToItemClick(Item.ITEM_1);} else if (rectMap.get(Item.ITEM_2).contains(e.getPoint())) {respondToItemClick(Item.ITEM_2);} else if (rectMap.get(Item.ITEM_3).contains(e.getPoint())) {respondToItemClick(Item.ITEM_3);} else if (boatRectangle.contains(e.getPoint())) {engine.rowBoat();}repaint();}

The respond to item click method is fairly simple.

Part 6 The GameEngine Interface

  • Rename getCurrentLocation to getBoatLocation.
  • Rename GameEngine to FarmerGameEngine.
  • Extract a GameEngine interface from FarmerGameEngine. Select all public methods *except* the no-argument unloadBoat.
  • Rename your GameEngineTest to FarmerGameEngineTest.
  • Make sure your GameEngineTest and your RiverGUI classes *declare* GameEngines but initialize using FarmerGameEngines.
  • Make any changes necessary to get your tests and your application to work (like replacing occurrences of unloadBoat() with unloadBoat(Item)).
  • Take a look at the GameEngine interface below. Specifically, look at the Javadocs and ensure that your FarmerGameEngine class behaves as specified in the Javadocs.

package river; import java.awt.Color; public interface GameEngine { /** * Returns the label of the specified item. This method may be used by a GUI * (for example) to put the label string inside of a rectangle. A label is * typically one or two characters long. * * @param item the item with the desired label * @return the label of the specified item */ String getItemLabel(Item item); /** * Returns the color of the specified item. This method may be used by a GUI * (for example) to color a rectangle that represents the item. * * @param item the item with the desired color * @return the color of the specified item */ Color getItemColor(Item item); /** * Returns the location of the specified item. The location may be START, * FINISH, or BOAT. * * @param item the item with the desired location * @return the location of the specified item */ Location getItemLocation(Item item); /** * Returns the location of the boat. * * @return the location of the boat */ Location getBoatLocation(); /** * Loads the specified item onto the boat. Assuming that all the * required conditions are met, this method will change the location * of the specified item to BOAT. Typically, the following conditions * must be met: (1) the items location and the boats location * must be the same, and (2) there must be room on the boat for the * item. If any condition is not met, this method does nothing. * * @param item the item to load onto the boat */ void loadBoat(Item item); /** * Unloads the specified item from the boat. If the item is on the boat * (the items location is BOAT), then the items location is changed to * the boats location. If the item is not on the boat, then this method * does nothing. * * @param item the item to be unloaded */ void unloadBoat(Item item); /** * Rows the boat to the other shore. This method will only change the * location of the boat if the boat has a passenger that can drive the boat. */ void rowBoat(); /** * True when the location of all the game items is FINISH. * * @return true if all game items of a location of FINISH, false otherwise */ boolean gameIsWon(); /** * True when one or more implementation-specific conditions are met. * The conditions have to do with which items are on which side of the * river. If an item is in the boat, it is typically still considered * to be on the same side of the river as the boat. * * @return true when one or more game-specific conditions are met, false * otherwise */ boolean gameIsLost();}

The requirements of this interface may change some functionality. For example, now you can load 2 non-farmer items into the boat (but you cant row it since only the farmer can drive). Therefore, if you created a passenger field from a previous refactoring, remove it. Instead, create a method that checks how many items are in the boat. When you load an item into the boat, see if there are already two items in the boat. If there are, dont load the item.

At this stage of the project, your RiverGUI application should behave as mine does in this video: River-GUI-Relative-Positioning (Links to an external site.)Links to an external site.. The relative positioning of items is maintained not only on the left and right banks of the river, but also on the boat.

Reviews

There are no reviews yet.

Only logged in customers who have purchased this product may leave a review.

Shopping Cart
[Solved] River Crossing Project Code Solution[Solved] River Crossing Project Code Solution
$25