games/play.py
2023-06-07 22:23:34 -07:00

93 lines
2.0 KiB
Python

from __future__ import annotations
import random
import typing as tp
from abc import ABC, abstractmethod
from operator import itemgetter
class QuitException(Exception):
pass
class GameState(tp.Protocol):
player: tp.Any
class Move(tp.Protocol):
pass
class Player(tp.Protocol):
pass
Score = int
class GameModule(tp.Protocol):
@staticmethod
def get_valid_moves(state: GameState) -> tp.Iterable[Move]:
...
@staticmethod
def apply_move(state: GameState, move: Move) -> GameState:
...
@staticmethod
def get_winner(state: GameState) -> Player | tp.Literal["draw"] | None:
...
@staticmethod
def get_next_states(
state: GameState,
) -> tuple[tuple[Move, GameState], ...]:
...
@staticmethod
def get_score(target: Player, state: GameState) -> Player:
...
@staticmethod
def get_scored_moves(
target: Player, state: GameState
) -> tuple[tuple[Score, Move], ...]:
...
@staticmethod
def get_human_move(state: GameState) -> Move:
...
def _get_ai_move(mod: GameModule, state: GameState) -> Move:
scored_moves = list(mod.get_scored_moves(state.player, state))
random.shuffle(scored_moves)
_, best_move = max(scored_moves, key=itemgetter(0))
return best_move
def play_human(mod: GameModule, human_player: Player, state: GameState) -> None:
try:
while not (winner := mod.get_winner(state)):
print(state)
if state.player == human_player:
move = mod.get_human_move(state)
else:
move = _get_ai_move(mod, state)
print(f"{state.player} plays {move}")
print()
state = mod.apply_move(state, move)
print(state)
if winner == "draw":
print("draw")
else:
print(f"{winner} wins")
except QuitException:
print("quit")
if __name__ == "__main__":
import tictactoe as ttt
play_human(ttt, "X", ttt.REAL)