Fix bugs with en passant and king movement
This commit is contained in:
parent
3f1109307b
commit
032435e10e
|
@ -693,6 +693,7 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
|
|||
of None:
|
||||
# Unreachable
|
||||
discard
|
||||
|
||||
# Some of these checks may seem redundant, but we
|
||||
# perform them because they're less expensive
|
||||
|
||||
|
@ -789,7 +790,11 @@ proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
|||
## Returns the squares that need to be covered to
|
||||
## resolve the current check (including capturing
|
||||
## the checking piece). In case of double check, an
|
||||
## empty list is returned (as the king must move)
|
||||
## empty list is returned (as the king must move).
|
||||
## Note that this function does not handle the special
|
||||
## case of a friendly pawn being able to capture an enemy
|
||||
## pawn that is checking our friendly king via en passant:
|
||||
## that is handled internally by generatePawnMoves
|
||||
var king: Location
|
||||
case color:
|
||||
of White:
|
||||
|
@ -810,12 +815,12 @@ proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
|||
var attack = self.getAttackFor(attacker, king)
|
||||
# Capturing the piece resolves the check
|
||||
result.add(attacker)
|
||||
|
||||
# Blocking the attack is also a viable strategy
|
||||
# (unless the check is from a knight or a pawn,
|
||||
# in which case either the king has to move or
|
||||
# that piece has to be captured, but this is
|
||||
# already implicitly handled by the loop below)
|
||||
|
||||
var location = attacker
|
||||
while location != king:
|
||||
location = location + attack.direction
|
||||
|
@ -830,7 +835,7 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
var
|
||||
piece = self.grid[location.row, location.col]
|
||||
directions: seq[Location] = @[]
|
||||
doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}"
|
||||
assert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}"
|
||||
# Pawns can move forward one square
|
||||
let forward = location + piece.color.topSide()
|
||||
# Only if the square is empty though
|
||||
|
@ -842,24 +847,32 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
# Check that both squares are empty
|
||||
if double.isValid() and self.grid[forward].color == None and self.grid[double].color == None:
|
||||
directions.add(piece.color.doublePush())
|
||||
let enPassantPawn = self.getEnPassantTarget() + piece.color.opposite().topSide()
|
||||
let
|
||||
enPassantTarget = self.getEnPassantTarget()
|
||||
enPassantPawn = enPassantTarget + piece.color.opposite().topSide()
|
||||
topLeft = piece.color.topLeftDiagonal()
|
||||
topRight = piece.color.topRightDiagonal()
|
||||
var enPassantLegal = false
|
||||
# They can also move one square on either of their
|
||||
# forward diagonals, but only for captures and en passant
|
||||
for diagonal in [piece.color.topRightDiagonal(), piece.color.topLeftDiagonal()]:
|
||||
for diagonal in [topRight, topLeft]:
|
||||
let target = location + diagonal
|
||||
if target.isValid():
|
||||
let otherPiece = self.grid[target]
|
||||
if target == self.position.enPassantSquare and self.grid[enPassantPawn].color == piece.color.opposite():
|
||||
if target == enPassantTarget and self.grid[enPassantPawn].color == piece.color.opposite():
|
||||
# En passant may be possible
|
||||
let targetPawn = self.grid[enPassantPawn]
|
||||
# Remove both pieces and see if the king ends up in check
|
||||
# Simulate the move and see if the king ends up in check
|
||||
self.removePiece(enPassantPawn, attack=false)
|
||||
self.removePiece(location, attack=false)
|
||||
self.spawnPiece(target, piece)
|
||||
self.updateAttackedSquares()
|
||||
if not self.inCheck(piece.color):
|
||||
# King is not in check after en passant: move is legal
|
||||
directions.add(diagonal)
|
||||
enPassantLegal = true
|
||||
# Reset what we just did and reupdate the attack metadata
|
||||
self.removePiece(target, attack=false)
|
||||
self.spawnPiece(location, piece)
|
||||
self.spawnPiece(enPassantPawn, targetPawn)
|
||||
self.updateAttackedSquares()
|
||||
|
@ -875,7 +888,13 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
newDirections.add(direction)
|
||||
directions = newDirections
|
||||
let checked = self.inCheck()
|
||||
let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color)
|
||||
var resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color)
|
||||
# If the check comes from a pawn and en passant is legal and would capture it,
|
||||
# we add that to the list of possible check resolutions
|
||||
if checked and enPassantLegal:
|
||||
let attackingPawn = self.getAttackFor(enPassantPawn, self.getKing(piece.color))
|
||||
if attackingPawn.source == enPassantPawn:
|
||||
resolutions.add(enPassantTarget)
|
||||
var targetPiece: Piece
|
||||
for direction in directions:
|
||||
let target = location + direction
|
||||
|
@ -900,7 +919,7 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
## Generates moves for the sliding piece in the given location
|
||||
let piece = self.grid[location.row, location.col]
|
||||
doAssert piece.kind in [Bishop, Rook, Queen], &"generateSlidingMoves called on a {piece.kind}"
|
||||
assert piece.kind in [Bishop, Rook, Queen], &"generateSlidingMoves called on a {piece.kind}"
|
||||
var directions: seq[Location] = @[]
|
||||
|
||||
# Only check in the right directions for the chosen piece
|
||||
|
@ -963,7 +982,7 @@ proc generateKingMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
## Generates moves for the king in the given location
|
||||
var
|
||||
piece = self.grid[location.row, location.col]
|
||||
doAssert piece.kind == King, &"generateKingMoves called on a {piece.kind}"
|
||||
assert piece.kind == King, &"generateKingMoves called on a {piece.kind}"
|
||||
var directions: seq[Location] = @[piece.color.topLeftDiagonal(),
|
||||
piece.color.topRightDiagonal(),
|
||||
piece.color.bottomRightDiagonal(),
|
||||
|
@ -1008,7 +1027,7 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
## Generates moves for the knight in the given location
|
||||
var
|
||||
piece = self.grid[location.row, location.col]
|
||||
doAssert piece.kind == Knight, &"generateKnightMoves called on a {piece.kind}"
|
||||
assert piece.kind == Knight, &"generateKnightMoves called on a {piece.kind}"
|
||||
var directions: seq[Location] = @[piece.color.bottomLeftKnightMove(),
|
||||
piece.color.bottomRightKnightMove(),
|
||||
piece.color.topLeftKnightMove(),
|
||||
|
@ -1124,8 +1143,9 @@ proc getAttacks*(self: ChessBoard, loc: Location): Attacked =
|
|||
|
||||
|
||||
proc getAttackFor*(self: ChessBoard, source, target: Location): tuple[source, target, direction: Location] =
|
||||
## Returns the first attack of the piece in the given
|
||||
## source location that also attacks the target location
|
||||
## Returns the first attack from the given source to the
|
||||
## given target square
|
||||
result = (emptyLocation(), emptyLocation(), emptyLocation())
|
||||
let piece = self.grid[source.row, source.col]
|
||||
case piece.color:
|
||||
of Black:
|
||||
|
@ -1158,6 +1178,10 @@ func addAttack(self: ChessBoard, attack: tuple[source, target, direction: Locati
|
|||
|
||||
|
||||
proc getPinnedDirections(self: ChessBoard, loc: Location): seq[Location] =
|
||||
## Returns all the directions along which the piece in the given
|
||||
## location is pinned. If the result is non-empty, the piece at
|
||||
## the given location is only allowed to move along the directions
|
||||
## returned by this function
|
||||
let piece = self.grid[loc.row, loc.col]
|
||||
case piece.color:
|
||||
of None:
|
||||
|
@ -1176,8 +1200,8 @@ proc updatePawnAttacks(self: ChessBoard) =
|
|||
## Internal helper of updateAttackedSquares
|
||||
for loc in self.position.pieces.white.pawns:
|
||||
# Pawns are special in how they capture (i.e. the
|
||||
# squares they can move to do not match the squares
|
||||
# they can capture on. Sneaky fucks)
|
||||
# squares they can regularly move to do not match
|
||||
# the squares they can capture on. Sneaky fucks)
|
||||
self.addAttack((loc, loc + White.topRightDiagonal(), White.topRightDiagonal()), White)
|
||||
self.addAttack((loc, loc + White.topLeftDiagonal(), White.topLeftDiagonal()), White)
|
||||
# We do the same thing for black
|
||||
|
@ -1193,11 +1217,20 @@ proc updateKingAttacks(self: ChessBoard) =
|
|||
self.addAttack((king, king + White.topLeftDiagonal(), White.topLeftDiagonal()), White)
|
||||
self.addAttack((king, king + White.bottomLeftDiagonal(), White.bottomLeftDiagonal()), White)
|
||||
self.addAttack((king, king + White.bottomRightDiagonal(), White.bottomRightDiagonal()), White)
|
||||
self.addAttack((king, king + White.leftSide(), White.leftSide()), White)
|
||||
self.addAttack((king, king + White.rightSide(), White.rightSide()), White)
|
||||
self.addAttack((king, king + White.bottomSide(), White.bottomSide()), White)
|
||||
self.addAttack((king, king + White.topSide(), White.topSide()), White)
|
||||
|
||||
king = self.position.pieces.black.king
|
||||
self.addAttack((king, king + Black.topRightDiagonal(), Black.topRightDiagonal()), Black)
|
||||
self.addAttack((king, king + Black.topLeftDiagonal(), Black.topLeftDiagonal()), Black)
|
||||
self.addAttack((king, king + Black.bottomLeftDiagonal(), Black.bottomLeftDiagonal()), Black)
|
||||
self.addAttack((king, king + Black.bottomRightDiagonal(), Black.bottomRightDiagonal()), Black)
|
||||
self.addAttack((king, king + Black.leftSide(), Black.leftSide()), Black)
|
||||
self.addAttack((king, king + Black.rightSide(), Black.rightSide()), Black)
|
||||
self.addAttack((king, king + Black.bottomSide(), Black.bottomSide()), Black)
|
||||
self.addAttack((king, king + Black.topSide(), Black.topSide()), Black)
|
||||
|
||||
|
||||
proc updateKnightAttacks(self: ChessBoard) =
|
||||
|
@ -1284,7 +1317,7 @@ proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked
|
|||
# we allow it to move two squares as well
|
||||
if square.col == loc.col:
|
||||
# The pawn can only push two squares if it's being pinned from the
|
||||
# top
|
||||
# top side (relative to the pawn itself)
|
||||
result.pins.add((loc, square, otherPiece.color.doublePush()))
|
||||
else:
|
||||
break
|
||||
|
@ -1300,7 +1333,6 @@ proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked
|
|||
|
||||
proc updateSlidingAttacks(self: ChessBoard) =
|
||||
## Internal helper of updateAttackedSquares
|
||||
|
||||
var data: tuple[attacks: Attacked, pins: Attacked]
|
||||
for loc in self.position.pieces.white.bishops:
|
||||
data = self.getSlidingAttacks(loc)
|
||||
|
@ -1841,6 +1873,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
if not bulk:
|
||||
if len(moves) == 0 and self.inCheck():
|
||||
result.checkmates = 1
|
||||
# TODO: Should we count stalemates?
|
||||
if ply == 0:
|
||||
result.nodes = 1
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue