Short Exercises #4¶
Due: Friday, Nov 5th at 4:30pm CDT (Note the non-standard due date)
The following short exercises are intended to help you practice some of the programming concepts introduced in the first four weeks of the quarter. These exercises should not take more than 1-2 hours in total to complete.
Fetching the instructor files¶
To get the files for this set of short exercises, first set the
GITHUB_USERNAME
environment variable by running the following
command at the Linux command line (replacing replace_me
with your
GitHub username):
GITHUB_USERNAME=replace_me
(remember you can double-check whether the variable is properly set by
running echo $GITHUB_USERNAME
)
Then navigate to your Short Exercises repository and pull
the new material:
cd ~/cmsc12100
cd short-exercises-$GITHUB_USERNAME
git pull upstream main
You will find the files you need in the se4
directory.
IMPORTANT: If you are unable to obtain the instructor files by running the commands above do not try to add the files in some other way. Doing so will likely prevent you from submitting your code. Instead, please seek assistance on Ed Discussion or at office hours.
Testing¶
As usual, you will want to test your solution manually before you try the automated tests. (See below for examples.) Remember to set up auto reload before you start testing.
$ ipython3
In [1]: %load_ext autoreload
In [2]: %autoreload 2
In [3]: import se4
A Game¶
Battleship is a strategy game normally played by two people. Each player lays out a fleet of ships on a board (a 10x10 grid) that is hidden from the other player, and then the players take turns calling shots in an attempt to sink each other’s ships. The game ends when one player has no ships left on the board (that is, they’ve all been sunk).
In this set of short exercises, you are going to implement several methods for managing a player’s board (that is, the grid) for a simplified version of this game.
Initially, the every cell in the board will contain water (represented
as "Water"
). Using just the first letter to represent the values
in the cells, the board would look like this:
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
After creating the initial board, a player can deploy up to five ships drawn from the following list of ships: Battleship, Carrier, Destroyer, Patrol Boat, and Submarine. Each ship occupies a certain number of cell in the board:
Battleship: 4
Carrier: 5
Destroyer: 3
Patrol Boat: 2
Submarine: 3
In our version of the game, ships can only be oriented horizontally
on the board. (In the board game Battleship, ships can be oriented
horizontally and vertically.) To deploy a ship, the user specifies a
ship and a starting grid cell. For example, let’s say we start from
an empty board and deploy a Destroyer at location (3, 2)
. The
resulting board would be: (D
stands for for Destroyer):
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W D D D W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
The user will specify a fleet using a dictionary. Here’s a fleet that contains four ships:
{'Carrier': (0, 4),
'Battleship': (1, 0),
'Destroyer': (3, 2),
'Patrol Boat': (9, 8)},
The Carrier will occupy grid spots (0, 4)
, (0, 5)
, (0, 6)
,
(0, 7)
, and (0, 8)
because it starts at (0, 4)
and has
width 5. The Battleship will occupy grid spots (1, 0)
through
(1, 3)
and so, on. Here’s the board after these ships have been
deployed (again using the first letter of the string to represent a
given value, B
for "Battleship"
etc.
W W W W C C C C C W
B B B B W W W W W W
W W W W W W W W W W
W W D D D W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W S S S W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W P P
We will refer to the part of a ship in a specific grid cell as a
fragment. So, in the figure above there are fragments of the Patrol
Boat in locations (9, 8)
and (9, 9)
, for example.
Once the ships have been deployed, the game can start. In our game, a single player (who we will call the shooter) will attempt to sink the ships on a board. The shooter will call out shot locations. For example, they might choose location (0, 3). This shot would land in the water and so, the board’s owner would reply with Miss. If the shooter called out (0, 4), the board’s owner would reply with Hit. The shot hit a ship, but the ship has not yet sunk. The shooter might then call out (0, 5) (yields Hit), (0, 6) (yields Hit), (0, 7) (yields Hit), and finally, (0, 8) which yields Carrier to indicate the shooter has sunk the Carrier. In general, if the shooter hits the last fragment of the ship, then the result will be the name of the ship. If the player hits a ship fragment, but not the last one, the shot will yield Hit.
The game ends when there are no ships left on the board.
Representation¶
We will represent the board as a list of list of strings. The strings
will be one of "Water"
, 'Battleship'
, 'Carrier'
,
'Destroyer'
, 'Patrol Boat'
, 'Submarine'
, and 'Hit'
.
Locations will be represent by tuples.
Exercises¶
The Board
class encapsulates information about the 10 by 10
board and contains methods for interacting with the
board. In these exercises, you will implement the Board
class.
Important: The Game
class and our tests access attributes and
call methods of your Board
class. It is important that you use the
required names for attributes and methods.
Magic numbers: Near the top of se4.py
, you will see the global constant
SIZE = 10
. You should use this constant in your implementation to avoid the use
of the “magic number” 10 throughout your code. We’ve used it in Game
, too.
Exercise 1
Implement a Board
class with the following attributes:
board
(list of list of string): the 10 by 10 board, with all locations initially set to contain “Water”.num_ships
(int): the number of ships currently on the board. This value will be zero initially.
Here’s a sample use of this class:
In [7]: from se4 import Board
In [8]: b1 = Board()
In [9]: len(b1.board) # Number of rows
Out[9]: 10
In [10]: len(b1.board[0]) # Number of columns
Out[10]: 10
In [11]: b1.board
Out[11]:
[['Water',
'Water',
'Water',
'Water',
'Water',
'Water',
'Water',
'Water',
'Water',
'Water'],
... eight more rows containing "Water" ...
['Water',
'Water',
'Water',
'Water',
'Water',
'Water',
'Water',
'Water',
'Water',
'Water']]
In [12]: b1.num_ships Out[12]: 0
To run the automated tests for this exercise, run the command:
py.test -xvk constructor
Exercise 2
Implement the __str__
method. This method should return a string
representation of the board.
A row should be represented a string that contains the first letter
from each of the elements in the row separated by space. For example,
a row of all "Water"
would be represented with: 'W W W W W W W W W
W'
. (The string join
method will be very useful for this task.)
The rows in turn should be terminated by newlines "\n"
. The initial board
would yield the string: 'W W W W W W W W W W\nW W W W W W W W W W\nW
W W W W W W W W W\nW W W W W W W W W W\nW W W W W W W W W W\nW W W W W
W W W W W\nW W W W W W W W W W\nW W W W W W W W W W\nW W W W W W W W W
W\nW W W W W W W W W W\n'.
Printing this string yields:
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W W W
To run the automated tests for this exercise, run the command:
py.test -xvk str
Exercise 3
Implement the deploy_fleet
method. This method takes a dictionary
of that maps ships to locations (as shown above) and adds the ships to
the board (replacing the initial "Water"
values as needed). It
also updates the num_ships
attribute to reflect the number of
ships now on the board.
In se4.py
, we have included a constant SHIP_SIZES
that maps a
ship ("Carrier"
, for example) to its width.
You can assume that the input will be valid. The ships used in the
input will appear in SHIP_SIZES
and the ship will fit in the row
starting at the specified location (that is, it won’t run off the end
of the board).
Here’s a sample use of this method:
In [19]: b1 = se4.Board()
In [20]: fleet1 = {'Carrier': (0, 4),
'Battleship': (1, 0),
'Destroyer': (3, 2),
'Submarine': (6, 4),
'Patrol Boat': (9, 8)},
In [20]: b1.deploy_fleet(fleet1)
In [21]: print(b1)
W W W W C C C C C W
B B B B W W W W W W
W W W W W W W W W W
W W D D D W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W S S S W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W P P
In [22]: b1.num_ships
Out[22]: 5
To run the automated tests for this exercise, run the command:
py.test -xvk deploy
Exercise 4
Implement the method is_game_over
that will return
True
if there are no ships left on the board and False
otherwise.
In [19]: b1 = se4.Board()
In [20]: b1.deploy_fleet(fleet1) # deploy the full fleet
In [23]: b1.is_game_over()
Out[23]: False
In [24]: b2 = Board() # don't deploy any ships!
In [25]: b2.is_game_over()
Out[25]: True
To run the automated tests for this exercise, run the command:
py.test -xvk over
Exercise 5
Implement the method play_move
, which takes a shot location,
updates the board, if needed, and returns one of 'Hit'
,
'Miss'
, or the name of a ship depending on the success of the
shot.
The shot will be a 'Miss'
if the location contains "Water"
or
'Hit'
(from a previously successful shot.) In this case,
play_move
will just return 'Miss'
. It will not update the
board.
The shot will be a 'Hit'
if it contains a ship fragment. In this
case, play_move
should update the board to set the location to
'Hit'
and check if the cell contained the last piece of the ship.
If so, the ship has sunk and play_move
should return the name of
the ship. If there is cell with the ship’s name remaining in the row,
then play_move
should return 'Hit'
.
Here’s sample use:
In [19]: b1 = se4.Board()
In [20]: b1.deploy_fleet(fleet1) # deploy the full fleet
In [27]: b1.play_move((9, 7))
Out[27]: 'Miss'
In [42]: b1.play_move((9, 8))
Out[42]: 'Hit'
In [43]: print(b1)
W W W W C C C C C W
B B B B W W W W W W
W W W W W W W W W W
W W D D D W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W S S S W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W H P ### Notice that location (9, 8) has an H
In [44]: b1.play_move((9, 9))
Out[44]: 'Patrol Boat'
In [45]: print(b1)
W W W W C C C C C W
B B B B W W W W W W
W W W W W W W W W W
W W D D D W W W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W S S S W W W
W W W W W W W W W W
W W W W W W W W W W
W W W W W W W W H H
The first shot lands in water and so, the method returns 'Miss'
.
The second shot hits a patrol boat, but there are still parts of the
patrol boat on the board, so the method updates the board to replace
'Patrol Boat'
with 'Hit'
and returns 'Hit'
. The third
shot sinks the boat and, so the method updates the board to replace
'Patrol Boat'
with 'Hit'
and returns 'Patrol Boat'
.
To run the automated tests for this exercise, run the command:
py.test -xvk play
Playing the Game¶
We have included code to allow you to play against the computer. Our code randomly chooses a fleet and configuration for that fleet and then offers you the change to take shots. To run it, run:
python3 se4.py
Please note that this program won’t produce meaningful results until you are passing all the tests.
The program will ask you to enter a move, which you can specify as two integers separated by a single space. For example:
$ python3 se4.py
Ready to play?
Enter your move: 6 5
Miss
Enter your move: 7 4
Hit
Enter your move: 7 3
Hit
Enter your move: 7 2
Miss
Enter your move: 7 5
Destroyer
If you’d like to exit the game, just enter concede
instead of a move.
By the way, like all good games, our game includes a cheat code that
will reveal the board. We encourage you to read through our code to see
if you can figure out how to enter the cheat code.
Note
Wondering how we’re able to run a game by running python3 se4.py
?
Confused about the line if __name__ == "__main__":
in our code?
Now might be a good moment to read the textbook chapter on the
Basics of Code Organization,
which explains the difference between importing a Python file
and running a Python file.
Submitting your work¶
Once you’ve completed the exercises, you must submit your work through Gradescope (linked from our Canvas site). Gradescope will fetch your files directly from your GitHub repository, so it is important that you remember to commit and push your work!
To submit your work, go to the “Gradescope” section on our Canvas
site. Then, click on “Short Exercises #4”. Then, under “Repository”,
make sure to select your
uchicago-cmsc12100-aut-21/short-exercises-$GITHUB_USERNAME.git
repository.
Under “Branch”, just select “main”.
Finally, click on “Upload”. An autograder will run, and will report back a score. Please note that this autograder runs the exact same tests (and the exact same grading script) described in Testing Your Code. If there is a discrepancy between the tests when you run them on your computer, and when you submit your code to Gradescope, please let us know.
Your ESNU score on this set of exercises will be determined solely on the basis of these automated tests:
Grade |
Percent tests passed |
---|---|
Exemplary |
at least 95% |
Satisfactory |
at least 80% |
Needs Improvement |
at least 60% |
Ungradable |
less than 60% |
If there is a discrepancy between the tests when you run them on your computer, and when you submit your code to Gradescope, please let us know. Please remember that you can submit as many times as you want before the deadline. We will only look at your last submission, and the number of submissions you make has no bearing on your score.