preparing for introduction of castling, en passant and promotion
This commit is contained in:
+80
-11
@@ -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
|
||||
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)]
|
||||
|
||||
# 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:
|
||||
@@ -330,6 +348,57 @@ class ChessBoard:
|
||||
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]:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user