This repository has been archived on 2026-03-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
cau-praktikum/app/chess_sim/test.py
T
2026-03-03 20:06:50 +01:00

176 lines
6.2 KiB
Python

from enum import Enum, auto
from dataclasses import dataclass
from typing import Iterator, Optional, List, Tuple
import random
from game_board import ChessBoard, MoveType, Color, PieceType, BoardMove, BoardPos
def play_random_game(board: Optional[ChessBoard] = None, max_moves: int = 400, verbose: bool = False) -> Tuple[ ChessBoard, int]:
if board is None:
board = ChessBoard.init_default()
current = Color.WHITE # white moves first
moves_played = 0
while moves_played < max_moves:
legal = board.generate_moves(current)
if not legal:
# no legal moves
if verbose:
print(f"no legal moves for {current.name} after {moves_played} moves")
return board, moves_played
# prefer castling for debugging
castling_moves = [
m for m in legal
if m.move_type in (MoveType.CASTLING_KINGSIDE, MoveType.CASTLING_QUEENSIDE)
]
if castling_moves:
move = castling_moves[0]
else:
move = random.choice(legal)
ok = board.make_move(move, current)
# debug assert
if not ok:
if verbose:
print(f"make_move returned False for move {move} by {current}")
return board, moves_played
moves_played += 1
if verbose:
print(f"{moves_played:03d}: {current.name} played {move}\nboard:\n{board}")
# check whether the captured piece was a king
_, captured = board.move_history[-1]
if captured is not None and captured.type == PieceType.KING:
if verbose:
print(f"king captured by {current.name} on move {moves_played}.")
raise RuntimeWarning("this should not be possible")
return board, moves_played
current = current.opposite
if verbose:
print(f"reached move limit ({max_moves})")
return board, moves_played
def run_random_games(n: int = 20, max_moves: int = 200, verbose: bool = False):
for i in range(n):
final_board, moves = play_random_game(None, max_moves=max_moves, verbose=verbose)
if verbose:
print(f"game {i+1}: moves={moves}\nfinal_board:\n{final_board}")
return
def test_castling_kingside_both_sides():
board = ChessBoard.init_default()
# rewrite with boardmove from string
seq = [
(BoardMove(BoardPos((7, 6)), BoardPos((5, 5))), Color.WHITE),
(BoardMove(BoardPos((0, 1)), BoardPos((2, 2))), Color.BLACK),
# clear white pawn allows bishop moving
(BoardMove(BoardPos((6, 6)), BoardPos((5, 6))), Color.WHITE),
# move bishop
(BoardMove(BoardPos((0, 6)), BoardPos((2, 5))), Color.BLACK),
(BoardMove(BoardPos((7, 5)), BoardPos((5, 7))), Color.WHITE),
# clear black pawn for bishop
(BoardMove(BoardPos((1, 4)), BoardPos((2, 4))), Color.BLACK),
# moving black bishop
(BoardMove(BoardPos((0, 5)), BoardPos((1, 4))), Color.BLACK),
# white castle kingside
(BoardMove(BoardPos((7, 4)), BoardPos((7, 6)), MoveType.CASTLING_KINGSIDE), Color.WHITE),
# black castle kingside
(BoardMove(BoardPos((0, 4)), BoardPos((0, 6)), MoveType.CASTLING_KINGSIDE), Color.BLACK),
]
for move, color in seq:
ok = board.make_move(move, color)
assert ok, f"move {move} by {color} failed unexpectedly on board:\n{board}"
# validate castle
wk = board.pos_of_king(Color.WHITE)
assert wk == BoardPos((7, 6)), f"white king expected at (7,6), found {wk}"
wf = board.fields[7][5].piece
assert wf is not None and wf.type == PieceType.ROOK and wf.color == Color.WHITE, "white rook not on f1 after castling"
bk = board.pos_of_king(Color.BLACK)
assert bk == BoardPos((0, 6)), f"black king expected at (0,6), found {bk}"
bf = board.fields[0][5].piece
assert bf is not None and bf.type == PieceType.ROOK and bf.color == Color.BLACK, "black rook not on f8 after castling"
print("test_castling_kingside_both_sides: passed")
# used only for testing purposes
def main():
default_brd = ChessBoard.init_default()
default_brd.generate_moves
# standardized expected string for initial position
expected_start = (
"rnbqkbnr\n"
"pppppppp\n"
"........\n"
"........\n"
"........\n"
"........\n"
"PPPPPPPP\n"
"RNBQKBNR"
)
# test string representation of board
actual_start = str(default_brd)
if actual_start != expected_start:
raise AssertionError(f"initial board mismatch:\nexpected:\n{expected_start}\n\nactual:\n{actual_start}")
# test num moves
mvs = default_brd.moves_unfiltered(Color.BLACK)
expected_move_count = 20 # 16 pawn moves and 4 knight
if len(mvs) != expected_move_count:
raise AssertionError(f"initial move count for black mismatch: expected {expected_move_count}, got {len(mvs)}")
mvs = default_brd.moves_unfiltered(Color.WHITE)
expected_move_count = 20 # 16 pawn moves and 4 knight
if len(mvs) != expected_move_count:
raise AssertionError(f"Initial move count for white mismatch: expected {expected_move_count}, got {len(mvs)}")
test_castling_kingside_both_sides()
run_random_games(verbose=False)
board = ChessBoard.init_default()
# helper to create boardmove from two-square string
def bm(s):
return BoardMove(
ChessBoard.algebraic_to_pos(s[0:2]),
ChessBoard.algebraic_to_pos(s[2:4])
)
# perform four cycles of the knight shuffle
# Nf3-Nf6-Ng1-Ng8 and returns to the starting position.
seq = []
for _ in range(4):
seq.extend([
(bm("g1f3"), Color.WHITE),
(bm("g8f6"), Color.BLACK),
(bm("f3g1"), Color.WHITE),
(bm("f6g8"), Color.BLACK),
])
for move, color in seq:
ok = board.make_move(move, color)
assert ok, "repetition test move failed"
rep = board.highest_repetiton_amount()
assert rep >= 5, f"expected at least 5 repetitions, got {rep}"
assert board.is_fivefold_repetition(), "board should report fivefold repetition"
print(f"all tests passed")
if __name__ == '__main__':
main()