Compare commits
2 Commits
fa8cfb2b5d
...
ee91d98383
Author | SHA1 | Date | |
---|---|---|---|
ee91d98383 | |||
fc813b6d5c |
@@ -55,13 +55,13 @@ True
|
|||||||
>>> Ship(5)
|
>>> Ship(5)
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValueError: 5 is not a valid Ship.ShipSize
|
ValueError: 5 is not a valid ShipSize
|
||||||
>>> Ship(1, 1)._tp, Ship(1, 2)._tp
|
>>> Ship(1, 1)._tp, Ship(1, 2)._tp
|
||||||
(1, 2)
|
(1, 2)
|
||||||
>>> Ship(1, 3)._tp
|
>>> Ship(1, 3)._tp
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValueError: 3 is not a valid Ship.ShipOrientation
|
ValueError: 3 is not a valid ShipOrientation
|
||||||
>>> s = Ship(1)
|
>>> s = Ship(1)
|
||||||
>>> {s[0] == 1, len(s) == 1}
|
>>> {s[0] == 1, len(s) == 1}
|
||||||
{True}
|
{True}
|
||||||
@@ -71,7 +71,7 @@ ValueError: 3 is not a valid Ship.ShipOrientation
|
|||||||
>>> s[0] = 4
|
>>> s[0] = 4
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValueError: 4 is not a valid Ship.DeckStatus
|
ValueError: 4 is not a valid DeckStatus
|
||||||
>>> s2 = Ship(2)
|
>>> s2 = Ship(2)
|
||||||
>>> s2._cells = [1, 2]
|
>>> s2._cells = [1, 2]
|
||||||
>>> s2._cells
|
>>> s2._cells
|
||||||
@@ -107,7 +107,7 @@ False
|
|||||||
>>> Ship(2, 1, 2, 3).is_collide(Ship(3, 2, 3, 2))
|
>>> Ship(2, 1, 2, 3).is_collide(Ship(3, 2, 3, 2))
|
||||||
True
|
True
|
||||||
>>> Ship(2, 1, 0, 0).is_collide(Ship(1, 2, 2, 2))
|
>>> Ship(2, 1, 0, 0).is_collide(Ship(1, 2, 2, 2))
|
||||||
True
|
False
|
||||||
>>> Ship(1).is_out_pole(10)
|
>>> Ship(1).is_out_pole(10)
|
||||||
False
|
False
|
||||||
>>> Ship(3, 1, 8, 1).is_out_pole(10)
|
>>> Ship(3, 1, 8, 1).is_out_pole(10)
|
||||||
@@ -115,15 +115,15 @@ True
|
|||||||
>>> Ship(3, 2, 1, 8).is_out_pole(10)
|
>>> Ship(3, 2, 1, 8).is_out_pole(10)
|
||||||
True
|
True
|
||||||
>>> s = Ship(4, 2)
|
>>> s = Ship(4, 2)
|
||||||
>>> s.try_move(1, 6, [Ship(1, 1, 3, 1)])
|
>>> s.try_move(1, 6, [Ship(1, 1, 1, 6)])
|
||||||
True
|
True
|
||||||
>>> s.get_start_coords()
|
>>> s.get_start_coords()
|
||||||
(0, 1)
|
(0, 1)
|
||||||
>>> s.try_move(1, 6, [Ship(1, 1, 3, 1)])
|
>>> s.try_move(1, 6, [Ship(1, 1, 1, 6)])
|
||||||
False
|
False
|
||||||
>>> s.get_start_coords()
|
>>> s.get_start_coords()
|
||||||
(0, 1)
|
(0, 1)
|
||||||
>>> s.try_move(1, 6, [Ship(1, 1, 2, 1)])
|
>>> s.try_move(1, 6, [Ship(1, 1, 1, 2)])
|
||||||
False
|
False
|
||||||
>>> s.get_start_coords()
|
>>> s.get_start_coords()
|
||||||
(0, 1)
|
(0, 1)
|
||||||
@@ -211,6 +211,26 @@ show() - отображение игрового поля в консоли (к
|
|||||||
|
|
||||||
get_pole() - получение текущего игрового поля в виде двумерного (вложенного) кортежа размерами size x size элементов.
|
get_pole() - получение текущего игрового поля в виде двумерного (вложенного) кортежа размерами size x size элементов.
|
||||||
|
|
||||||
|
>>> pole = GamePole(5)
|
||||||
|
>>> pole.get_pole()
|
||||||
|
((0, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0))
|
||||||
|
>>> pole.show()
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
0 0 0 0 0
|
||||||
|
>>> pole = GamePole(10)
|
||||||
|
>>> pole.init()
|
||||||
|
>>> sum(map(sum, pole.get_pole()))
|
||||||
|
20
|
||||||
|
|
||||||
|
>>> [*map(lambda x: x.__class__.__name__, pole.get_ships())]
|
||||||
|
['Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship']
|
||||||
|
>>> pole.move_ships()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Пример отображения игрового поля:
|
Пример отображения игрового поля:
|
||||||
0 0 1 0 1 1 1 0 0 0
|
0 0 1 0 1 1 1 0 0 0
|
||||||
1 0 0 0 0 0 0 0 0 0
|
1 0 0 0 0 0 0 0 0 0
|
||||||
@@ -242,7 +262,7 @@ P.S. Для самых преданных поклонников програм
|
|||||||
Сыграйте в эту игру и выиграйте у компьютера.
|
Сыграйте в эту игру и выиграйте у компьютера.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional, Tuple
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
@@ -261,6 +281,10 @@ class EnumOrdering(Enum):
|
|||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
return self.value < other
|
return self.value < other
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def values(cls):
|
||||||
|
return tuple(item.value for item in cls)
|
||||||
|
|
||||||
|
|
||||||
class IntEnumField:
|
class IntEnumField:
|
||||||
def __init__(self, enum_cls: Enum):
|
def __init__(self, enum_cls: Enum):
|
||||||
@@ -326,21 +350,24 @@ class NonNegativeIntField:
|
|||||||
setattr(instance, self.name, value)
|
setattr(instance, self.name, value)
|
||||||
|
|
||||||
|
|
||||||
|
class ShipSize(EnumOrdering):
|
||||||
|
ONE_DECK = 1
|
||||||
|
TWO_DECKS = 2
|
||||||
|
THREE_DECKS = 3
|
||||||
|
FOUR_DECKS = 4
|
||||||
|
|
||||||
|
|
||||||
|
class ShipOrientation(EnumOrdering):
|
||||||
|
HORIZONTAL = 1
|
||||||
|
VERTICAL = 2
|
||||||
|
|
||||||
|
|
||||||
|
class DeckStatus(EnumOrdering):
|
||||||
|
OK = 1
|
||||||
|
DAMAGED = 2
|
||||||
|
|
||||||
|
|
||||||
class Ship:
|
class Ship:
|
||||||
class ShipSize(EnumOrdering):
|
|
||||||
ONE_DECK = 1
|
|
||||||
TWO_DECKS = 2
|
|
||||||
THREE_DECKS = 3
|
|
||||||
FOUR_DECKS = 4
|
|
||||||
|
|
||||||
class ShipOrientation(EnumOrdering):
|
|
||||||
HORIZONTAL = 1
|
|
||||||
VERTICAL = 2
|
|
||||||
|
|
||||||
class DeckStatus(EnumOrdering):
|
|
||||||
OK = 1
|
|
||||||
DAMAGED = 2
|
|
||||||
|
|
||||||
pole_size: int = 10
|
pole_size: int = 10
|
||||||
|
|
||||||
_length: int = IntEnumField(ShipSize)
|
_length: int = IntEnumField(ShipSize)
|
||||||
@@ -357,10 +384,10 @@ class Ship:
|
|||||||
tp: int = 1,
|
tp: int = 1,
|
||||||
x: int = 0,
|
x: int = 0,
|
||||||
y: int = 0,
|
y: int = 0,
|
||||||
cells: Optional[list[DeckStatus]] = None,
|
cells: Optional[List[DeckStatus]] = None,
|
||||||
):
|
):
|
||||||
self._length, self._tp, self._x, self._y = length, tp, x, y
|
self._length, self._tp, self._x, self._y = length, tp, x, y
|
||||||
self._cells = cells or [self.DeckStatus.OK] * self._length
|
self._cells = cells or [DeckStatus.OK] * self._length
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.__class__.__name__}{(self._length, self._tp, self._x, self._y, self._cells)!r}"
|
return f"{self.__class__.__name__}{(self._length, self._tp, self._x, self._y, self._cells)!r}"
|
||||||
@@ -376,11 +403,11 @@ class Ship:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _is_move(self) -> bool:
|
def _is_move(self) -> bool:
|
||||||
return all(cell == self.DeckStatus.OK for cell in self)
|
return all(cell == DeckStatus.OK for cell in self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_alive(self) -> bool:
|
def is_alive(self) -> bool:
|
||||||
return any(cell == self.DeckStatus.OK for cell in self)
|
return any(cell == DeckStatus.OK for cell in self)
|
||||||
|
|
||||||
def set_start_coords(self, x: int, y: int):
|
def set_start_coords(self, x: int, y: int):
|
||||||
self._x, self._y = x, y
|
self._x, self._y = x, y
|
||||||
@@ -390,7 +417,7 @@ class Ship:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_horizontal(self):
|
def is_horizontal(self):
|
||||||
return self._tp == self.ShipOrientation.HORIZONTAL
|
return self._tp == ShipOrientation.HORIZONTAL
|
||||||
|
|
||||||
def move(self, go: int):
|
def move(self, go: int):
|
||||||
if self._is_move:
|
if self._is_move:
|
||||||
@@ -414,9 +441,11 @@ class Ship:
|
|||||||
and self.rect.top <= other.rect.bottom
|
and self.rect.top <= other.rect.bottom
|
||||||
and self.rect.bottom >= other.rect.top
|
and self.rect.bottom >= other.rect.top
|
||||||
or any(
|
or any(
|
||||||
not (getattr(a.rect, x) - getattr(b.rect, y))
|
all(
|
||||||
|
not (getattr(a.rect, x) - getattr(b.rect, y))
|
||||||
|
for x, y in (("left", "right"), ("top", "bottom"))
|
||||||
|
)
|
||||||
for a, b in ((self, other), (other, self))
|
for a, b in ((self, other), (other, self))
|
||||||
for x, y in (("left", "right"), ("top", "bottom"))
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -431,8 +460,14 @@ class Ship:
|
|||||||
def try_move(self, go: int, pole_size: int, other_ships: List["Ship"]) -> bool:
|
def try_move(self, go: int, pole_size: int, other_ships: List["Ship"]) -> bool:
|
||||||
if not self._is_move:
|
if not self._is_move:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
backup = self.get_start_coords()
|
backup = self.get_start_coords()
|
||||||
self.move(go)
|
try:
|
||||||
|
self.move(go)
|
||||||
|
except ValueError:
|
||||||
|
self.set_start_coords(*backup)
|
||||||
|
return False
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
self.is_out_pole(pole_size)
|
self.is_out_pole(pole_size)
|
||||||
or any(self.is_collide(ship) for ship in other_ships)
|
or any(self.is_collide(ship) for ship in other_ships)
|
||||||
@@ -442,13 +477,19 @@ class Ship:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def computer_move(self, pole_size: int, other_ships: List["Ship"]):
|
def computer_move(self, pole_size: int, other_ships: List["Ship"]):
|
||||||
self.try_move(random.randint(-1, 1), pole_size, other_ships)
|
go = random.randint(-1, 1)
|
||||||
|
if not go:
|
||||||
|
return # решили не двигать корабль
|
||||||
|
moved = self.try_move(go, pole_size, other_ships)
|
||||||
|
if not moved:
|
||||||
|
go = -go
|
||||||
|
self.try_move(go, pole_size, other_ships)
|
||||||
|
|
||||||
def hit(self, x: int, y: int) -> bool:
|
def hit(self, x: int, y: int) -> bool:
|
||||||
left, top, right, bottom = self.rect
|
left, top, right, bottom = self.rect
|
||||||
if left <= x < right and top <= y < bottom:
|
if left <= x < right and top <= y < bottom:
|
||||||
a, b = ((y, top), (x, left))[self.is_horizontal]
|
a, b = ((y, top), (x, left))[self.is_horizontal]
|
||||||
self[a - b] = self.DeckStatus.DAMAGED
|
self[a - b] = DeckStatus.DAMAGED
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -463,9 +504,120 @@ class Ship:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class GamePoleError(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class ShipPlacementError(GamePoleError):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
class GamePole:
|
class GamePole:
|
||||||
def __init__(self, size: int = 10):
|
def __init__(self, size: int = 10, ships: Optional[List[Ship]] = None):
|
||||||
...
|
self._size = size
|
||||||
|
self._ships = ships or []
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}{(self._size, self._ships)!r}"
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._ships)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._ships[key]
|
||||||
|
|
||||||
|
def get_pole_list(self) -> List[List[int]]:
|
||||||
|
pole = [[0] * self._size for _ in range(self._size)]
|
||||||
|
for ship in self._ships:
|
||||||
|
ship.place_to_pole(pole)
|
||||||
|
return pole
|
||||||
|
|
||||||
|
def get_pole(self) -> Tuple[Tuple[int, ...], ...]:
|
||||||
|
return tuple(tuple(row) for row in self.get_pole_list())
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "\n".join(" ".join(map(str, row)) for row in self.get_pole_list())
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
print(self)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_free_box(pole_s, i, j) -> bool:
|
||||||
|
"""проверка, что в пределах 9 клеток вокруг координат (включая их самих) нет значений кроме 0"""
|
||||||
|
offsets = [(a, b) for a in range(-1, 2) for b in range(-1, 2)]
|
||||||
|
n, m = len(pole_s), len(pole_s[0])
|
||||||
|
for a, b in map(lambda a, b: (a + i, b + j), *zip(*offsets)):
|
||||||
|
if 0 <= a < n and 0 <= b < m:
|
||||||
|
if pole_s[a][b] != 0:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_free_places_for_ship(
|
||||||
|
pole, ship_size: int, tp: int
|
||||||
|
) -> List[Tuple[int, int]]:
|
||||||
|
"""возвращает список возможных координат для нового корабля
|
||||||
|
координаты в виде кортежей в формате (x, y) | (индекс столбца, индекс строки)
|
||||||
|
"""
|
||||||
|
free_places = []
|
||||||
|
is_horizontal = tp == ShipOrientation.HORIZONTAL
|
||||||
|
pole_s = pole
|
||||||
|
if not is_horizontal:
|
||||||
|
pole_s = list(zip(*pole))
|
||||||
|
for i, row in enumerate(pole_s):
|
||||||
|
for j in range(len(row) - ship_size):
|
||||||
|
if all(
|
||||||
|
GamePole._check_free_box(pole_s, i, j + k) for k in range(ship_size)
|
||||||
|
):
|
||||||
|
pos = (j, i) if is_horizontal else (i, j)
|
||||||
|
free_places.append(pos)
|
||||||
|
return free_places
|
||||||
|
|
||||||
|
def _validete_new_ship(self, ship):
|
||||||
|
if ship.is_out_pole(self._size):
|
||||||
|
raise ShipPlacementError(f"Новый корабль выходит за пределы поля: {ship!r}")
|
||||||
|
for other in self._ships:
|
||||||
|
if ship.is_collide(other):
|
||||||
|
raise ShipPlacementError(
|
||||||
|
f"Новый корабль {ship!r} пересекается с {other!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def place_new_ship(self, pole, ship_size: int, tp: int):
|
||||||
|
free_places = GamePole._find_free_places_for_ship(pole, ship_size, tp)
|
||||||
|
if not free_places:
|
||||||
|
# должно быть так
|
||||||
|
# raise ShipPlacementError("Нет свободных мест для размещения корабля")
|
||||||
|
# но в тестах есть размер поля 8x8, поэтому просто игнорим
|
||||||
|
return
|
||||||
|
pos = random.choice(free_places)
|
||||||
|
ship = Ship(ship_size, tp, *pos)
|
||||||
|
self._validete_new_ship(ship)
|
||||||
|
self._ships.append(ship)
|
||||||
|
ship.place_to_pole(pole)
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self._ships.clear()
|
||||||
|
pole = self.get_pole_list()
|
||||||
|
sizes = ShipSize.values()[::-1]
|
||||||
|
for ship_size, ship_count in zip(sizes, range(1, len(sizes) + 1)):
|
||||||
|
for _ in range(ship_count):
|
||||||
|
tp = random.choice(ShipOrientation.values())
|
||||||
|
self.place_new_ship(pole, ship_size, tp)
|
||||||
|
|
||||||
|
def get_ships(self) -> List[Ship]:
|
||||||
|
return self._ships
|
||||||
|
|
||||||
|
def move_ships(self):
|
||||||
|
for ship in self._ships:
|
||||||
|
ship.computer_move(self._size, self._ships)
|
||||||
|
|
||||||
|
|
||||||
|
pole = GamePole(10)
|
||||||
|
|
||||||
|
pole.init()
|
||||||
|
print(sum(map(sum, pole.get_pole())))
|
||||||
|
print(len(pole))
|
||||||
|
pole.show()
|
||||||
|
|
||||||
|
|
||||||
def tests():
|
def tests():
|
||||||
@@ -517,7 +669,4 @@ if __name__ == "__main__":
|
|||||||
import doctest
|
import doctest
|
||||||
|
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
##############################################################################################################################
|
|
||||||
exit(0)
|
|
||||||
##############################################################################################################################
|
|
||||||
tests()
|
tests()
|
||||||
|
Reference in New Issue
Block a user