From 7f866282ea210f575253013ec844a7e42f6aff83 Mon Sep 17 00:00:00 2001 From: simoncreates Date: Thu, 26 Feb 2026 00:50:32 +0100 Subject: [PATCH] preparing for introduction of castling, en passant and promotion --- app/chess_sim/test.py | 99 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/app/chess_sim/test.py b/app/chess_sim/test.py index 41ca2a2..5d4b88d 100644 --- a/app/chess_sim/test.py +++ b/app/chess_sim/test.py @@ -65,10 +65,19 @@ class Piece: return f"Piece({self.type.name},{self.color.name})" +class MoveType(Enum): + NORMAL = auto() + CASTLING_KINGSIDE = auto() #todo: implement + CASTLING_QUEENSIDE = auto() #todo: implement + EN_PASSANT = auto() #todo: implement + PROMOTION = auto() #todo: implement + @dataclass class BoardMove: m_from: BoardPos m_to: BoardPos + move_type: MoveType = MoveType.NORMAL #todo: implement and handle + promotion_piece: Optional[PieceType] = None #todo: implement and handle def __str__(self) -> str: return f"{self.m_from}->{self.m_to}" @@ -268,26 +277,35 @@ class ChessBoard: # manually adding castling - row_height = 0 - if color == Color.BLACK: - row_height = 7 - expected_king_pos = BoardPos((row_height,4)) + row_height = 7 if color == Color.WHITE else 0 + expected_king_pos = BoardPos((row_height, 4)) - # if the king has moved, return as usual + # if king moved if self.has_piece_moved(expected_king_pos): return moves - expected_lhs_rook_pos = BoardPos((row_height,0)) - expected_rhs_rook_pos = BoardPos((row_height,7)) - lhs_pieces_inbetween = [BoardPos((row_height, i)) for i in range(1, 3)] - rhs_pieces_inbetween = [BoardPos((row_height, i)) for i in range(5, 6)] + expected_lhs_rook_pos = BoardPos((row_height, 0)) + expected_rhs_rook_pos = BoardPos((row_height, 7)) - # make sure the pieces inbetween are gone and the rook has not moved - if not self.has_piece_moved(expected_lhs_rook_pos) and self.are_pieces_none(lhs_pieces_inbetween): - #todo: allow castling here - if not self.has_piece_moved(expected_rhs_rook_pos) and self.are_pieces_none(rhs_pieces_inbetween): - #todo: allow castling here - + # squares that must be empty between rook and king + lhs_pieces_inbetween = [BoardPos((row_height, i)) for i in range(1, 4)] # cols 1,2,3 + rhs_pieces_inbetween = [BoardPos((row_height, i)) for i in range(5, 7)] # cols 5,6 + + # squares that must not be attacked by the opponent + lhs_fields_attacked = [BoardPos((row_height, i)) for i in (4, 3, 2)] + rhs_fields_attacked = [BoardPos((row_height, i)) for i in (4, 5, 6)] + + if (not self.has_piece_moved(expected_lhs_rook_pos) + and self.are_pieces_none(lhs_pieces_inbetween) + and not self.are_fields_attacked(lhs_fields_attacked, color.opposite)): + # todo: allow castling here + pass + + if (not self.has_piece_moved(expected_rhs_rook_pos) + and self.are_pieces_none(rhs_pieces_inbetween) + and not self.are_fields_attacked(rhs_fields_attacked, color.opposite)): + # todo: allow castling here + pass return moves def has_piece_moved(self, pos: BoardPos) -> bool: @@ -329,6 +347,57 @@ class ChessBoard: if piece.type == PieceType.KING and piece.color == color: return pos raise ValueError("player has no king") + + # returns true if one field is actively being attacked + def are_fields_attacked(self, fields: List[BoardPos], attacker_color: Color) -> bool: + for field in fields: + if self.is_field_attacked(field, attacker_color): + return True + return False + + # checks if the field is being actively attacked by any of the pieces of the attacker color + # this "complicated" implementation was necessary to not rely on recursive calls + def is_field_attacked(self, pos: BoardPos, attacker_color: Color) -> bool: + # iterate opponent pieces and check whether they attack + for attacker_pos, attacker in self.iter_pieces(): + if attacker.color != attacker_color: + continue + + rays = self.moves_unchecked(attacker, attacker_pos) + + if attacker.type == PieceType.PAWN: + # pawn attacks are the pawn capture rays only + forward = -1 if attacker.color == Color.WHITE else 1 + for dc in (-1, 1): + rr = attacker_pos.x + forward + cc = attacker_pos.y + dc + if not self.on_board(rr, cc): + continue + if BoardPos((rr, cc)) == pos: + return True + continue + + if attacker.type in (PieceType.KNIGHT, PieceType.KING): + # non-sliding + for (rr, cc) in rays[0]: + if not self.on_board(rr, cc): + continue + if BoardPos((rr, cc)) == pos: + return True + continue + + # sliding pieces + for ray in rays: + for (rr, cc) in ray: + if not self.on_board(rr, cc): + break + if BoardPos((rr, cc)) == pos: + return True + # stop at first occupied square (blocker) + if self.fields[rr][cc].piece is not None: + break + + return False # if a player does not have any moves, it has lost def generate_moves(self, color: Color) -> List[BoardMove]: