93 lines
2.0 KiB
Python
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)
|