diff --git a/mod_oop/3.x_01_tictactoe.py b/mod_oop/3.x_01_tictactoe.py new file mode 100644 index 0000000..819ce0c --- /dev/null +++ b/mod_oop/3.x_01_tictactoe.py @@ -0,0 +1,329 @@ +""" + 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_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{$@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"ve32BfY80rzRI(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"+TQMvkFdRBvT2NL5WiPG49oAR^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?izRUATeDaJv|^)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(7MpLAkehXzRm+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" + + 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=$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