330 lines
15 KiB
Python
330 lines
15 KiB
Python
|
"""
|
|||
|
https://stepik.org/lesson/717070/step/1?unit=717930
|
|||
|
|
|||
|
Испытание магией.
|
|||
|
|
|||
|
Вы прошли магические методы.
|
|||
|
Начальство оценило вашу стойкость, рвение и решило дать вам испытание для подтверждения уровня полученных навыков.
|
|||
|
Вам выпала великая честь создать полноценную программу игры в "Крестики-нолики".
|
|||
|
И вот перед вами текст с заданием самого испытания.
|
|||
|
|
|||
|
## Техническое задание
|
|||
|
|
|||
|
Необходимо объявить класс с именем TicTacToe (крестики-нолики) для управления игровым процессом. Объекты этого класса будут создаваться командой:
|
|||
|
game = TicTacToe()
|
|||
|
|
|||
|
В каждом объекте этого класса должен быть публичный атрибут:
|
|||
|
pole - двумерный кортеж, размером 3x3.
|
|||
|
|
|||
|
Каждый элемент кортежа pole является объектом класса Cell:
|
|||
|
cell = Cell()
|
|||
|
|
|||
|
В объектах этого класса должно автоматически формироваться локальное свойство:
|
|||
|
value - текущее значение в ячейке: 0 - клетка свободна; 1 - стоит крестик; 2 - стоит нолик.
|
|||
|
|
|||
|
Также с объектами класса Cell должна выполняться функция:
|
|||
|
bool(cell) - возвращает True, если клетка свободна (value = 0) и False - в противном случае.
|
|||
|
|
|||
|
К каждой клетке игрового поля должен быть доступ через операторы:
|
|||
|
res = game[i, j] # получение значения из клетки с индексами i, j
|
|||
|
game[i, j] = value # запись нового значения в клетку с индексами i, j
|
|||
|
|
|||
|
Если индексы указаны неверно (не целые числа или числа, выходящие за диапазон [0; 2]), то следует генерировать исключение командой:
|
|||
|
raise IndexError('некорректно указанные индексы')
|
|||
|
|
|||
|
Чтобы в программе не оперировать величинами: 0 - свободная клетка; 1 - крестики и 2 - нолики, в классе TicTacToe должны быть три публичных атрибута (атрибуты класса):
|
|||
|
FREE_CELL = 0 # свободная клетка
|
|||
|
HUMAN_X = 1 # крестик (игрок - человек)
|
|||
|
COMPUTER_O = 2 # нолик (игрок - компьютер)
|
|||
|
|
|||
|
В самом классе TicTacToe должны быть объявлены следующие методы (как минимум):
|
|||
|
init() - инициализация игры (очистка игрового поля, возможно, еще какие-либо действия);
|
|||
|
show() - отображение текущего состояния игрового поля (как именно - на свое усмотрение);
|
|||
|
human_go() - реализация хода игрока (запрашивает координаты свободной клетки и ставит туда крестик);
|
|||
|
computer_go() - реализация хода компьютера (ставит случайным образом нолик в свободную клетку).
|
|||
|
|
|||
|
Также в классе TicTacToe должны быть следующие объекты-свойства (property):
|
|||
|
is_human_win - возвращает True, если победил человек, иначе - False;
|
|||
|
is_computer_win - возвращает True, если победил компьютер, иначе - False;
|
|||
|
is_draw - возвращает True, если ничья, иначе - False.
|
|||
|
|
|||
|
Наконец, с объектами класса TicTacToe должна выполняться функция:
|
|||
|
bool(game) - возвращает True, если игра не окончена (никто не победил и есть свободные клетки) и False - в противном случае.
|
|||
|
|
|||
|
Все эти функции и свойства предполагается использовать следующим образом (эти строчки в программе не писать):
|
|||
|
game = TicTacToe()
|
|||
|
game.init()
|
|||
|
step_game = 0
|
|||
|
while game:
|
|||
|
game.show()
|
|||
|
|
|||
|
if step_game % 2 == 0:
|
|||
|
game.human_go()
|
|||
|
else:
|
|||
|
game.computer_go()
|
|||
|
|
|||
|
step_game += 1
|
|||
|
|
|||
|
|
|||
|
game.show()
|
|||
|
|
|||
|
if game.is_human_win:
|
|||
|
print("Поздравляем! Вы победили!")
|
|||
|
elif game.is_computer_win:
|
|||
|
print("Все получится, со временем")
|
|||
|
else:
|
|||
|
print("Ничья.")
|
|||
|
|
|||
|
Вам в программе необходимо объявить только два класса: TicTacToe и Cell так, чтобы с их помощью можно было бы сыграть в "Крестики-нолики" между человеком и компьютером.
|
|||
|
|
|||
|
P.S. Запускать игру и выводить что-либо на экран не нужно. Только объявить классы.
|
|||
|
P.S.S. Домашнее задание: завершите создание этой игры и выиграйте у компьютера хотя бы один раз.
|
|||
|
|
|||
|
"""
|
|||
|
|
|||
|
from enum import Enum
|
|||
|
|
|||
|
|
|||
|
class Cell:
|
|||
|
CHARS = "⬜⭕❌"
|
|||
|
|
|||
|
class State(Enum):
|
|||
|
FREE = 0
|
|||
|
HUMAN = 1
|
|||
|
COMPUTER = 2
|
|||
|
|
|||
|
@classmethod
|
|||
|
def wrap(cls, v):
|
|||
|
return v if isinstance(v, cls) else cls(v)
|
|||
|
|
|||
|
def __init__(self, value=State.FREE):
|
|||
|
self.__state = self.State.wrap(value)
|
|||
|
|
|||
|
@property
|
|||
|
def is_free(self):
|
|||
|
return self.state is self.State.FREE
|
|||
|
|
|||
|
@property
|
|||
|
def value(self):
|
|||
|
return self.__state.value
|
|||
|
|
|||
|
@value.setter
|
|||
|
def value(self, v):
|
|||
|
self.__state = self.State.wrap(v)
|
|||
|
|
|||
|
@property
|
|||
|
def state(self):
|
|||
|
return self.__state
|
|||
|
|
|||
|
@state.setter
|
|||
|
def state(self, v):
|
|||
|
self.__state = self.State.wrap(v)
|
|||
|
|
|||
|
def __bool__(self):
|
|||
|
return self.is_free
|
|||
|
|
|||
|
def __repr__(self):
|
|||
|
return f"{self.__class__.__name__}{(self.value)!r}"
|
|||
|
|
|||
|
def __str__(self):
|
|||
|
return self.CHARS[not self and self.value]
|
|||
|
|
|||
|
|
|||
|
class TicTacToe:
|
|||
|
# для тестов
|
|||
|
FREE_CELL = Cell.State.FREE.value
|
|||
|
HUMAN_X = Cell.State.HUMAN.value
|
|||
|
COMPUTER_O = Cell.State.COMPUTER.value
|
|||
|
|
|||
|
class State(Enum):
|
|||
|
ACTIVE = 0
|
|||
|
HUMAN_WIN = 1
|
|||
|
COMPUTER_WIN = 2
|
|||
|
DRAW = 3
|
|||
|
|
|||
|
def is_ended(self):
|
|||
|
return self is not self.ACTIVE
|
|||
|
|
|||
|
def __init__(self, pole=None):
|
|||
|
self.state = self.State.ACTIVE
|
|||
|
if pole is None:
|
|||
|
pole = tuple(tuple(Cell() for _ in range(3)) for _ in range(3))
|
|||
|
self.pole = pole
|
|||
|
|
|||
|
def init(self):
|
|||
|
for row in self.pole:
|
|||
|
for cell in row:
|
|||
|
cell.value = Cell.State.FREE.value
|
|||
|
self.state = self.State.ACTIVE
|
|||
|
|
|||
|
def __bool__(self):
|
|||
|
return not self.state.is_ended()
|
|||
|
|
|||
|
def _find_line(self, value):
|
|||
|
for row in self.pole:
|
|||
|
if all(cell.value == value for cell in row):
|
|||
|
return True
|
|||
|
for col in zip(*self.pole):
|
|||
|
if all(cell.value == value for cell in col):
|
|||
|
return True
|
|||
|
if all(self[i, i] == value for i in range(len(self.pole))):
|
|||
|
return True
|
|||
|
if all(self[i, len(self.pole) - i + 1] == value for i in range(len(self.pole))):
|
|||
|
return True
|
|||
|
|
|||
|
def _update_state(self):
|
|||
|
if not any(cell.is_free for row in self.pole for cell in row):
|
|||
|
self.state = self.State.DRAW
|
|||
|
elif self._find_line(Cell.State.HUMAN.value):
|
|||
|
self.state = self.State.HUMAN_WIN
|
|||
|
elif self._find_line(Cell.State.COMPUTER.value):
|
|||
|
self.state = self.State.COMPUTER_WIN
|
|||
|
|
|||
|
@property
|
|||
|
def is_human_win(self):
|
|||
|
return self.state is self.State.HUMAN_WIN
|
|||
|
|
|||
|
@property
|
|||
|
def is_computer_win(self):
|
|||
|
return self.state is self.State.COMPUTER_WIN
|
|||
|
|
|||
|
@property
|
|||
|
def is_draw(self):
|
|||
|
return self.state is self.State.DRAW
|
|||
|
|
|||
|
def _get_key(self, key):
|
|||
|
if not isinstance(key, tuple) or len(key) != 2:
|
|||
|
raise IndexError("неверный индекс клетки")
|
|||
|
return tuple(x if isinstance(x, int) else None for x in key)
|
|||
|
|
|||
|
def __getitem__(self, key):
|
|||
|
key = self._get_key(key)
|
|||
|
row, col = key
|
|||
|
if row is None and col is None:
|
|||
|
return tuple(tuple(c.value for c in r) for r in self.pole)
|
|||
|
if row is not None and col is not None:
|
|||
|
return self.pole[row][col].value
|
|||
|
if row is not None and col is None:
|
|||
|
return tuple(x.value for x in self.pole[row])
|
|||
|
return tuple(self.pole[row][col].value for row in range(len(self.pole)))
|
|||
|
|
|||
|
def __setitem__(self, key, value):
|
|||
|
key = self._get_key(key)
|
|||
|
row, col = key
|
|||
|
if row is None and col is None:
|
|||
|
for i, row in enumerate(value):
|
|||
|
for j, v in enumerate(row):
|
|||
|
self[i, j] = v
|
|||
|
return
|
|||
|
if row is not None and col is not None:
|
|||
|
cell = self.pole[row][col]
|
|||
|
if not cell.is_free:
|
|||
|
raise ValueError("клетка уже занята")
|
|||
|
cell.value = value
|
|||
|
return
|
|||
|
if row is not None and col is None:
|
|||
|
for j, v in enumerate(value):
|
|||
|
self[row, j] = v
|
|||
|
return
|
|||
|
for i, v in enumerate(value):
|
|||
|
self[i, col] = v
|
|||
|
self._update_state()
|
|||
|
|
|||
|
def human_go(self):
|
|||
|
...
|
|||
|
|
|||
|
def computer_go(self):
|
|||
|
...
|
|||
|
|
|||
|
def show(self):
|
|||
|
print(self)
|
|||
|
|
|||
|
def __str__(self):
|
|||
|
c = 2
|
|||
|
result = (
|
|||
|
f"╭─{'─┬─' * c}─╮\n"
|
|||
|
+ f"├─{'─┼─' * c}─┤\n".join(
|
|||
|
map(lambda row: f"│{'│'.join(map(str, row))}│\n", self.pole)
|
|||
|
)
|
|||
|
+ f"╰─{'─┴─' * c}─╯"
|
|||
|
)
|
|||
|
return result
|
|||
|
|
|||
|
def __repr__(self):
|
|||
|
return f"{self.__class__.__name__}({self.pole!r})"
|
|||
|
|
|||
|
|
|||
|
t = TicTacToe()
|
|||
|
t[1, 1] = 2
|
|||
|
t[0, 1] = 1
|
|||
|
t[0, 0] = 2
|
|||
|
t[2, 1] = 1
|
|||
|
t[2, 2] = 2
|
|||
|
t.show()
|
|||
|
print(t.is_computer_win, t.is_human_win, t.is_draw)
|
|||
|
|
|||
|
|
|||
|
def tests():
|
|||
|
code = (
|
|||
|
b"V`Xe?AUz;MWo&FHDGFh8b7gXLAY)~0Y%X?TY;|QIJv|^WEFdD#z0k1HhtROlyU~o$z0khUwII;"
|
|||
|
+ b"9(7n*G(TC8r(7n*O(6u1Yu+f6ifY7+mvC)Ikg3z!ac42IFWgyVL(6P~q(6!LI(Sp#hAkezdyU?)F"
|
|||
|
+ b"fzg4`upmQaY-}LVw9vlLyU@1Kz0kfO(6P~r(Sp&8Akl!(u+Xy5z0kfOFd_<Jb8}^KbRc4HZ)_-IW"
|
|||
|
+ b"o&FIEFdD$gwcc0z0kVRhS0dtk04@iZ)_mYw9vcJk08*#(6P~q(6!LI(Sp#hAkezdyU?)Ffzg4`up"
|
|||
|
+ b"mQaY-}LVve32BfY80sgV4LsuprRA(6!LA(6!Nk(7n*U(6u1Yx6r-Nu+fLmwa~rLxX`sC3S(t#Y%X"
|
|||
|
+ b"?TY;|QIJs>d(VRLh3a&#bKZ*OcUV`Xe?DIh&PAVy(qb7d?bBGH7=gV4Ruy3vNvxY3UwVsCG3Akeh"
|
|||
|
+ b"XyU~vz(7w>I(TdQu(7Mrr(6AuTy3o7Ou+f3hfzYrZLuG7iAkebVwb6jkz0rfvyU?&8(7n*L(6Z3A"
|
|||
|
+ b"(SXps(7w>MAkeqaz0k1HhtRdqz0kPOwIT`%VRLh3a&#bQVRK=0baE(EX=7AjV^nWtEFdRyXm58XD"
|
|||
|
+ b"Ij5PWFTl^b76FJawt@3V^m>dRBvT0ASY;bZDDR-XKyDdAYpD~AZTH8VRUqIC{$@<RAFOOZ)GeXCu"
|
|||
|
+ b"47IaCLNLa$jd}Cn+o-BG9_fyU?)Ffzg2=RB2;WVPjNpWgyVB(7w>S(6-RE(7hngxX`@Nwb6pnj3C"
|
|||
|
+ b"gw(6!Nm(7w>L(TgB+Xm58cAZT@MVQyb%Z!92VZ*6dObY*g1XKx}33TI($WgtBuRB2;WVPjNpWhf~"
|
|||
|
+ b"MVRLh3a&#bKZ*OcUXJKt+DJ&o&(S*^1(7n*Q(T32t(T^ZvZ*OcM(6Z3A(SXps(Sy*t(6AuTz0kGL"
|
|||
|
+ b"ve32BfY80rzR<NG(6`XN(6G^m(6!LL(74f$AkehXyU~vz(7w>I(TdQu(7Mrr(6AuTy3o7Ou+f3hf"
|
|||
|
+ b"zYrZRB2;WVPjNpWg-e;b8}^KbRcJ8ZDm_9EFdslAU!=GFd$)WWFTi@ZDm_BEFdynAU!=GFf1S<(7"
|
|||
|
+ b"n*L(6Z3A(SXps(TmWvAkeqaz0k1HhtRdqz0kPPk08;H(TC8r(6!LIEFjRb(6`Z#(Sp&7(6u1YztF"
|
|||
|
+ b"xQ(74dO(6rFC(7Mrq(6G?FA_`|=ZDm_AEFdvmAUz;dX=7AjV^nWtE=W~PK~7&-3So0|WpZ>NXJKt"
|
|||
|
+ b"+TQMvkF<l@%Js?zRV^m>dRBvT2NL5WiPG49oAR^Gc(6!LA(6!Nk(7n*UAkl!(u+Xv4zR`lvu+X*9"
|
|||
|
+ b"f*{bo(7({N(SXpf(Sp#v(SRV(ztMouxY2>ove2;5xX`lDu+Y8GxY3Uw(7n*U(6Z3J(6i9KAkeqaz"
|
|||
|
+ b"0k1HhtRdqz0kPPk08*pAkmM}htRdqxzM`NgCNkj(6iBi(7w>J(7w>K(7qthztFzWyU~v#3JPaoZD"
|
|||
|
+ b"m_9EFdslAUz;dX=7AjV^nWtE<;aEP*qe#QeRIBVRLh3a&#bPVQpnwFf1T2T_8O@AXI5%RAFOOZ)G"
|
|||
|
+ b"k+Pfbu&R7Fx>Pb?rJ(7n*L(6Z3A(SXps(7qtifY7kevCzKJg3z$gwb6ng(7w>W(6!Nk(6G^h(7w@"
|
|||
|
+ b"tAke?jfY7+nfzYzhu+X^Bve2;5z0kPPk08*!(7w>J(7w>K(7qthx6r-Nu+fLmwa~rLxY3Uw(6S)W"
|
|||
|
+ b"kI{$Fwa~fHy3vCm(74dE(SXpt(6Z3J(6i9KAke?izR<hTk0J^RXJKt+E@^IQbSNnbVRLh3a&#bPV"
|
|||
|
+ b"QpnwFf1T2T_8O@AXI5%RAFOOZ)Gk<Qbk2yLq$wXAYpD~AZKB1Wm_>UATeDaJv|^)X=7AjV^nWtE="
|
|||
|
+ b"E#CMPEZjOiU~wBGA9lfY7)g(74dO(74ft(74dB(7VvM(6`XA(T32t(6}JbxX`oFfY83sve3TJv(U"
|
|||
|
+ b"aE(7({W(7Vx(AkebWfzY)e(7MpO(6!Nm(7MpLAkehXzR<hSw$Q!Niy+Xy(SXpn(7n*O(7e#F(Sp&"
|
|||
|
+ b"8Akeqaz0k1HhtRdqz0kPOwII;A(6=Dau+f6ifY7+mvC)Ikg3z!aMp8vZUqeMqOd<*jbaHt*3LqdL"
|
|||
|
+ b"AZKB1Wm_{WATV7ZJs>m+Wq4y{aC9I^Ze(S6MRIa)aykkiARr)Nb8}^KbRbl6b!7@=Y;$Eg3LqdLA"
|
|||
|
+ b"YpTJWpZ>NMqzAoWh@{f(7n*LAkl%)v(UBBz0kGMfY7+nfY83sve2;5yU@PTfzga0(74fo(7MpO(T"
|
|||
|
+ b">rF(6!LL(74dGAW3dyWq3t$a&K}X3JPaoZDlTLZfSHVDGFh8b7gXLAZKB1WiDxRUubo0VQyb{X>K"
|
|||
|
+ b"4rJs?J5Y;$EGVQyp~XJKt+E@^XLV{dJ6b#!HNUw3J4AU!=GMqzAoWgua0WFTi@ZDlTLb6;d~VRs-"
|
|||
|
+ b"sJs?J5Y;$ESAR^Ge(SXpnAketbz0kPPhS0dsu+Y2ExX`!Iu+fIlxX`#D(74dE(SXs5AkeVUg3*A`"
|
|||
|
+ b"xX`iDgVBP~iy&!pUubo0VQyb{X>KeaX>(s=Z*6dObY*g1cWG`cAZc@7WO8A5AkehXzR<hSw$Q!Ni"
|
|||
|
+ b"y+Xk(TmZ7(TpI`fY7keve3QJiy%f}Y;$ESAkebVzR<VOywJYTw$Q!Mz97)O(6u1YztFYOfY7zkfz"
|
|||
|
+ b"gN1xY2^qi_o&ru+X*9g3*D|k08;3(Sp#h(Sp%~(Sab)xX`oFfYFN}(7(}u(6}JbveApsx6r=Ove3"
|
|||
|
+ b"04(7e#K(Sp#v(6rF7AZc!CbSNnz3JPaoZDm_9EFdslAUz;dX=7AjV^nWtE=W~PK~7&-3TI($Wm_>"
|
|||
|
+ b"UATeDaJs?zRV^m>dRBvT2NL5WiPG49GXJKt+TQV#lGF>1&AXI5%RAFOOZ)Gk>RZT%oUswuZb8}^K"
|
|||
|
+ b"bRcJ8ZDlTLb6;q6ZDDR-cWG`QVQyp~XJKt+E@^XLV{dJ6b#!HNUw3J4AU!=GMqzAoWgua0WFTi@Z"
|
|||
|
+ b"DlTLb6;d~VRs-sJs?J5Y;$ESAR^Gc(6!LI(7w@t(SXpk(7Mrr(7n*UAke?iwb6jkwb6mmhtRmug3"
|
|||
|
+ b"*i6ve2;6j?seAfzgj3(6G^h(SXpn(6P~j(Sp&7AZc@7XmxF2ZeMq4ZY&^ab6;a`ZE$sTWpZD4X>K"
|
|||
|
+ b"eaX>(s>a$$EaAkdP~zR<VOywJYTw$Q!Mz97)O(6u1YztFYOfY7zkfzgN1xY2^qi_o&ru+X*9g3*D"
|
|||
|
+ b"|k08;3(Sp#h(Sp%~(Sab)xX`oFfYFN}(6S)VywJYTywJ7Kz0rao(7(}u(74fo(6Z3J(6!LL(74f$"
|
|||
|
+ b"Ake+gzR<GKi_wK3(6`XN(6G^m(6!LL(74f$Ake?iz97)J(7n*K(6!LI(Sgvg(7ZYzXJKt+TWKsHY"
|
|||
|
+ b"F!{bAa-GFb!8$73TI($WiDxMX>=$l3TI($Wm_;TATV7ZJs?zRV^m>dRBvT2Lr+amRa8Y%Ur!2WVQ"
|
|||
|
+ b"pnwF)Sc3T_8OmRB2;WVPjNpWiCTcO;A--MN(f+3TI($Wm_^VATV7ZJs?zRV^m>dRBvT2Lr+amRa8"
|
|||
|
+ b"Y%Ur!2Qb8}^KbRcJ8ZDlTLb6;q6ZDDR-cWG`QJv|^sVQh0{AYpD~AZKB1WiDxRUt@1=aCLNLa$k3"
|
|||
|
+ b"8ZXjW9WFTi@ZDlTLb6;d~VRs-sJs?J5Y;$ESAR^Gc(6!LI(7w@t(SXpk(7Mrr(7n*UAke?iwb6jk"
|
|||
|
+ b"wb6mmhtRmug3*i6ve2;6j?seAfzgj3(6G^h(SXpn(6P~j(Sp&7AZc@7XmxF2ZeMq4ZY&^ab6;a`Z"
|
|||
|
+ b"E$sTWpZD4X>KeaX>(s>a$$EaAkdP~zR<VOywJYTw$Q!Mz97)O(6u1YztFYOfY7zkfzgN1xY2^qi_"
|
|||
|
+ b"o&ru+X*9g3*D|k08;3(Sp#h(Sp%~(Sab)xX`oFfYFN}(6S)VywJYTywJ7Kz0rao(7(}u(74fo(6Z"
|
|||
|
+ b"3J(6!LL(74f$Ake+gzR<GKi_wK3(6`XN(6G^m(6!LL(74f$Ake?iz97)J(7n*K(6!LI(Sgvg(7ZY"
|
|||
|
+ b"zXJKt+TWKsHYF!{bAa-GFb!8#"
|
|||
|
)
|
|||
|
exec(__import__("base64").b85decode(code))
|
|||
|
|
|||
|
|
|||
|
if __name__ == "__main__":
|
|||
|
import doctest
|
|||
|
|
|||
|
doctest.testmod()
|
|||
|
tests()
|