preparing for introduction of castling, en passant and promotion
This commit is contained in:
+84
-15
@@ -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:
|
expected_king_pos = BoardPos((row_height, 4))
|
||||||
row_height = 7
|
|
||||||
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:
|
||||||
@@ -329,6 +347,57 @@ class ChessBoard:
|
|||||||
if piece.type == PieceType.KING and piece.color == color:
|
if piece.type == PieceType.KING and piece.color == color:
|
||||||
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]:
|
||||||
|
|||||||
Reference in New Issue
Block a user