mirror of
https://codeberg.org/andyscott/ShipGame.git
synced 2024-09-07 21:08:07 -04:00
Added ShipGame.py
This commit is contained in:
parent
8045d82e4a
commit
547e47cfa7
1 changed files with 468 additions and 0 deletions
468
ShipGame.py
Normal file
468
ShipGame.py
Normal file
|
@ -0,0 +1,468 @@
|
|||
# Author: Andrew Scott
|
||||
# Date: 2022-03-04
|
||||
# Description: Allows two people to play the ShipGame. Each player has their own
|
||||
# 10x10 grid on which they place their ships. On their turn, each
|
||||
# player can fire a torpedo at a square on the enemy's grid. Player
|
||||
# 'first' gets the first turn to fire a torpedo, after which
|
||||
# players alternate. A ship is sunk when all of its squares have
|
||||
# been hit. When a player sinks their opponent's final ship, they
|
||||
# win.
|
||||
|
||||
class Ship():
|
||||
"""Represents a ship in the ShipGame."""
|
||||
|
||||
def __init__(self, player, length, coords, orientation):
|
||||
"""Constructor for the Ship class. All data members are private.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
player : obj
|
||||
The player object that owns the ship ('first' or 'second')
|
||||
length : int
|
||||
The length of the ship
|
||||
coords : str
|
||||
Coordinates of the square closest to A1 that the ship will occupy
|
||||
orientation : str
|
||||
'R' if the ships squares occupy the same row, or 'C' if its squares
|
||||
occupy the same column
|
||||
"""
|
||||
|
||||
self._player = player
|
||||
self._length = length
|
||||
self._coords = coords
|
||||
self._orientation = orientation
|
||||
self._squares = []
|
||||
self._hits = set()
|
||||
|
||||
if coords[0] == 'A':
|
||||
self._indices = '0' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'B':
|
||||
self._indices = '1' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'C':
|
||||
self._indices = '2' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'D':
|
||||
self._indices = '3' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'E':
|
||||
self._indices = '4' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'F':
|
||||
self._indices = '5' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'G':
|
||||
self._indices = '6' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'H':
|
||||
self._indices = '7' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'I':
|
||||
self._indices = '8' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'J':
|
||||
self._indices = '9' + str(int(coords[1]) - 1)
|
||||
|
||||
|
||||
def get_player(self):
|
||||
"""Get the player who owns the ship.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Either 'first' or 'second'
|
||||
"""
|
||||
|
||||
return self._player
|
||||
|
||||
def get_length(self):
|
||||
"""Get the ships length.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The length of the ship
|
||||
"""
|
||||
|
||||
return self._length
|
||||
|
||||
def get_indices(self):
|
||||
"""Get the square occupied by the ship closes to A1 as a 2-digit string.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
2-digit string representing the square the ship occupies on the grid
|
||||
closest to A1 (e.g 'A1' = '00')
|
||||
"""
|
||||
|
||||
return self._indices
|
||||
|
||||
def get_coords(self):
|
||||
"""Get the coordinates of the square occupied by the ship closest to A1.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The coordinates (e.g. 'B7')
|
||||
"""
|
||||
|
||||
return self._coords
|
||||
|
||||
def get_orientation(self):
|
||||
"""Get the ships orientation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
'R' if the ships squares occupy the same row, or 'C' if its squares
|
||||
occupy the same column
|
||||
"""
|
||||
|
||||
return self._orientation
|
||||
|
||||
def get_hits(self):
|
||||
"""Get the number of hits recorded against a ship.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The number of times the ships has been hit
|
||||
"""
|
||||
|
||||
return self._hits
|
||||
|
||||
def get_squares(self):
|
||||
"""Get the squares occupied by the ship.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
List of 2-digit strings representing squares occupied by the ship
|
||||
(e.g 'A1' = '00')
|
||||
"""
|
||||
|
||||
return self._squares
|
||||
|
||||
def set_squares(self, squares):
|
||||
"""Set the squares occupied by the ship.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
squares : list
|
||||
List of 2-digit strings representing squares occupied by the ship
|
||||
(e.g 'A1' = '00')
|
||||
"""
|
||||
|
||||
self._squares = squares
|
||||
|
||||
def add_hit(self, index):
|
||||
"""Records when the ship is hit. A hit only counts once per square.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
index : str
|
||||
2-digit string representing the square on the grid to add
|
||||
(e.g coordinate 'A1' = index '00')
|
||||
"""
|
||||
|
||||
self._hits.add(index)
|
||||
|
||||
def is_sunk(self):
|
||||
"""Checks if the ship was sunk.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the ship was sunk, otherwise False
|
||||
"""
|
||||
|
||||
if len(self._squares) == len(self._hits):
|
||||
return True
|
||||
return False
|
||||
|
||||
class Board():
|
||||
"""Represents each players board for the ShipGame class."""
|
||||
|
||||
def __init__(self, player):
|
||||
"""Constructor for the Board class. All data members are private.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
player : str
|
||||
Either 'first' or 'second'
|
||||
"""
|
||||
|
||||
self._player = player
|
||||
self._rows = 10
|
||||
self._columns = 10
|
||||
self._grid = [[' ' for r in range(self._rows)] for c in range(self._columns)]
|
||||
|
||||
def render(self):
|
||||
"""Prints the current state of the board to the terminal"""
|
||||
|
||||
print('\n 1 2 3 4 5 6 7 8 9 10')
|
||||
|
||||
column_char = 'A'
|
||||
for column in self._grid:
|
||||
output = str()
|
||||
for item in column:
|
||||
output += item
|
||||
print(column_char, ' '.join(output))
|
||||
column_char = chr(ord(column_char) + 1)
|
||||
|
||||
def check_hit(self, index):
|
||||
"""Checks the grid for a hit.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
index : str
|
||||
2-digit string representing the square on the grid to check for a hit
|
||||
(e.g coordinate 'A1' = index '00')
|
||||
"""
|
||||
|
||||
try:
|
||||
location = self._grid[int(index[0])][int(index[1])]
|
||||
except:
|
||||
return False
|
||||
if location != ' ':
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_valid(self, ship):
|
||||
"""Determines whether a position requested for ship placement is valid.
|
||||
|
||||
Uses the current state of the board to determine whether or not the
|
||||
coordinates and orientation passed to the ShipGame's place_ship method
|
||||
represent a valid move.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ship : obj
|
||||
The ship that needs to be validated
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if coordinates are valid, otherwise false
|
||||
"""
|
||||
|
||||
length = ship.get_length()
|
||||
orientation = ship.get_orientation()
|
||||
location = ship.get_indices()
|
||||
column = int(location[0])
|
||||
row = int(location[1])
|
||||
|
||||
try:
|
||||
if orientation == 'R':
|
||||
for index in range(length):
|
||||
if self._grid[column][row] != ' ':
|
||||
return False
|
||||
row += 1
|
||||
return True
|
||||
if orientation == 'C':
|
||||
for index in range(length):
|
||||
if self._grid[column][row] != ' ':
|
||||
return False
|
||||
column += 1
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def add_ship(self, ship):
|
||||
"""Called by ShipGame's place_ship method to add a ship to the grid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ship : obj
|
||||
A Ship object
|
||||
|
||||
Returns
|
||||
-------
|
||||
ship_squares : list
|
||||
List of 2-digit strings representing squares occupied by the ship
|
||||
(e.g 'A1' = '00')
|
||||
"""
|
||||
|
||||
length = ship.get_length()
|
||||
orientation = ship.get_orientation()
|
||||
location = ship.get_indices()
|
||||
column = int(location[0])
|
||||
row = int(location[1])
|
||||
|
||||
if orientation == 'R':
|
||||
ship_squares = []
|
||||
for index in range(length):
|
||||
self._grid[column][row] = str(length)
|
||||
ship_squares.append(str(column) + str(row))
|
||||
row += 1
|
||||
return ship_squares
|
||||
if orientation == 'C':
|
||||
ship_squares = []
|
||||
for index in range(length):
|
||||
self._grid[column][row] = str(length)
|
||||
ship_squares.append(str(column) + str(row))
|
||||
column += 1
|
||||
return ship_squares
|
||||
|
||||
class ShipGame():
|
||||
"""Represents the game ShipGame. Uses Board and Player classes."""
|
||||
|
||||
def __init__(self):
|
||||
"""Constructor for the ShipGame class. All data members are private."""
|
||||
|
||||
self._player1 = 'first'
|
||||
self._player2 = 'second'
|
||||
self._board1 = Board(self._player1)
|
||||
self._board2 = Board(self._player2)
|
||||
self._player1_ships_dict = dict()
|
||||
self._player2_ships_dict = dict()
|
||||
self._current_state = 'UNFINISHED'
|
||||
self._current_turn = self._player1
|
||||
|
||||
def place_ship(self, player, length, coords, orientation):
|
||||
"""Places a ship on the player's board.
|
||||
|
||||
If a ship would not fit entirely on that player's board, if it would
|
||||
overlap any previously placed ships, or if the length of the ship is
|
||||
less than 2 the ship will not be added.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
player : str
|
||||
Either 'first' or 'second'
|
||||
length : int
|
||||
The length of the ship
|
||||
coords : str
|
||||
Coordinates of the square closest to A1 that the ship will occupy
|
||||
orientation : str
|
||||
'R' if the ships squares occupy the same row, or 'C' if its squares
|
||||
occupy the same column
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the ship is added, False if it cannot be added
|
||||
"""
|
||||
|
||||
if length < 2:
|
||||
return False
|
||||
if length > 10:
|
||||
return False
|
||||
|
||||
ship = Ship(player, length, coords, orientation)
|
||||
if player == 'first':
|
||||
board = self._board1
|
||||
if board.is_valid(ship):
|
||||
squares = board.add_ship(ship)
|
||||
self._player1_ships_dict[coords] = ship
|
||||
ship.set_squares(squares)
|
||||
return True
|
||||
return False
|
||||
if player == 'second':
|
||||
board = self._board2
|
||||
if board.is_valid(ship):
|
||||
squares = board.add_ship(ship)
|
||||
self._player2_ships_dict[coords] = ship
|
||||
ship.set_squares(squares)
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_current_state(self):
|
||||
"""Get the current state of the game.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
'FIRST_WON', 'SECOND_WON', or 'UNFINISHED'
|
||||
"""
|
||||
|
||||
return self._current_state
|
||||
|
||||
def fire_torpedo(self, player, coords):
|
||||
"""Fires a torpedo at the enemy ships.
|
||||
|
||||
Records the move, updates whose turn it is, and updates the current
|
||||
state of the game if the move resulted in a win.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
player : str
|
||||
Either 'first' or 'second'
|
||||
coords : str
|
||||
Coordinates of the target square (e.g. 'B7')
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
False when the game is already over or it's not the players turn,
|
||||
otherwise True
|
||||
"""
|
||||
|
||||
if self._current_state != 'UNFINISHED' or self._current_turn != player:
|
||||
return False
|
||||
|
||||
index = ''
|
||||
if coords[0] == 'A':
|
||||
index = '0' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'B':
|
||||
index = '1' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'C':
|
||||
index = '2' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'D':
|
||||
index = '3' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'E':
|
||||
index = '4' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'F':
|
||||
index = '5' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'G':
|
||||
index = '6' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'H':
|
||||
index = '7' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'I':
|
||||
index = '8' + str(int(coords[1]) - 1)
|
||||
elif coords[0] == 'J':
|
||||
index = '9' + str(int(coords[1]) - 1)
|
||||
|
||||
if player == 'first':
|
||||
board = self._board2
|
||||
ships_dict = self._player2_ships_dict
|
||||
if board.check_hit(index):
|
||||
for key in ships_dict:
|
||||
ship = ships_dict[key]
|
||||
if index in ship.get_squares():
|
||||
ship.add_hit(index)
|
||||
if ship.is_sunk():
|
||||
del ships_dict[key]
|
||||
ships_remaining = self.get_num_ships_remaining('second')
|
||||
if ships_remaining == 0:
|
||||
self._current_state = 'FIRST_WON'
|
||||
return True
|
||||
self._current_turn = 'second'
|
||||
if player == 'second':
|
||||
board = self._board1
|
||||
ships_dict = self._player1_ships_dict
|
||||
if board.check_hit(index):
|
||||
for key in ships_dict:
|
||||
ship = ships_dict[key]
|
||||
if index in ship.get_squares():
|
||||
ship.add_hit(index)
|
||||
if ship.is_sunk():
|
||||
del ships_dict[key]
|
||||
ships_remaining = self.get_num_ships_remaining('first')
|
||||
if ships_remaining == 0:
|
||||
self._current_state = 'SECOND_WON'
|
||||
return True
|
||||
self._current_turn = 'first'
|
||||
return True
|
||||
|
||||
def get_num_ships_remaining(self, player):
|
||||
"""Get how many ships a player has left.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
player : str
|
||||
Either 'first' or 'second'
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
How many ships the specified player has left
|
||||
"""
|
||||
|
||||
if player == 'first':
|
||||
return len(self._player1_ships_dict.keys())
|
||||
if player == 'second':
|
||||
return len(self._player2_ships_dict.keys())
|
Loading…
Reference in a new issue