Skip to content
This repository was archived by the owner on Apr 24, 2025. It is now read-only.

Commit 46a3469

Browse files
committed
feat(Battleship): added ship module
1 parent b634303 commit 46a3469

1 file changed

Lines changed: 136 additions & 0 deletions

File tree

projects/Battleship/ship.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
from typing import Set, Tuple, List
2+
from exceptions import InvalidHitMoveException, InvalidShipCoordinateException
3+
4+
5+
class Ship:
6+
def __init__(self, coordinates: List[Tuple[int, int]]):
7+
"""
8+
Represents a ship on the game board.
9+
10+
Attributes:
11+
coordinates (List[Tuple[int, int]]): The coordinates representing the position of the ship.
12+
"""
13+
14+
self.coordinates = coordinates
15+
self._check_ship_coordinates()
16+
17+
self._un_hit_coordinates: Set[Tuple[int, int]] = {tup for tup in self.coordinates}
18+
19+
@property
20+
def is_destroyed(self) -> bool:
21+
"""
22+
Checks if the ship is destroyed.
23+
24+
Returns:
25+
bool: True if the ship is destroyed, False otherwise.
26+
"""
27+
return len(self._un_hit_coordinates) == 0
28+
29+
@property
30+
def num_hits(self) -> int:
31+
"""
32+
Returns the number of hits on the ship.
33+
34+
Returns:
35+
int: Number of hits on the ship.
36+
"""
37+
return len(self.hit_cells)
38+
39+
@property
40+
def hit_cells(self) -> List[Tuple[int, int]]:
41+
"""
42+
Returns the coordinates of the hit cells on the ship.
43+
44+
Returns:
45+
List[Tuple[int, int]]: List of tuples representing the coordinates of the hit cells.
46+
"""
47+
return list(set(self.coordinates) - self._un_hit_coordinates)
48+
49+
@property
50+
def un_hit_cells(self) -> List[Tuple[int, int]]:
51+
"""
52+
Returns the coordinates of the un-hit cells on the ship.
53+
54+
Returns:
55+
List[Tuple[int, int]]: List of tuples representing the coordinates of the un-hit cells.
56+
"""
57+
return list(self._un_hit_coordinates)
58+
59+
def hit_ship(self, row, col) -> None:
60+
"""
61+
Marks the specified cell on the ship as hit.
62+
63+
Args:
64+
row (int): The row index of the cell to be hit.
65+
col (int): The column index of the cell to be hit.
66+
67+
Raises:
68+
InvalidHitMoveException: If the specified cell is already hit or if the ship is already destroyed.
69+
"""
70+
if self.is_destroyed:
71+
raise InvalidHitMoveException("The ship is already destroyed.")
72+
if (row, col) not in self._un_hit_coordinates:
73+
raise InvalidHitMoveException(f"{(row, col)} is an invalid hit move coordinate.")
74+
75+
self._un_hit_coordinates.discard((row, col))
76+
77+
def is_ship_overlap(self, other) -> bool:
78+
"""
79+
Checks if the ship overlaps with another ship.
80+
81+
Args:
82+
other (Ship): The other ship to check for overlap.
83+
84+
Returns:
85+
bool: True if there is an overlap, False otherwise.
86+
87+
Raises:
88+
ValueError: If the given ship is not of the same type as this ship.
89+
"""
90+
if type(other) is not self.__class__:
91+
raise ValueError(f"The given ship is not of type `{self.__class__.__name__}`")
92+
93+
return any([tup in other.coordinates for tup in self.coordinates])
94+
95+
def _check_ship_coordinates(self) -> None:
96+
"""
97+
Checks if the ship's coordinates are valid.
98+
99+
Raises:
100+
InvalidShipCoordinateException: If the ship's coordinates are invalid.
101+
"""
102+
rows = sorted([tup[0] for tup in self.coordinates])
103+
columns = sorted([tup[1] for tup in self.coordinates])
104+
105+
# Check if there are given coordinates for ship's position
106+
if len(self.coordinates) < 1:
107+
raise InvalidShipCoordinateException("Cannot instantiate a ship without coordinates for it's position.")
108+
109+
# Check if any of rows or columns are less than 0
110+
if any(row < 0 for row in rows):
111+
raise InvalidShipCoordinateException("One of the ship's coordinates have negative row value.")
112+
if any(col < 0 for col in columns):
113+
raise InvalidShipCoordinateException("One of the ship's coordinates have negative column value.")
114+
115+
if len(self.coordinates) > 1:
116+
# Check if neither the rows nor columns have constant value
117+
is_row_constant = not any([rows[j] != rows[j + 1] for j in range(len(rows) - 1)])
118+
is_col_constant = not any([columns[i] != columns[i + 1] for i in range(len(columns) - 1)])
119+
120+
if any([rows[j] != rows[j + 1] for j in range(len(rows) - 1)]) and any(
121+
[columns[i] != columns[i + 1] for i in range(len(columns) - 1)]):
122+
raise InvalidShipCoordinateException("Neither the rows or columns have constant value.")
123+
124+
# Check if there's a duplicate tuple in the coordinates
125+
if len(self.coordinates) != len(set(self.coordinates)):
126+
raise InvalidShipCoordinateException("There is a duplicate in one of the ship's coordinates.")
127+
128+
# Check if the non-constant row/column is in consecutive order
129+
if is_row_constant:
130+
# The columns must be consecutive
131+
assert all([columns[idx] + 1 == columns[idx + 1] for idx in
132+
range(len(columns) - 1)]), "The columns are not in consecutive order."
133+
if is_col_constant:
134+
# The rows must be consecutive
135+
assert all([rows[idx] + 1 == rows[idx + 1] for idx in
136+
range(len(rows) - 1)]), "The rows are not in consecutive order."

0 commit comments

Comments
 (0)