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()