Skip to content

Instantly share code, notes, and snippets.

@ianliu
Created August 9, 2021 23:49
Show Gist options
  • Save ianliu/3e0e829eb1a56b093564a61b7fedf92f to your computer and use it in GitHub Desktop.
Save ianliu/3e0e829eb1a56b093564a61b7fedf92f to your computer and use it in GitHub Desktop.
from __future__ import annotations
from dataclasses import dataclass, replace
from typing import Callable, Generic, Literal, Optional, TypeVar, Union, cast
Player = Union[Literal['X'], Literal['O']]
Piece = Union[Player, Literal[' ']]
@dataclass
class Winner:
name: Player
@dataclass
class NextPlayer:
name: Player
@dataclass
class Board:
b00: Piece = ' '
b01: Piece = ' '
b02: Piece = ' '
b10: Piece = ' '
b11: Piece = ' '
b12: Piece = ' '
b20: Piece = ' '
b21: Piece = ' '
b22: Piece = ' '
def update(self, row: int, col: int, p: Player) -> Optional[Board]:
if 0 <= row <= 2 and 0 <= col <= 2:
attr = f'b{row}{col}'
if getattr(self, attr) == ' ':
return replace(self, **{attr: p})
@dataclass
class Game:
player: Union[Winner, NextPlayer]
errors: list[str]
board: Board
@dataclass
class Move:
row: int
col: int
@dataclass
class Restart:
pass
Msg = Union[Move, Restart]
def check_winner(b: Board) -> Optional[Player]:
checks = [
# lines
set([b.b00, b.b01, b.b02]),
set([b.b10, b.b11, b.b12]),
set([b.b20, b.b21, b.b22]),
# columns
set([b.b00, b.b10, b.b20]),
set([b.b01, b.b11, b.b21]),
set([b.b02, b.b12, b.b22]),
# diagonals
set([b.b00, b.b11, b.b22]),
set([b.b02, b.b11, b.b20]),
]
for check in checks:
if len(check) == 1 and ' ' not in check:
return cast(Player, check.pop())
return None
def update_board(game: Game, m: Move) -> Game:
if isinstance(game.player, Winner):
raise TypeError()
curr_player = game.player
new_board = game.board.update(m.row, m.col, curr_player.name)
if new_board is None:
return Game(game.player, ['invalid move, play again'], game.board)
winner = check_winner(new_board)
if winner is None:
next_player = NextPlayer('X' if game.player.name == 'O' else 'O')
return Game(next_player, [], new_board)
else:
return Game(Winner(winner), [], new_board)
def update(game: Game, msg: Msg) -> Game:
if isinstance(msg, Restart):
return Game(NextPlayer('X'), [], Board())
elif isinstance(msg, Move):
return update_board(game, msg)
def view(game: Game, send: Callable[[Msg], None]):
print((
"{b.b00}│{b.b01}│{b.b02}\n"
"─┼─┼─\n"
"{b.b10}│{b.b11}│{b.b12}\n"
"─┼─┼─\n"
"{b.b20}│{b.b21}│{b.b22}\n"
).format(b=game.board))
for msg in game.errors:
print(f" ** {msg} **")
print()
if isinstance(game.player, Winner):
print(f"Winner is {game.player.name}!")
input("Press any key to replay")
send(Restart())
elif isinstance(game.player, NextPlayer):
move = input(f"Player {game.player.name} move: ")
i, j = map(int, move.split())
send(Move(i, j))
A = TypeVar("A")
B = TypeVar("B")
@dataclass
class Program(Generic[A, B]):
init: A
view: Callable[[A, Callable[[B], None]], None]
update: Callable[[A, B], A]
def run(self):
while True:
print(chr(27) + "[2J")
print(chr(27) + "[H")
self.view(self.init, self.send)
def send(self, msg: B):
self.init = self.update(self.init, msg)
if __name__ == '__main__':
Program(
init=Game(NextPlayer('X'), [], Board()),
view=view,
update=update).run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment