start on connect 4
This commit is contained in:
parent
4afcf8aa94
commit
db21ec3ce1
74
connect4.py
Normal file
74
connect4.py
Normal 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
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
pdbp
|
18
tictactoe.py
18
tictactoe.py
@ -33,7 +33,7 @@ class TTTGameState:
|
|||||||
player: Player
|
player: Player
|
||||||
board: Board
|
board: Board
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
b = self.board
|
b = self.board
|
||||||
return (
|
return (
|
||||||
f" {b[0]} │ {b[1]} │ {b[2]}\n"
|
f" {b[0]} │ {b[1]} │ {b[2]}\n"
|
||||||
@ -52,14 +52,14 @@ class Move:
|
|||||||
return str(self.position + 1)
|
return str(self.position + 1)
|
||||||
|
|
||||||
|
|
||||||
# @cache
|
@cache
|
||||||
def get_valid_moves(state: TTTGameState) -> tp.Iterable[Move]:
|
def get_valid_moves(state: TTTGameState) -> tp.Iterable[Move]:
|
||||||
return tuple(
|
return tuple(
|
||||||
Move(position=i) for i, square in enumerate(state.board) if square == " "
|
Move(position=i) for i, square in enumerate(state.board) if square == " "
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# @cache
|
@cache
|
||||||
def apply_move(state: TTTGameState, move: Move) -> TTTGameState:
|
def apply_move(state: TTTGameState, move: Move) -> TTTGameState:
|
||||||
new_player = "X" if state.player == "O" else "O"
|
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)
|
return TTTGameState(player=new_player, board=new_board)
|
||||||
|
|
||||||
|
|
||||||
# @cache
|
@cache
|
||||||
def get_winner(state: TTTGameState) -> Winner | None:
|
def get_winner(state: TTTGameState) -> Winner | None:
|
||||||
# abc
|
# abc
|
||||||
# def
|
# def
|
||||||
@ -103,18 +103,18 @@ def get_winner(state: TTTGameState) -> Winner | None:
|
|||||||
if not any(square == " " for square in state.board):
|
if not any(square == " " for square in state.board):
|
||||||
return "draw"
|
return "draw"
|
||||||
|
|
||||||
# no winner
|
# no winner yet
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# @cache
|
@cache
|
||||||
def get_next_states(state: TTTGameState) -> tuple[tuple[Move, TTTGameState], ...]:
|
def get_next_states(state: TTTGameState) -> tuple[tuple[Move, TTTGameState], ...]:
|
||||||
assert get_winner(state) is None, "should not be called if game ended"
|
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))
|
return tuple((move, apply_move(state, move)) for move in get_valid_moves(state))
|
||||||
|
|
||||||
|
|
||||||
# @cache
|
@cache
|
||||||
@util.count_calls
|
# @util.count_calls
|
||||||
def get_score(target: Player, state: TTTGameState) -> Score:
|
def get_score(target: Player, state: TTTGameState) -> Score:
|
||||||
winner = get_winner(state)
|
winner = get_winner(state)
|
||||||
if winner == target:
|
if winner == target:
|
||||||
@ -132,7 +132,7 @@ def get_score(target: Player, state: TTTGameState) -> Score:
|
|||||||
return score
|
return score
|
||||||
|
|
||||||
|
|
||||||
# @cache
|
@cache
|
||||||
def get_scored_moves(
|
def get_scored_moves(
|
||||||
target: Player, state: TTTGameState
|
target: Player, state: TTTGameState
|
||||||
) -> tuple[tuple[Score, Move], ...]:
|
) -> tuple[tuple[Score, Move], ...]:
|
||||||
|
Loading…
Reference in New Issue
Block a user