""" https://stepik.org/lesson/727588/step/1?unit=728924 Посвящение в ООП Вы прошли серию испытаний и совершили множество подвигов, чтобы лицом к лицу столкнуться с настоящим вызовом, достойным лишь избранных! Для подтверждения своих знаний и навыков вам предлагается пройти этап посвящения в объектно-ориентированное программирование. И вот задание, которое выпало на вашу долю. Руководство компании целыми днями не знает куда себя деть. Поэтому они решили дать задание своим программистам написать программу игры "Морской бой". Но эта игра будет немного отличаться от классической. Для тех, кто не знаком с этой древней, как мир, игрой, напомню ее краткое описание. Каждый игрок у себя на бумаге рисует игровое поле 10 х 10 клеток и расставляет на нем десять кораблей: однопалубных - 4; двухпалубных - 3; трехпалубных - 2; четырехпалубный - 1. Корабли расставляются случайным образом, но так, чтобы не выходили за пределы игрового поля и не соприкасались друг с другом (в том числе и по диагонали). Затем, игроки по очереди называют клетки, куда производят выстрелы. И отмечают эти выстрелы на другом таком же поле в 10 х 10 клеток, которое представляет поле соперника. Соперник при этом должен честно отвечать: "промах", если ни один корабль не был задет и "попал", если произошло попадание. Выигрывает тот игрок, который первым поразит все корабли соперника. Но это была игра из глубокого прошлого. Теперь же, в компьютерную эру, корабли на игровом поле могут перемещаться в направлении своей ориентации на одну клетку после каждого хода соперника, если в них не было ни одного попадания. Итак, лично вам поручается сделать важный фрагмент этой игры - расстановку и управление кораблями в этой игре. А само задание звучит так. Техническое задание В программе необходимо объявить два класса: Ship - для представления кораблей; GamePole - для описания игрового поля. Класс Ship Класс Ship должен описывать корабли набором следующих параметров: x, y - координаты начала расположения корабля (целые числа); length - длина корабля (число палуб: целое значение: 1, 2, 3 или 4); tp - ориентация корабля (1 - горизонтальная; 2 - вертикальная). Объекты класса Ship должны создаваться командами: >>> ship = Ship(length := 1) >>> ship = Ship(length := 1, tp := 2) >>> ship = Ship(length := 1, tp := 2, x := 1, y := 1) По умолчанию (если не указывается) параметр tp = 1, а координаты x, y равны None. В каждом объекте класса Ship должны формироваться следующие локальные атрибуты: _x, _y - координаты корабля (целые значения в диапазоне [0; size), где size - размер игрового поля); _length - длина корабля (число палуб); _tp - ориентация корабля; _is_move - возможно ли перемещение корабля (изначально равно True); _cells - изначально список длиной length, состоящий из единиц (например, при length=3, _cells = [1, 1, 1]). # доп проверки кораблей --- >>> [*map(len, (Ship(1),Ship(2),Ship(3),Ship(4)))] == [*range(1, 5)] True >>> Ship(5) Traceback (most recent call last): ... ValueError: 5 is not a valid Ship.ShipSize >>> Ship(1, 1)._tp, Ship(1, 2)._tp (1, 2) >>> Ship(1, 3)._tp Traceback (most recent call last): ... ValueError: 3 is not a valid Ship.ShipOrientation >>> s = Ship(1) >>> {s[0] == 1, len(s) == 1} {True} >>> s[0] = 2 >>> s[0] 2 >>> s[0] = 4 Traceback (most recent call last): ... ValueError: 4 is not a valid Ship.DeckStatus >>> s2 = Ship(2) >>> s2._cells = [1, 2] >>> s2._cells [1, 2] >>> s._cells = [1, 2] Traceback (most recent call last): ... ValueError: _cells_ must be 1 elements long >>> s[0] = 1 >>> s._is_move True >>> s[0] = 2 >>> s._is_move False >>> s = Ship(1) >>> s.get_start_coords() (0, 0) >>> s.set_start_coords(2, 3) >>> s.get_start_coords() (2, 3) >>> s.move(1) >>> s.get_start_coords() (3, 3) >>> s.move(-1) >>> s.get_start_coords() (2, 3) >>> s[0] = 2 >>> s.move(2) >>> s.get_start_coords() (2, 3) >>> Ship(1).is_collide(Ship(2, 1, 2, 3)) 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 >>> Ship(1).is_out_pole(10) False >>> Ship(3, 1, 8, 1).is_out_pole(10) 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)]) True >>> s.get_start_coords() (0, 1) >>> s.try_move(1, 6, [Ship(1, 1, 3, 1)]) False >>> s.get_start_coords() (0, 1) >>> s.try_move(1, 6, [Ship(1, 1, 2, 1)]) False >>> s.get_start_coords() (0, 1) >>> s[0] = 2 >>> s.try_move(1, 10, [Ship(1, 1, 3, 1)]) False >>> s.get_start_coords() (0, 1) >>> s[0] = 1; s.computer_move(10, [Ship(1, 1, 3, 1)]) >>> _, y = s.get_start_coords(); 0 <= y <= 2 True >>> s = Ship(4, 2) >>> s.hit(1, 1) False >>> s._is_move True >>> s.hit(0, 4) False >>> s._is_move True >>> s.hit(0, 3) True >>> s._is_move False >>> s.is_alive True >>> {s.hit(0, x) for x in range(3)} ; s.is_alive {True} False >>> sz = 5; pole = [[0 for _ in range(sz)] for _ in range(sz)] >>> s1, s2 = Ship(2, 1), Ship(2, 2, 3, 1) >>> s1.place_to_pole(pole) True >>> pole [[1, 1, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] >>> s2.place_to_pole(pole) True >>> pole [[1, 1, 0, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] # --- Список _cells будет сигнализировать о попадании соперником в какую-либо палубу корабля. Если стоит 1, то попадания не было, а если стоит значение 2, то произошло попадание в соответствующую палубу. При попадании в корабль (хотя бы одну его палубу), флаг _is_move устанавливается в False и перемещение корабля по игровому полю прекращается. В самом классе Ship должны быть реализованы следующие методы (конечно, возможны и другие, дополнительные): set_start_coords(x, y) - установка начальных координат (запись значений в локальные атрибуты _x, _y); get_start_coords() - получение начальных координат корабля в виде кортежа x, y; move(go) - перемещение корабля в направлении его ориентации на go клеток (go = 1 - движение в одну сторону на клетку; go = -1 - движение в другую сторону на одну клетку); движение возможно только если флаг _is_move = True; is_collide(ship) - проверка на столкновение с другим кораблем ship (столкновением считается, если другой корабль или пересекается с текущим или просто соприкасается, в том числе и по диагонали); метод возвращает True, если столкновение есть и False - в противном случае; is_out_pole(size) - проверка на выход корабля за пределы игрового поля (size - размер игрового поля, обычно, size = 10); возвращается булево значение True, если корабль вышел из игрового поля и False - в противном случае; С помощью магических методов __getitem__() и __setitem__() обеспечить доступ к коллекции _cells следующим образом: value = ship[indx] # считывание значения из _cells по индексу indx (индекс отсчитывается от 0) ship[indx] = value # запись нового значения в коллекцию _cells Класс GamePole Следующий класс GamePole должен обеспечивать работу с игровым полем. Объекты этого класса создаются командой: >>> pole = GamePole(10) >>> pole = GamePole() где size - размеры игрового поля (обычно, size = 10). В каждом объекте этого класса должны формироваться локальные атрибуты: _size - размер игрового поля (целое положительное число); _ships - список из кораблей (объектов класса Ship); изначально пустой список. В самом классе GamePole должны быть реализованы следующие методы (возможны и другие, дополнительные методы): init() - начальная инициализация игрового поля; здесь создается список из кораблей (объектов класса Ship): однопалубных - 4; двухпалубных - 3; трехпалубных - 2; четырехпалубный - 1 (ориентация этих кораблей должна быть случайной). Корабли формируются в коллекции _ships следующим образом: однопалубных - 4; двухпалубных - 3; трехпалубных - 2; четырехпалубный - 1. Ориентация этих кораблей должна быть случайной. Для этого можно воспользоваться функцией randint следующим образом: [Ship(4, tp=randint(1, 2)), Ship(3, tp=randint(1, 2)), Ship(3, tp=randint(1, 2)), ...] Начальные координаты x, y не расставленных кораблей равны None. После этого, выполняется их расстановка на игровом поле со случайными координатами так, чтобы корабли не пересекались между собой. get_ships() - возвращает коллекцию _ships; move_ships() - перемещает каждый корабль из коллекции _ships на одну клетку (случайным образом вперед или назад) в направлении ориентации корабля; если перемещение в выбранную сторону невозможно (другой корабль или пределы игрового поля), то попытаться переместиться в противоположную сторону, иначе (если перемещения невозможны), оставаться на месте; show() - отображение игрового поля в консоли (корабли должны отображаться значениями из коллекции _cells каждого корабля, вода - значением 0); get_pole() - получение текущего игрового поля в виде двумерного (вложенного) кортежа размерами size x size элементов. Пример отображения игрового поля: 0 0 1 0 1 1 1 0 0 0 1 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 1 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 Пример использования классов (эти строчки в программе не писать): SIZE_GAME_POLE = 10 pole = GamePole(SIZE_GAME_POLE) pole.init() pole.show() pole.move_ships() print() pole.show() В программе требуется только объявить классы Ship и GamePole с соответствующим функционалом. На экран выводить ничего не нужно. P.S. Для самых преданных поклонников программирования и ООП. Завершите эту программу, добавив еще один класс SeaBattle для управления игровым процессом в целом. Игра должна осуществляться между человеком и компьютером. Выстрелы со стороны компьютера можно реализовать случайным образом в свободные клетки. Сыграйте в эту игру и выиграйте у компьютера. """ from typing import List, Optional from enum import Enum from functools import total_ordering from collections import namedtuple import random @total_ordering class EnumOrdering(Enum): def __eq__(self, other): if isinstance(other, self.__class__): return self.value == other.value return self.value == other def __lt__(self, other): if isinstance(other, self.__class__): return self.value < other.value return self.value < other class IntEnumField: def __init__(self, enum_cls: Enum): self.enum_cls = enum_cls def __set_name__(self, owner, name): self.name_orig = name self.name = name + "_" def __str__(self): return self.name_orig def __get__(self, instance, owner): if instance is None: return self return getattr(instance, self.name).value def __set__(self, instance, value): setattr(instance, self.name, self.enum_cls(value)) class ListEnumField: def __init__(self, enum_cls: Enum, length_key): self.enum_cls = enum_cls self.length_key = length_key def __set_name__(self, owner, name): self.name = name + "_" def __get__(self, instance, owner): if instance is None: return self return [x.value for x in getattr(instance, self.name)] def get_item(self, instance, index): return getattr(instance, self.name)[index] def set_item(self, instance, index, value): getattr(instance, self.name)[index] = self.enum_cls(value) def __set__(self, instance, value): length = getattr(instance, str(self.length_key)) new_value = list(map(self.enum_cls, value)) if len(new_value) != length: raise ValueError(f"{self.name} must be {length} elements long") setattr(instance, self.name, new_value) class NonNegativeIntField: def __set_name__(self, owner, name): self.name = name + "_" def __get__(self, instance, owner): if instance is None: return self return getattr(instance, self.name) def __set__(self, instance, value): if not isinstance(value, int): raise TypeError(f"{self.name} must be an integer") if value < 0: raise ValueError(f"{self.name} must be non-negative") setattr(instance, self.name, value) 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 _length: int = IntEnumField(ShipSize) _tp: int = IntEnumField(ShipOrientation) _cells: List[int] = ListEnumField(DeckStatus, _length) _x: int = NonNegativeIntField() _y: int = NonNegativeIntField() Rect = namedtuple("Rect", ["left", "top", "right", "bottom"]) def __init__( self, length: int, tp: int = 1, x: int = 0, y: int = 0, 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 def __repr__(self): return f"{self.__class__.__name__}{(self._length, self._tp, self._x, self._y, self._cells)!r}" def __len__(self): return self._length def __getitem__(self, key): return self.__class__._cells.get_item(self, key).value def __setitem__(self, key, value): return self.__class__._cells.set_item(self, key, value) @property def _is_move(self) -> bool: return all(cell == self.DeckStatus.OK for cell in self) @property def is_alive(self) -> bool: return any(cell == self.DeckStatus.OK for cell in self) def set_start_coords(self, x: int, y: int): self._x, self._y = x, y def get_start_coords(self): return self._x, self._y @property def is_horizontal(self): return self._tp == self.ShipOrientation.HORIZONTAL def move(self, go: int): if self._is_move: if self.is_horizontal: self._x += go else: self._y += go @property def rect(self): x, y = self.get_start_coords() if self.is_horizontal: return self.Rect(x, y, x + self._length, y + 1) else: return self.Rect(x, y, x + 1, y + self._length) def is_collide(self, other: "Ship") -> bool: return ( self.rect.left <= other.rect.right and self.rect.right >= other.rect.left 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)) for a, b in ((self, other), (other, self)) for x, y in (("left", "right"), ("top", "bottom")) ) ) def is_out_pole(self, size: int) -> bool: return ( self.rect.left < 0 or self.rect.top < 0 or self.rect.right > size or self.rect.bottom > size ) 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) if not ( self.is_out_pole(pole_size) or any(self.is_collide(ship) for ship in other_ships) ): return True self.set_start_coords(*backup) return False def computer_move(self, pole_size: int, other_ships: List["Ship"]): self.try_move(random.randint(-1, 1), pole_size, other_ships) def hit(self, x: int, y: int) -> bool: left, top, right, bottom = self.rect if left <= x < right and top <= y < bottom: a, b = ((y, top), (x, left))[self.is_horizontal] self[a - b] = self.DeckStatus.DAMAGED return True return False def place_to_pole(self, pole: List[List[int]]) -> bool: if self.is_out_pole(len(pole)): return False rect = self.rect for i in range(rect.top, rect.bottom): for j in range(rect.left, rect.right): a, b = ((i, rect.top), (j, rect.left))[self.is_horizontal] pole[i][j] = self[a - b] return True class GamePole: def __init__(self, size: int = 10): ... def tests(): code = ( b"b7*OBAUz;cXlZaLGARmkXlZaDJs?wPX>ceqEFdu{3Ug>_a3DP(Q)p>$C^IY|GAtl4EFdr`3JPI" + b"!b7gXLAaiJGa4uhLWo~D5Xdpd3ATuCgZe$>HXlZaRUvzLFJv|^YAYpD~AaiJGa4uhXAU!=GFd$)W" + b"WFT{BX>cxIc_2MKATTT-BGA3iwa~KAwb6jkz0r%%wII;9(7n*G(TC8r(7n*O(T^a|u+f6ifY7+mv" + b"C)Ikg3!LuvLMjD(6P~q(6!LI(Sp#hAkezdyU?)Ffzg4`upm=tX>cM6VRLh3a&#bbXlZaRUt?u#Y;" + b"zzzJs?{#EFdu~ATeDmAR^Gc(6!LA(6!Nk(7n-%(77PdfzZFuxY2>ozR_a4vIYbYF9HVRCd|V{dPAWOFDnEFdx|3So0|WpZ>Nb7*OBE?;;c" + b"Jv|^XAYpD~AaiJGa4uhYAU!=GGAtk>(7n*L(6Z3A(SXps(7qthzR`lwfY7kevCzKJg3z$gyCBfK(" + b"6!Nm(7w>LAaiAOUvqR}a&%u~Z*OvBb0{ey3So0|WpZ>Nb7*OBE@x$QUvqR}a&%u~Z*OvBb0{ewJv" + b"|^OF)Sc5DJ&o&(7n*L(6Z3A(SXps(7qthzR`lwfY7kevCzKJg3z$gyCBfK(6!Nm(7w>LAZKNCUvq" + b"R}a&%u~Z*OvBb0{ey3JP;*X>cxWZ+2xUF)0djF(5r4Q)p>$C^Re}F)Sc3EFdr`3Ue|bJs?wPX>ce" + b"rEFdy0ATTT-FewUiGax-6Q)p>$C^IY|GAtl4EFdx|3JPI!b7gXLAagM;X>(s=Z)|L7WMwFGGAS$|" + b"BGA3iwa~KAwb6jkz0kfO(SXpf(6P|I(Sp#h(6!NmAke(fwb6pmzR(s=Z)|L7WMwERAkehXy" + b"U~vz(7MpR(SXpf(6P|F(6!LHAX8{*a40k^ATcZ;Ff1T2DIn0eAX8{*a40h@ATlf<77Ut@1<77Ut@1(t1b#z~FZ){~KF)%40(6rFI(T^a|y3oGSfY7kevCzBGk0" + b"4WMX>cerEFdy0ATcZ;H7Ozr3Ue}BFkK)$ATkPJb8}^KbRcswTQFT9Jv|^YEFdD#z0kGLve32BfY8" + b"0rz97+n(6G?4(7w@v(6G?8(Sjh*zReO=$l3TAI|AZ~6TX>K5LVQyz-C^acM3LqdLAZBlJAafvTZXj?jUvp?_aC15e" + b"ARr(hARr(hVRLh3a&#bbE@^XLZ*_EEaBpm7C^0Z8AU!=GMqzAoWh@{f(7MpR(SXpf(6P|F(6}Jbv" + b"eApth0wmxw9${zf*{bh(6AuTztMouwa~QCwa~lKiy+Xr(6iBi(7w>J(7w>K(7qthztFzWyU~v#3J" + b"M?~ARr(hARuOMav*bPX>cHEZXj?jXJvF>b7*OBb0{e~3LqdLARr(hARr(hAZcbGb08r-AaiJGa5@" + b"SgARr(hARr(hARr(hARr)Nb8}^KbRcssX>(s=Z)|L7WMwFGXlZaMAU!=GMqzAoWh@{f(7MpR(SXp" + b"f(6P|F(6}Jbz0j~A(74dE(SXpt(6Z3J(7YhfztFzWyU?{D(Sgvu(7(}u(74dL(6G^g(6G^t(Sp%|" + b"(T^euARr(ha4v0cc4c34XlZbBC@BgcARr(LXK)}rAaE{cWprO~Z){~KDGFh8b7gXLAar?fWhiHGD" + b"Ih&PAar$bY-J!}Ze$>Id2nSYXK-6ET`3?vJs@;-aBO8PAR^Gb(6!Nm(7w>LAZKNCUvO`1WgyVB(7" + b"w>S(6-RE(7hngve3TJx6rcDfY7kfiO{gog3*j1(6rF9(Sy*u(6!Nk(7n-%(77Pcy3oGSfYE}`wa~" + b"UA3So0|WpZ>NY-MgJXK*PXJv|^XFd$)WWFTy1ZYXDPTQFTIAU!=GF)%D3BGA3iwa~KAwb6jkz0r%" + b"%wII=e(6G?A(7e#K(SXs5Aketbv(bRizRS(T^-3(7MpR(Sp#v(SXpt(6u1Y" + b"ve32BfY80sgV4Jm(7e#K(Sp#v(6k_DWprO~Z){~E3JP#MtBC@B" ) exec(__import__("base64").b85decode(code)) if __name__ == "__main__": import doctest doctest.testmod() ############################################################################################################################## exit(0) ############################################################################################################################## tests()