or this program you will write a game that is played between the computer and the user. The computer will hide ships on a virtual sea and then allow the user to pick targets on the sea to attempt to destroy each ship. The sea will be a two-dimensional array of Ship objects and will be implemented in the Sea class, as detailed below. The requirements for the Ship class are detailed below. This two-dimensional array will be 10 by 10 and all elements of this array will automatically be set to null. Note: the bold numbers in this document indicate values that should be named constants in your program.
This Java file will contain the static application class containing main and any help methods needed. The main method in this class will begin the game, but you should plan to divide the work of this client into a number of static helper methods within the ShipSinkingGame class. The data shared among the static methods in this class should all be via parameters passed. There should be NO variables declared at the class level; ONLY named constants and the Random object (for generating random numbers) should be defined at the class level. As you read through the following requirements, you should refer to the example execution logs provided on Canvas.
This application should first ask the user which mode to use in playing the game. If the user answers 'y' or Y, then the game enters play mode, otherwise it enters cheat mode. In play mode, the sea appears as a 10 X 10 grid of dots. Later in the game, when a shot is fired by the user, the grid will be redisplayed to show an x where a shot missed all ships and an asterisk (*) where a ship was hit. In cheat mode, the game shows the exact position of each ship on the sea. In the execution logs showing cheat mode, you can see that each ship has a unique identification letter, the first ship is A, the second is B, then C, etc. (NOTE: The last identification letter used should match the number of ships being used, as defined by the named constant for that purpose. For example, if 8 ships are created, their id letters must be A through H.) In the logs you will also see that even in cheat mode the xs and asterisks are displayed showing misses and hits respectively.
Before the main loop begins, the application must create the fleet of ships and place them on the sea. Each ship is an object of the class, Ship, which is detailed below. The sea is an object of the Sea class, also detailed below. In creating the fleet, the application will create 8 ships giving them random sizes and positions on the sea. (NOTE: This value, 8, should be a named constant so that the user may change it and recompile the app to try fewer or more ships than 8.) The ships may be size 2, 3, or 4. The application must select the sizes so that there is 20% chance of a size 2 ship being created, 30% chance of a ship being size 3, and a 50% chance a ship will be size 4. Next, a random position must be chosen and a random orientation. The orientation is either vertical or horizontal. If a ship is oriented vertically, then the first cell occupied by the ship has the randomly chosen (r, c) coordinates - note that, r is the row and c is the column. If a vertical ship is size 2, then the next cell it occupies is at (r+1, c), i.e., the cell immediately below the first cell. If a ship is size 3, then the third cell occupied is (r+2, c); finally, (r+3, c) if a ship is size 4. When a ship is oriented horizontally, the first cell occupied by that ship is (r, c), the next is (r, c+1), then (r, c+2) and (r, c+3) for sizes 2, 3 and 4, respectively. Here is an example of several ships on the virtual sea: see image.
For each ship shown, the randomly selected position for that ship shows the identification character in bold. If the ship is horizontal, it also occupies cells to the right of the first randomly selected cell. If the ship is vertical, then it also occupies cells on the rows below the first randomly selected cell. So, in the diagram above, when a random position was selected for ship A, the row selected was 1 and the column was also 1. Since the orientation for ship A is vertical, the next position occupied by this size 2 ship is on the next row, row 2, and in the same column, column 1. The diagram also shows several other example ships.
The main loop of this application will display the sea (as shown in the logs, including the row numbers at the left and the column numbers above each column - these help the user see what row and column values to enter), accept two coordinates (NOTE: row is ALWAYS entered FIRST, column is entered SECOND, BOTH values must be verified as always being in the range of 0 through 9) indicating the target cell for the next shot, then report one of the possible outcomes:
1) no ship was hit,
2) a ship was hit, or
3) a ship was destroyed.
When a ship is hit, a message just before the report states which ship was hit and how many times it has now been hit. See logs for examples.
This main loop continues until A) all ships have been completely destroyed or B) the user enters 'q' or Q instead of either of the coordinates to terminate the program early or C) the user fires 12 missed shots. The game termination conditions:
A. Once all ships have been destroyed, a termination message is displayed along with the total shots that were taken and then the program ends.
B. If the letter, q or Q, is entered instead of either coordinate, then the program terminates early by first displaying the sea in cheat mode and then printing a message indicating how many total shots were used. See log of early termination. Example of early termination message:
Quitting early! 15 shots were used.
C. As soon as the user has accumulated 12 misses, the game will terminate with the user having lost. The number of misses and the total number of shots fired are both reported. For example:
Sorry, you have accumulated 12 misses out of 23 shots: You lost!
See logs for terminations because of too many misses. The value, 12, must be a named constant that the user could change to play with more or fewer misses.
Objects of this class will populate the sea created by the client. Each Ship object has the following attributes (private fields or instance variables). Each Ship will need integers for the two coordinates of its location (row and column), one integer for its size, and one for the number of times it has been hit. Each Ship will need a boolean to indicate its orientation (vertical or horizontal) and a char to store its identifying character (A..G - when the client creates 8 Ships).
Constructor: only one constructor is needed. This constructor should accept the two integer coordinates, the Ship's orientation (boolean), and the size (integer). This constructor should use the char value of a class variable to set the value of the current Ship objects identification character, then increment this class variable so that no other Ship has the same identity. NOTE: For 8 ships, the letters used should be A through H, no letters should be skipped. If the named constant for the number of ships were changed to two, then only A and B would be used as identifiers. In other words, do not create a ship unless you know beforehand that it will fit on the sea. If you create the ships first and then check to see if they fit on the sea, you will wind up using more letters than the number of ships.
The client will not need mutators for any of the fields except for the hit count. The client will probably need accessors for all of the private fields.
A toString method will be useful for returning a String containing a space followed by the id-character for a Ship object.
The application will create only one instance of this class. The object of this class will have the responsibility of populating and maintaining the sea, that is, the two-dimensional array of Ship objects. Since this two-dimensional array will be private data within the Sea object, clients (the application and the Ship objects) will have to call methods in this class for purposes such as: placing a ship on the sea, finding out what ship object. if any, is in a particular spot on the sea (cell in the array), removing part or all of a ship from the sea, determining whether a cell can be occupied by a ship being created, etc. Basically, any task that involves reading or writing an element of the two-dimensional array of Ship objects must be a task implemented within this class.
A toString method or something similar will be useful for returning a String that the client can display whenever there is a need to display the sea in either play or cheat mode.
When you turn in this project, you want to turn in something that runs even if you have trouble making the program meet all the requirements. To that end, follow the guidelines below.
First, begin by writing the Ship class. Use a temporary main method in the Ship class to create a single Ship object. Call each accessor method and display the return values to ensure that the ship's fields are correct. Call the method used to increment the Ships hit count and access it to be sure it is incremented correctly. Also, create several Ships and make sure that each one has a unique identifying character by printing the ship objects - this will also test the toString method in this class.
Second, implement the Sea class with a constructor. Implement a method to accept a Ship object and place it on the Sea correctly. Test this class using a temporary main method that creates the Sea object, creates a Ship object, places it on the sea, and then displays the Sea using the toString method. As you think of other methods that this class will need, implement them and test them using this main method.
Next, begin the client class in the file, ShipSinkingGame.java, by having the main method in the client create a Sea object. Display the welcome message and then get the user's input regarding the mode in which to play the game. Then, you should create the fleet of ships. This means that you create all the ships needed, one at a time, and then place them on the sea, one at a time.
To do this, begin by creating only one ship. Then, write the necessary methods to validate that the ship fits into the sea. Validate that the random parameters for the new Ship will work on the Sea BEFORE you actually create the Ship object. This involves using logic to check that the new ship will not hang off the bottom of the grid (if the ship is vertical) or hang off the right (if the ship is horizontal). The logic that ensures that the new ship fits should also make sure that the cells that hold references to the new Ship are not already occupied, i.e., not null. Note that it will be easier to verify that the new Ship object fits on the sea BEFORE actually creating that object. Use the random position and the other attributes of this new Ship to determine whether it will fit. If the Ship will fit, then create that Ship and then have it placed on the sea. Some helper methods in accomplishing this verification will be static and located in the client. However, any task that entails accessing the elements of the two-dimensional array of Ship objects will need to be part of the Sea object. Of course, the Ship constructor will be used to actually create the new object. Once you know the new Ship will fit using the attributes you verified, use the Ship constructor to create the new object, then call a method in the Sea class to place that ship on the sea by assigning that Ship reference to every cell occupied by that Ship.
As the next step, write code to display the sea. This will need to be mostly set in the Sea class. Create the logic to display the sea in CHEAT mode so that you can see on the console exactly where your Ship object is. Once this is working, have your program begin creating and then displaying two Ship objects on the sea. Run this often to see that the two Ship objects are appearing in different, random positions each time.
Only after the two Ship objects are appearing according to the requirements should you move toward creating more Ships until you can create the total number of Ship objects that are required.
Once you can create Ship objects, place them on the sea, and display the grid showing their positions accurately, then (and ONLY THEN) should you plan to accept the coordinates from the user for shots fired at the Ship objects. This process begins by prompting the user and making sure that an integer is entered AND that it is in the proper range of the grid's coordinates. Note: the user CANNOT crash this program. THEREFORE, make sure you defend against the user entering input that is not an integer or is an integer that is not in the correct range.
After you can read coordinates, plan the method that will assess the damage caused by the shot hitting the cell targeted by those coordinates. The shot will either miss all ships, hit a part of one ship, or complete the destruction of a ship. Your method to do this assessment will need to modify the grid to reflect the results. This will involve being able to cause the display to show a dot, an x, or an asterisk in the cells where the is no Ship object, and show the Ship's identifying character in cells where there is a Ship. Using a parallel two-dimensional array of char can help with this task.
Now that you can create and place Ships as well as fire shots and assess the damage you are ready to have the main loop repeat the game steps until all ships are destroyed. When the loop exits, show the user how many shots it took them to destroy all the ships.