CIT 590 Assignment 6: Battleship
Spring 2013, David Matuszek
Purposes of this assignment
- To give you some experience with classes and inheritance.
- To give you more experience with tuples, sets, and dictionaries.
General idea of the assignment
This assignment is based on a game, since games
are a good source of relatively simple problems.
|
The Fleet
| One battleship |
|
| Two cruisers |
|
| Three destroyers |
|
| Four submarines |
|
|
Battleship is usually a two-player game, where each player has a fleet and
an ocean (hidden from the other player), and tries to be the first to sink
the other player's fleet. We'll just do a solo version, where the computer
places the ships, and the human attempts to sink them. (Unfortunately, this makes the game much less fun to play.)
Pay careful attention to the types of information (sets, dictionaries, tuples) used in this assignment; where specified, these are requirements.
How to play
The computer places the ten ships on the ocean in such a way that
no ships are immediately adjacent to each other, either horizontally, vertically,
or diagonally. For example,
Legal arrangement |
Illegal--ships diagonally adjacent |
Illegal--ships horizontally adjacent |
| |
|
|
|
|
|
|
|
|
|
| |
S |
S |
S |
|
|
S |
|
|
|
| |
|
|
|
|
|
S |
|
|
S |
| S |
|
S |
S |
S |
|
|
|
|
S |
| |
|
|
|
|
|
|
|
|
|
| |
S |
S |
|
S |
|
|
S |
|
|
| |
|
|
|
|
|
|
|
|
S |
| |
|
|
|
|
|
|
|
|
S |
| |
|
|
|
|
|
|
|
|
S |
| S |
|
|
|
|
|
|
|
|
S |
|
| |
|
|
|
|
|
|
|
|
|
| |
S |
S |
S |
|
|
S |
|
|
|
| |
|
|
|
|
|
S |
|
|
S |
| S |
|
S |
S |
S |
|
|
|
|
S |
| |
|
|
|
|
|
|
|
|
|
| |
S |
S |
|
S |
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
S |
| |
|
|
|
|
|
|
|
|
S |
| |
|
|
|
|
|
|
|
|
S |
| S |
|
|
|
|
|
|
|
|
S |
|
| |
|
|
|
|
|
|
|
|
|
| |
S |
S |
S |
|
|
S |
|
|
|
| |
|
|
|
|
|
S |
|
|
S |
| S |
|
S |
S |
S |
|
|
|
|
S |
| |
|
|
|
|
|
|
|
|
|
| |
S |
S |
|
S |
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
S |
| |
|
|
|
|
|
|
|
|
S |
| |
|
|
|
|
|
|
|
|
S |
| S |
|
|
|
|
|
|
|
|
S |
|
The human player does not know where the ships are. The initial display of
the ocean shows a 10 by 10 array of locations, all the same.
The human player tries to hit the ships, by calling out a row and column number.
The computer responds with one bit of information--hit" or "miss." When a ship is hit but not sunk, the program does not provide any information about what kind of a ship was hit. However, when a ship is hit and sinks, the program prints out a message "You just sank a ship-type." After
each shot, the computer redisplays the ocean with the new information.
A ship is "sunk" when every square of the ship has been hit. Thus,
it takes four hits (in four different places) to sink a battleship, three to
sink a cruiser, two for a destroyer, and one for a submarine. The object is
to sink the fleet with as few shots as possible; the best possible score would
be 20. (Low scores are better.) When all ships have been sunk, the program
prints out a message that the game is over, tells how many shots were required, and prints an evaluation (for example, "You did great!" for an exceptionally small number of shots).
Details
Important note: In all of the above method descriptions I have omitted the initial self parameter. It's your job to remember that functions don't need it, methods do.
The modules
Your program should have the following modules (files):
battleship_game.py
battleship_game_tests.py
- This module runs all the unit tests.
- Other modules
- Everything should be in the two files listed above. You can use other modules while you are developing the program, if you like, but before you turn in the program, combine the code into the two required files.
The classes
Your program should have the following classes:
class BattleshipGame -- This is the "main" class, containing the main method. Interaction with the user occurs in this class.
class Ocean -- This contains a dictionary of Ships, representing the "ocean," and some methods to manipulate it.
class Ship -- This describes characteristics common to all the ships.
It has subclasses:
class Battleship extends Ship -- Describes a ship of length 4.
class Cruiser extends Ship -- Describes a ship of length 3.
class Destroyer extends Ship -- Describes a ship of length 2.
class Submarine extends Ship -- Describes a ship of length 1.
class BattleshipGame
The BattleshipGame class is the "main" class--that is,
it contains a main method. In this class you will set up the
game; accept "shots" from the user; display the results; print
final scores; and ask the user if he/she wants to play again. All input/output
is done here (although some of it is done by calling a print method in the Ocean class.) All significant computation will be done
in the Ocean class and the various Ship classes.
Use methods. Don't cram everything into one or two methods, but try to divide up the work into sensible parts with reasonable names.
class ShipTest
Test every method in the Ship class. TDD (Test-Driven Design is highly recommended.) Test the different types of ships.
Also test the methods in each subclass of Ship. You can do this here or in separate test classes, as you wish.
class Ship
Since we don't really care which end of a ship is the bow and which the stern, we will consider all ships to be facing up or left. Other parts of the ship are in higher-numbered rows or columns.
In the following, location always means a 2-tuple, (row, column), where 0 <= row <= 9 and 0 <= column <= 9.
- Instance variables
locations -- A set of locations occupied by the ship. Battleships occupy four locations, Cruisers 3, etc.
hits -- A set of locations on this ship that have been "hit;" hits is always a subset of locations.
- Methods:
ok_to_place_ship_at(row, column, horizontal, length, ocean)
- Returns
True if it is okay to put a ship of this length with its bow in this row and column, with the given orientation, and returns False otherwise. The ship must not overlap another ship, or touch another ship (vertically, horizontally, or diagonally), and it must not "stick out" beyond the array. Does not actually change either the ship or the Ocean, just says whether it is legal to do so. Note: row, column, and length should be integers; horizontal should be a boolean, and ocean should be an Ocean object, not just the map contained in the Ocean.
place_ship_at(row, column, horizontal, length, ocean)
- "Puts" the ship in the ocean. This involves giving values to the
locations instance variable in the ship, and it also involves putting a reference to the ship in each of 1 or more locations (up to 4) in the ships dictionary in the Ocean object. (Note: This will be as many as four identical references; you can't refer to a "part" of a ship, only to the whole ship.) Note: row, column, and length should be integers; horizontal should be a boolean, and ocean should be an Ocean object, not just the map contained in the Ocean.
shoot_at(location)
- If a part of this ship occupies the given row and column, and the ship hasn't been sunk, mark that part of the ship as "hit" (in the
hits set) and return True, otherwise return False. Notice that this method has the same name as a method in the Ocean class, and will be used by that method.
Programming note: This method expects a 2-tuple as a parameter. The call shoot_at(r, c) has two parameters, not a tuple parameter; but the call shoot_at((r, c)) works correctly. The same is true for other methods that expect a location as a parameter.
is_sunk()
- Returns
True if all locations of this Ship have been hit, and False otherwise. (Hint: It's easiest to compute this each time the method is called.)
class Battleship(Ship)
class Cruiser(Ship)
class Destroyer(Ship)
class Submarine(Ship)
Each of these classes has the following instance variables and methods:
ship_type
- Instance variable that holdsone of the strings
"battleship", "cruiser", "destroyer", or "submarine", as appropriate.
-
__init__()
- Sets the ship type, sets
hits to an empty set, and does any other necessary initialization.
get_ship_type()
- Returns one of the strings
"battleship", "cruiser", "destroyer", or "submarine", as appropriate.
__str__()
- Returns a single-character String to use in the
Ocean's print method (see below).
class OceanTest
This is a JUnit test class for Ocean. Test every required
method for Ocean, including the constructor, but not including
the print() method. If you create additonal methods in the Ocean class, write tests for them. Test methods do
not need comments, unless they do something non-obvious.
Experience has shown that it is a bad idea for one partner to write the
tests and the other partner to write the code. It works much better if
you write tests for your own code.
class Ocean
- Instance variables
ships-- A dictionary whose keys are locations and whose values are Ships. Used to quickly determine which ship is in any given location.
shots_fired -- The total number of shots fired by the user.
hit_count -- The number of times a shot hit a ship. If the user shoots the same part of a ship more than once, every hit is counted, even though the additional "hits" don't do the user any good.
number_of_ships_sunk -- The number of ships sunk (there are 10 ships in all).
__init__()
- The constructor. Initializes the ocean to contain all the ships (use the
place_all_ships_randomly method).
place_all_ships_randomly()
- Create all ten ships and place them randomly on the ocean. Place larger ships before smaller ones, or you may end up with no legal place to put a large ship. (Hint: use
random.randint).
is_occupied(location)
- Returns
True if the given location contains a ship, False if it does not.
is_open_sea(location)
- Returns
True if the given location neither contains nor is adjacent to some ship, False otherwise.
shoot_at(location)
- Returns
True if the given location contains a ship that is still afloat (before this shot), False if
it does not. In addition, this method updates the number of shots that have
been fired, and the number of hits.
Note: If a location
contains a "real" ship, shoot_at should return True every
time the user shoots
at that same location. Once a ship has been "sunk", additional shots at its location should return False.
get_shots_fired()
- Returns the number of shots fired (in this game).
get_hit_count()
- Returns the number of hits recorded (in this game). All hits are counted,
not just the first time a given square is hit.
get_ships_sunk()
- Returns the number of ships sunk (in this game).
game_is_over()
- Returns
True if all ten ships have been sunk, otherwise False.
get_ocean()
- Returns the dictionary of ships (not everything about this ocean). This is the methods in the
Ship class that take an Ocean parameter really need to be able to look at the contents of this array; the place_ship_at method even needs to modify it. While it is undesirable to allow methods in one class to directly access instance variables in another class, sometimes there is just no good alternative.
print()
- Prints the ocean. To aid the user, row numbers should be displayed along
the left edge of the array, and column numbers should be displayed along
the top. Numbers should be
0 to 9, not 1 to 10.
The top left corner square should be 0, 0. Use the following codes:
. (period) to indicate a location that has never been shot at.
- (hyphen) to indicate a location that has been shot at, but nothing was there.
* to indicate a location containing a ship (of unknown type) that has been hit but not yet sunk, and
B, C, D, or S to idicate a ship that has been sunk; the letter indicates the kind of ship it was.
Example: 0 1 2 3 4 5 6 7 8 9
---------------------
0 | . . . . . . . . . .
1 | . C C C . * - . . *
2 | . . . . . . . . . .
3 | . * . . * - . . . .
4 | . - . . . . . S . .
5 | . * . . . . . . . -
6 | . . - . . - . . . *
7 | . . . - . - . . . *
8 | . - . . . . . . . .
9 | . . . . . . . . . .
This is the only method in the Ocean class that does any input/output,
and it is never called from within the Ocean class (except possibly during
debugging), only from the BattleshipGame class.
You are welcome to write additional methods of your own. All methods that do computation should be tested.
Additional requirements
- Every method, except your test methods, should have docstrings.
- Every method should be tested, except for the test methods themselves, and the methods whose sole purpose is to do input or output.
- When the unit tests are run, no input should be required from the user, and the program should produce no output (other than the test results).
- Every method should be short enough to see all at once on
the screen.
Due date
Decide which of you will submit the assignment (only one assignment per team, please!) and submit it by 6am Friday February 22. Zip (don't use .rar) your .py files and submit the zipped file to Canvas. No other form of submission will be accepted.