# You must make sure to run all cells in sequence using shift + enter or you might encounter errors
from pykubegrader.initialize import initialize_assignment
responses = initialize_assignment("Lab_4_Battleship", "week_4", "lab", assignment_points = 17.0, assignment_tag = 'week4-lab')
# Initialize Otter
import otter
grader = otter.Notebook("Lab_4_Battleship.ipynb")
๐งช ๐ฎLAB 4 : BATTLESHIP๐ฎ#
Complete the following 4 functions and corresponding function calls which facilitate playing battleship in the Jupyter console.
๐พ 1: Set up game board ๐ฟ#
Add the following doc string to
initialize_board()
. It goes between the pair of"""
at the top of the function (replacing...
).
"""
Initialize a game board for tracking ships or shots.
:param size: The size of the game board (size x size).
:return: A grid filled with '"๐ฆ"' to represent water.
"""
None of the next functions are called in the cells in which they are defined by default. You may add calls to run the functions inside their cells if you like, or use the ones in 5: Play the game below.
initialize_board()
is given to you in its entirety (minus the doc string above). The operations it performs use a for loop to execute a program designed to generate data for a game board.
We set up an object called board
which is populated with an empty list by board = []
. This gives us something to which we can add symbols representing water.
The for loop in initialize_board()
uses an iterator _
generated by range(size)
, an iterable that produces values from the functionโs input size
. The _
acts as a placeholder. _
will automatically be replaced with "๐ฆ"
as two lines inside the for loop are executed.
Each time the loop runs, a row of "๐ฆ"
symbols is created and appended to the object board
. The function returns the complete size
ร size
grid, called board
.
def initialize_board(size):
#doc string goes below here, replacing ...
"""
...
"""
board = []
for _ in range(size):
row = ["๐ฆ"] * size
board.append(row)
return board
grader.check("task1-initialize_board")
Question 2 (Points: 4.0): Question 1 (Points: 2.0): ๐ข2: Place ships on board๐ข#
Now that we have a game board filled with "๐ฆ"
to represent water, we want to replace some of those "๐ฆ"
with "๐ข"
to represent ships placed by a player.
1: Extract row and column indices from coordinates#
To iterate through the list of ship coordinates
, we want to unpack each tupleโs values to two variables, row
and col
. These variables will represent the row and column indices for each part of the shipโs placement.
Write a for loop around
coordinates
to replace the first...
belowThe for loop should use two iterators
row, col
, generated by an iterablecoordinates
, which is made of a list of tuples containing row and column coordinates for the desired ship location
Example:
If [(1, 2), (3, 4)]
is input to place_ship(coordinates)
:
On the 1st iteration:
row
will be assigned1
andcol
will be assigned2
On the 2nd iteration:
row
will be assigned3
andcol
will be assigned4
2: Adjust row and column for pythonโs indexing#
We want our game to accept human input for ship placement (1-based). This means if a person wants to place part of a ship in the upper leftmost element they should input (1,1). Python, by default, uses 0-based input, so any time that we accept human input we need to subtract 1 from each component to correspond to correct placement on the board.
Inside the for loop, do the following to replace ...
:
Use the compound assignment operator
-=
to subtract1
fromrow
and update its valueDo the same for
col
on the next line (still inside the for loop)
3: Place the ship on the board#
Now that row
and col
have been adjusted for Pythonโs indexing, the last line inside the for loop, board[row][col] = "๐ข"
looks into the 2D list board
at an index specified by [row][col]
and replaces whatever is there with "๐ข"
.
Since the board starts out as all water after running initialize_board()
, that means the existing "๐ฆ"
gets replaced wite "๐ข"
to represent part of a ship.
def place_ship(board, coordinates):
"""
Place ships on the board.
:param board: The game board (list of list of str).
:param coordinates: List of (row, col) tuples for ship positions (1-based indexing).
"""
# Add a for loop below this line
...
# Convert user input to 0-based index to make Python happy
...
board[row][col] = "๐ข"
grader.check("task2-place_ship")
Question 3 (Points: 6.0): ๐ฆ3: Display board๐ฆ#
Now that we have a function to generate data for board
and another function to place ships on board
, we want to display board
in the console. If we tried to directly print board
like this:
attempt_to_display_board = (initialize_board(5))
print(attempt_to_display_board)
the result would be:
[['๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ'], ['๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ'], ['๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ'], ['๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ'], ['๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ']]
That doesnโt look like a board at all! We want to remove all the extra characters like [
, ,
, and '
. We also want to make sure the board displays as a grid with rows stacked evenly on top of each other instead of presented end-to-end in one row.
To accomplish this goal, we will use the display_board()
function below.
1: Set up a for loop to access row
data#
We want to iterate though every row
in board
and keep only the ๐ฆ
for display. To do this, we can use the built-in Python string method "".join()
inside a for loop.
Write a for loop around the
board
to replace the first...
belowThe for loop should use one iterator
row
, generated by the iterableboard
The 1st time the loop runs, row
will be equal to the entire first row of board
.
The 2nd time the loops runs, row
will be equal to the entire 2nd row of board
.
This still includes the [
, ,
, and '
symbols.
2: Remove extra characters from display#
Inside the for loop:
Use the
"".join()
method to join all entries inrow
into one stringThe syntax is:
"".join(row)
""
is used to indicate what comes between the entries as they are joined โ in this case we want nothing, so leave it empty
Save the result to a variable
row_content
Now [
and ,
have been removed from the display.
On the next line, wrap
row_content
in a print statementprint(row_content)
print()
removes the'
or"
that would normally be wrapped around a string in the console
Now, if we were to run:
display_board(attempt_to_display_board))
the result would be:
๐ฆ๐ฆ๐ฆ๐ฆ๐ฆ
๐ฆ๐ฆ๐ฆ๐ฆ๐ฆ
๐ฆ๐ฆ๐ฆ๐ฆ๐ฆ
๐ฆ๐ฆ๐ฆ๐ฆ๐ฆ
๐ฆ๐ฆ๐ฆ๐ฆ๐ฆ
That looks like a good board, but because the ๐ฆ
are in such a close grid it may result in a grid illusion, which can cause eye strain and make it tough to quickly count rows and columns. Due to differences in how Mac and Windows display unicode characters, this may only occur when using a Windows operating system.
To fix this, we can adjust the spacing between rows and columns.
3: Optionally reduce eye strain#
Change
"".join()
to" ".join()
to add two spaces between๐ฆ
in each rowAdd a second print statement one line before the existing one to print an empty line between each row
The syntax is
print()
This part isnโt graded, so if you donโt find the dot illusion straining feel free to skip
def display_board(board, otter = False):
"""
Displays a 2D board in a visually appealing format.
:param board: The game board (list of list of str).
"""
#Add a for loop below this line
...
#Define row_content below this line
...
#Add print statement(s) below this line
...
#Do not edit below this line
if otter:
return row, row_content
grader.check("task3-display_board")
Question 4 (Points: 5.0): ๐ฅ4: Fire away๐ฅ#
Now we want to manipulate board
using indices provided from a tuple, target
, which will be input to a function, fire_shot()
.
1: Extract row and column from target#
We want to unpack the tuple, target
, which should always be input by players as (x,y) where x and y are two integers corresponding to a single space on board
. Since we know it will always be one tuple with two values inside we donโt need to use a for loop this time.
Define two variables,
row
andcol
, to store the two values contained intarget
.The syntax for this is
row,col = target
2: Adjust for human-friendly input#
To make our game user friendly we want target
to use 1-based indexing, just like we did for place_ship()
Use a compound assignment operator to decrease the value of
row
andcol
by 1.
3: Determine action based on current content of target#
Next comes the core implementation of logic in our game. We need to determine what happens when any given spot on board
is targeted.
We will do this by checking the current content of the index called by target
then deciding happens to that content based on getting hit by a shot. The first step of the logic if otter:
is just for grading (donโt change it!).
We want to apply the following game logic:
If
"๐ข"
is present we want to replace it with"๐ฅ"
to represent a hitWe also want to return the string
"Hit"
If
"๐ฆ"
is present we want to replace it with"๐ฆ"
to represent a missWe also want to return the string
"Miss"
If
"๐ฅ"
is present we want to keep it"๐ฅ"
We also want to return the string
"Already fired"
The input tuple shouldnโt be able to target anything else, so we finish the series of logical tests with an else:
statement that returns "Invalid Target"
Since we already initiated an if
statement to allow for grading, you can start with elif
for the game logic. Then, the final else
cleans up any other possible outcomes.
Replace the three sets of ...
to perform logical statements where indicated below
def fire_shot(board, target, otter = False):
"""
Fire a shot at the board and return the result.
:param board: The game board.
:param target: A tuple (row, col) where the shot is fired (1-based indexing).
:return: The result of the shot: "Hit", "Miss", or "Already Fired".
"""
# Unpack the tuple on the next line
...
# Convert user input to 0-based index to make Python happy
...
if otter:
return row,col
# Logical statement that should return "Hit"
...
# Logical statement that should return "Miss"
...
# Logical statement that should return "Already Targeted"
...
# Logical statement that should return "Invalid Target"
else:
return "Invalid Target"
grader.check("task4-fire_shot")
๐ฎ5: Play game๐ฎ#
The variable player_board
is populated using initialize_board(10)
to produce a 10 x 10 game board filled with water.
To play battleship, you should place 5 ships on your board. These ships are:
Carrier (occupies 5 spaces)
Battleship (occupies 4)
Submarine (occupies 3)
Destroyer (occupies 3)
Patrol boat (occupies 2)
The destroyer and patrol boat have already been placed for you with place_ship()
.
Keep your screen hidden from your opponent so they donโt know where your ships are!
Edit the placement of the destroyer and patrol boat so your opponent doesnโt know where they are
Or try to be sneaky and keep them in their default position as the one place your opponent wonโt check?
Add 3 new calls to
place_ship()
to place the remaining 3 shipsRun the cell to save your ship placement and view the board
You can run the cell as many times as you like before the game begins to help make sure youโre placing ships where you think youโre placing them, but donโt re-initialize the board once youโve started to play or youโll lose all progress.
Ships should be continuous and not overlap. They can run on diagonals.
# Initialize boards
player_board = initialize_board(10)
# Place destroyer and patrol boat on board
place_ship(player_board, [(2, 2), (2, 3), (2, 4)]) #destroyer
place_ship(player_board, [(7, 7), (8, 8)]) #patrol boat
# Place remaining 3 ships on board
# Display board
display_board(player_board)
Once you and your opponent have boards set up you can use the next cell to target spaces on their board (and vice versa). You can also use the tracking function below to record your shots on your opponentโs board (unless you can keep track of all that in your head?!)
Player 1 says: โ7,7โ
Player 2 runs: result =
fire_shot(player_board, (7, 7))
anddisplay_board(player_board)
Player 2 says: โHit!โ
Optional: Player 1 runs:
record_opponent_feedback(tracking_board)
and provides input to the box, then runsdisplay_board(tracking_board)
If your opponent hits all parts of a ship, you need to tell them which ship they sank.
# Fire shots
result = fire_shot(player_board, (7, 7))
print(result) # Should print "Hit" if ships are placed in default positions
# Display the updated board
display_board(player_board)
Track shots on your opponentโs board#
You want to make sure youโre not aiming at the same place twice.
Run the following cell to initialize an editable tracking board.
def record_opponent_feedback(tracking_board):
"""
Prompt the user for a target and result, then update the tracking board.
:param tracking_board: The board tracking your shots on the opponent's board.
"""
try:
# Get input for target coordinates as a tuple
target_input = input("row,col: ")
target = tuple(map(int, target_input.split(",")))
# Get input for result ("Hit" or "Miss")
result = input("hit or miss?: ").capitalize()
# Update the tracking board
row, col = target
row -= 1 # Convert to 0-based index
col -= 1 # Convert to 0-based index
if result == "Hit":
tracking_board[row][col] = "๐ฅ" # Mark hit
elif result == "Miss":
tracking_board[row][col] = "๐ฆ" # Mark miss
else:
print("Invalid result. Please enter 'hit' or 'miss'.")
except ValueError:
print("Invalid input. Please ensure coordinates are in the format row,col.")
tracking_board = initialize_board(size=10)
#Running this cell will prompt for input.
#First, tell your opponent your shot and input coordinates as a comma separated pair of integers, then hit enter.
#Input "hit" or "miss" (no quotes) depending on what your opponent says.
record_opponent_feedback(tracking_board)
display_board(tracking_board)
Submitting Assignment#
Please run the following block of code using shift + enter
to submit your assignment, you should see your score.
from pykubegrader.submit.submit_assignment import submit_assignment
submit_assignment("week4-lab", "Lab_4_Battleship")