py_stepik/mod_oop/5.6_01_sea_battle.py
2024-04-29 19:05:48 +03:00

671 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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 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 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 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))
False
>>> 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, 1, 6)])
True
>>> s.get_start_coords()
(0, 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, 1, 2)])
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 элементов.
>>> 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
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, Tuple
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
@classmethod
def values(cls):
return tuple(item.value for item in cls)
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 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:
_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 [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 == DeckStatus.OK for cell in self)
@property
def is_alive(self) -> bool:
return any(cell == 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 == 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(
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))
)
)
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()
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)
):
return True
self.set_start_coords(*backup)
return False
def computer_move(self, pole_size: int, other_ships: List["Ship"]):
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
if left <= x < right and top <= y < bottom:
a, b = ((y, top), (x, left))[self.is_horizontal]
self[a - b] = 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 GamePoleError(Exception):
...
class ShipPlacementError(GamePoleError):
...
class GamePole:
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():
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<cLUt?u#Y;z(CVRLh3a&#"
+ b"bbXlZaRUukn+ZEtpEEFdD#z0kGLve32BfY80rzR<NG(6`XN(6G^m(6!LL(74dGAkeVUg3*A`xX`i"
+ b"DgVBP}upnP)b6;(5c4Z<83Ug>_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<KFX>(s=Z)|L7WMwERAkehXy"
+ b"U~vz(7MpR(SXpf(6P|F(6!LHAX8{*a40k^ATcZ;Ff1T2DIn0eAX8{*a40h@ATlf<Ff1T2DIyACb8"
+ b"}^KbRcsvE@^XLV{dG1X=G(6b2BL*Jv|^sVQh0{EFdD#z0kGLve32BfY80rz97+n(6G?4(7w@v(6G"
+ b"?8(Sjh*ywJ7Lg3!Luv><77Ut@1<Y-wa=C@CP&w9vcJk08*x(7w@t(6G?4(7VvJ(77N}XlZaLG%O%"
+ b"7EFdr}ATTK)(6}H|XlZaLGb|u7EFdr}ATlW;3JP;FAUz;cXlZaLGb|u7EFdu~ATcQlVRLh3a&#bb"
+ b"F)nFyUt@1<Y-wa=D04C?EFdD#z0kGLve32BfY80rz97+n(6G?4(7w@v(6G?8(Sjh*ywJ7Lg3!Luv"
+ b"><77Ut@1<Y-wa=C@CP&w9vcJk08*x(7w@t(6G?4(7VvJ(77N}XlZaLG%O%7EFdr}ATTK)(6}H|Xl"
+ b"ZaLGb|u7EFdu~ATcQ-3JP;FAUz;cXlZaLGb|u6EFd^6ATcQlVRLh3a&#bbGA?OzUvG7EUvO`1Whg"
+ b"N)DJ&o&(7n*L(6Z3A(SXps(7qtifY7kevCzKJg3z$gwb6ng(7e#K(Sp#v(6k_Fb6;<DbYF09Y-K1"
+ b"ZAkehXyU~vz(7MpR(SXpf(6P|F(T^ZgXlZaLGb|u6EFd^6ATcQ-3JP;FAUz;cXlZaLGb|u7EFdu~"
+ b"AT=opVRLh3a&#bbGA?OzUvG7EUvO`1WhgN)DIh&PAVy(qb7d?bBGA3iwa~KAwb6jkz0kfO(SXpf("
+ b"6P|I(Sp#h(6!NmAke(fwb6pmzR<KFX>(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*zR<DJfY7kfiO{vsz0kPOwIFk7X>eO<Ze(~}A_@v{AUz;QVQpn"
+ b"lZ){~KF)%3#a4u<XX>=$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(bRizR<GJzR<JKz97)Q(7w>S(T^-3(7MpR(Sp#v(SXpt(6u1Y"
+ b"ve32BfY80sgV4Jm(7e#K(Sp#v(6k_DWprO~Z){~E3JP#<Y-L|_X?kT}I3PVBM`3McP;YEyC^#t!a"
+ b"Bpm7Uvp`CWnVZhX>MtBC@B"
)
exec(__import__("base64").b85decode(code))
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()