start on connect 4

This commit is contained in:
Michael Peters 2024-01-03 09:38:49 -08:00
parent 4afcf8aa94
commit db21ec3ce1
3 changed files with 84 additions and 9 deletions

74
connect4.py Normal file
View File

@ -0,0 +1,74 @@
from __future__ import annotations
import typing as tp
from dataclasses import dataclass
from functools import cache
Player = tp.Literal["X", "O"]
Winner = Player | tp.Literal["draw"]
Space = Player | tp.Literal[" "]
Board = tuple[Space, ...]
Score = int
C4_COLS = 7
C4_ROWS = 6
@dataclass(frozen=True)
class C4GameState:
player: Player
board: Board
def __str__(self) -> str:
b = self.board
return f"───┼───┼───┼───┼───┼───┼───\n".join(
f" {b[i+0]}{b[i+1]}{b[i+2]}{b[i+3]}{b[i+4]}{b[i+5]}{b[i+6]}\n"
for i in range(0, C4_COLS * C4_ROWS, C4_COLS)
)
@dataclass(frozen=True)
class Move:
col: int
def __str__(self) -> str:
return str(self.col + 1)
@cache
def get_valid_moves(state: C4GameState) -> tp.Iterable[Move]:
# NOTE: a slot must not be full if its top row is empty
return tuple(Move(col) for col in range(C4_COLS) if state.board[col] == " ")
@cache
def apply_move(state: C4GameState, move: Move) -> C4GameState:
new_player = "X" if state.player == "O" else "O"
# B)
for i in range(1, C4_ROWS):
idx_above = (i - 1) * C4_COLS + move.col
idx_below = (i - 0) * C4_COLS + move.col
if state.board[idx_below] != " ":
idx = idx_above
break
else:
# bottom
idx = (C4_ROWS - 1) * C4_COLS + move.col
new_board = list(state.board)
new_board[idx] = state.player
new_board = tuple(new_board)
return C4GameState(player=new_player, board=new_board)
@cache
def get_winner(state: C4GameState) -> Winner | None:
pass
START = C4GameState(player="X", board=(" ",) * C4_COLS * C4_ROWS)
if __name__ == "__main__":
print(START)
print(apply_move(START, Move(col=6)))

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
pdbp

View File

@ -33,7 +33,7 @@ class TTTGameState:
player: Player
board: Board
def __str__(self):
def __str__(self) -> str:
b = self.board
return (
f" {b[0]}{b[1]}{b[2]}\n"
@ -52,14 +52,14 @@ class Move:
return str(self.position + 1)
# @cache
@cache
def get_valid_moves(state: TTTGameState) -> tp.Iterable[Move]:
return tuple(
Move(position=i) for i, square in enumerate(state.board) if square == " "
)
# @cache
@cache
def apply_move(state: TTTGameState, move: Move) -> TTTGameState:
new_player = "X" if state.player == "O" else "O"
@ -70,7 +70,7 @@ def apply_move(state: TTTGameState, move: Move) -> TTTGameState:
return TTTGameState(player=new_player, board=new_board)
# @cache
@cache
def get_winner(state: TTTGameState) -> Winner | None:
# abc
# def
@ -103,18 +103,18 @@ def get_winner(state: TTTGameState) -> Winner | None:
if not any(square == " " for square in state.board):
return "draw"
# no winner
# no winner yet
return None
# @cache
@cache
def get_next_states(state: TTTGameState) -> tuple[tuple[Move, TTTGameState], ...]:
assert get_winner(state) is None, "should not be called if game ended"
return tuple((move, apply_move(state, move)) for move in get_valid_moves(state))
# @cache
@util.count_calls
@cache
# @util.count_calls
def get_score(target: Player, state: TTTGameState) -> Score:
winner = get_winner(state)
if winner == target:
@ -132,7 +132,7 @@ def get_score(target: Player, state: TTTGameState) -> Score:
return score
# @cache
@cache
def get_scored_moves(
target: Player, state: TTTGameState
) -> tuple[tuple[Score, Move], ...]: