py_stepik/mod_oop/3.x_01_tictactoe.py

330 lines
15 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/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()