preparing for introduction of castling, en passant and promotion

This commit is contained in:
simoncreates
2026-02-26 00:50:32 +01:00
parent 61a531c6f2
commit 7f866282ea
+80 -11
View File
@@ -65,10 +65,19 @@ class Piece:
return f"Piece({self.type.name},{self.color.name})" 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 @dataclass
class BoardMove: class BoardMove:
m_from: BoardPos m_from: BoardPos
m_to: 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: def __str__(self) -> str:
return f"{self.m_from}->{self.m_to}" return f"{self.m_from}->{self.m_to}"
@@ -268,26 +277,35 @@ class ChessBoard:
# manually adding castling # manually adding castling
row_height = 0 row_height = 7 if color == Color.WHITE else 0
if color == Color.BLACK:
row_height = 7
expected_king_pos = BoardPos((row_height, 4)) 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): if self.has_piece_moved(expected_king_pos):
return moves return moves
expected_lhs_rook_pos = BoardPos((row_height, 0)) expected_lhs_rook_pos = BoardPos((row_height, 0))
expected_rhs_rook_pos = BoardPos((row_height, 7)) 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)]
# make sure the pieces inbetween are gone and the rook has not moved # squares that must be empty between rook and king
if not self.has_piece_moved(expected_lhs_rook_pos) and self.are_pieces_none(lhs_pieces_inbetween): lhs_pieces_inbetween = [BoardPos((row_height, i)) for i in range(1, 4)] # cols 1,2,3
#todo: allow castling here rhs_pieces_inbetween = [BoardPos((row_height, i)) for i in range(5, 7)] # cols 5,6
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 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 return moves
def has_piece_moved(self, pos: BoardPos) -> bool: def has_piece_moved(self, pos: BoardPos) -> bool:
@@ -330,6 +348,57 @@ class ChessBoard:
return pos return pos
raise ValueError("player has no king") 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 # if a player does not have any moves, it has lost
def generate_moves(self, color: Color) -> List[BoardMove]: def generate_moves(self, color: Color) -> List[BoardMove]: