CIT 590 Assignment 6: Battleship
Spring 2013, David Matuszek

Purposes of this assignment

General idea of the assignment

This assignment is based on a game, since games are a good source of relatively simple problems.

The Ocean (10x10)
                   
                   
                   
                   
                   
                   
                   
                   
                   
                   
The Fleet
One battleship
S
S
S
S
Two cruisers
S
S
S
   
S
S
S
Three destroyers
S
S
   
S
S
   
S
S
Four submarines
S
   
S
   
S
   
S

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

The classes

Your program should have the following classes:

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 extends Ship
class Cruiser extends Ship
class Destroyer extends Ship
class Submarine extends Ship

Each of these classes has the following instance variables 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 dictionary 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: 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

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.