diff --git a/mod_oop/5.6_01_sea_battle.py b/mod_oop/5.6_01_sea_battle.py index b6bede3..30ec0ba 100644 --- a/mod_oop/5.6_01_sea_battle.py +++ b/mod_oop/5.6_01_sea_battle.py @@ -107,7 +107,7 @@ False >>> Ship(2, 1, 2, 3).is_collide(Ship(3, 2, 3, 2)) True >>> Ship(2, 1, 0, 0).is_collide(Ship(1, 2, 2, 2)) -True +False >>> Ship(1).is_out_pole(10) False >>> Ship(3, 1, 8, 1).is_out_pole(10) @@ -115,15 +115,15 @@ True >>> Ship(3, 2, 1, 8).is_out_pole(10) True >>> 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 >>> s.get_start_coords() (0, 1) ->>> s.try_move(1, 6, [Ship(1, 1, 3, 1)]) +>>> s.try_move(1, 6, [Ship(1, 1, 1, 6)]) False >>> s.get_start_coords() (0, 1) ->>> s.try_move(1, 6, [Ship(1, 1, 2, 1)]) +>>> s.try_move(1, 6, [Ship(1, 1, 1, 2)]) False >>> s.get_start_coords() (0, 1) @@ -211,6 +211,26 @@ show() - отображение игрового поля в консоли (к 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 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 functools import total_ordering from collections import namedtuple @@ -261,6 +281,10 @@ class EnumOrdering(Enum): return self.value < other.value return self.value < other + @classmethod + def values(cls): + return tuple(item.value for item in cls) + class IntEnumField: def __init__(self, enum_cls: Enum): @@ -357,7 +381,7 @@ class Ship: tp: int = 1, x: 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._cells = cells or [self.DeckStatus.OK] * self._length @@ -414,9 +438,11 @@ class Ship: and self.rect.top <= other.rect.bottom and self.rect.bottom >= other.rect.top 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 x, y in (("left", "right"), ("top", "bottom")) ) ) @@ -431,8 +457,14 @@ class Ship: def try_move(self, go: int, pole_size: int, other_ships: List["Ship"]) -> bool: if not self._is_move: return False + backup = self.get_start_coords() - self.move(go) + try: + self.move(go) + except ValueError: + self.set_start_coords(*backup) + return False + if not ( self.is_out_pole(pole_size) or any(self.is_collide(ship) for ship in other_ships) @@ -442,7 +474,13 @@ class Ship: return False 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: left, top, right, bottom = self.rect @@ -463,9 +501,120 @@ class Ship: return True +class GamePoleError(Exception): + ... + + +class ShipPlacementError(GamePoleError): + ... + + 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 == Ship.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 = Ship.ShipSize.values()[::-1] + for ship_size, ship_count in zip(sizes, range(1, len(sizes) + 1)): + for _ in range(ship_count): + tp = random.choice(Ship.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(): @@ -517,7 +666,4 @@ if __name__ == "__main__": import doctest doctest.testmod() - ############################################################################################################################## - exit(0) - ############################################################################################################################## tests()