273 lines
14 KiB
Python
273 lines
14 KiB
Python
|
"""
|
|||
|
https://stepik.org/lesson/701992/step/10?unit=702093
|
|||
|
|
|||
|
Вы начинаете разрабатывать игру "Сапер". Для этого вам нужно уметь представлять и управлять игровым полем. Будем полагать, что оно имеет размеры N x M клеток. Каждая клетка будет представлена объектом класса Cell и содержать либо число мин вокруг этой клетки, либо саму мину.
|
|||
|
|
|||
|
Для начала в программе объявите класс GamePole, который будет создавать и управлять игровым полем. Объект этого класса должен формироваться командой:
|
|||
|
pole = GamePole(N, M, total_mines)
|
|||
|
#>>> 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 - если открыта.
|
|||
|
|
|||
|
Пример использования классов (эти строчки в программе писать не нужно):
|
|||
|
|
|||
|
>>> 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 Final, List
|
|||
|
|
|||
|
|
|||
|
class SingletonMeta(type):
|
|||
|
_instances = {}
|
|||
|
|
|||
|
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 = (
|
|||
|
(-1, -1),
|
|||
|
(-1, 0),
|
|||
|
(-1, 1),
|
|||
|
(0, -1),
|
|||
|
(0, 1),
|
|||
|
(1, -1),
|
|||
|
(1, 0),
|
|||
|
(1, 1),
|
|||
|
)
|
|||
|
|
|||
|
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):
|
|||
|
return "\n".join(" ".join(map(str, row)) for row in self.pole)
|
|||
|
|
|||
|
def show_pole(self):
|
|||
|
print(self)
|
|||
|
|
|||
|
|
|||
|
def tests():
|
|||
|
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 sys
|
|||
|
|
|||
|
if sys.argv[-1] == "doctest":
|
|||
|
import doctest
|
|||
|
|
|||
|
doctest.testmod()
|
|||
|
elif sys.argv[-1] == "show":
|
|||
|
pole_game = GamePole(10, 8, 12)
|
|||
|
pole_game.open_random(30)
|
|||
|
pole_game.show_pole()
|
|||
|
else:
|
|||
|
tests()
|