Fix bugs with en passant and king movement
This commit is contained in:
parent
f75f7533f5
commit
2ada052460
|
@ -693,6 +693,7 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
|
||||||
of None:
|
of None:
|
||||||
# Unreachable
|
# Unreachable
|
||||||
discard
|
discard
|
||||||
|
|
||||||
# Some of these checks may seem redundant, but we
|
# Some of these checks may seem redundant, but we
|
||||||
# perform them because they're less expensive
|
# 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
|
## Returns the squares that need to be covered to
|
||||||
## resolve the current check (including capturing
|
## resolve the current check (including capturing
|
||||||
## the checking piece). In case of double check, an
|
## 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
|
var king: Location
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
|
@ -810,12 +815,12 @@ proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
||||||
var attack = self.getAttackFor(attacker, king)
|
var attack = self.getAttackFor(attacker, king)
|
||||||
# Capturing the piece resolves the check
|
# Capturing the piece resolves the check
|
||||||
result.add(attacker)
|
result.add(attacker)
|
||||||
|
|
||||||
# Blocking the attack is also a viable strategy
|
# Blocking the attack is also a viable strategy
|
||||||
# (unless the check is from a knight or a pawn,
|
# (unless the check is from a knight or a pawn,
|
||||||
# in which case either the king has to move or
|
# in which case either the king has to move or
|
||||||
# that piece has to be captured, but this is
|
# that piece has to be captured, but this is
|
||||||
# already implicitly handled by the loop below)
|
# already implicitly handled by the loop below)
|
||||||
|
|
||||||
var location = attacker
|
var location = attacker
|
||||||
while location != king:
|
while location != king:
|
||||||
location = location + attack.direction
|
location = location + attack.direction
|
||||||
|
@ -830,7 +835,7 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
var
|
var
|
||||||
piece = self.grid[location.row, location.col]
|
piece = self.grid[location.row, location.col]
|
||||||
directions: seq[Location] = @[]
|
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
|
# Pawns can move forward one square
|
||||||
let forward = location + piece.color.topSide()
|
let forward = location + piece.color.topSide()
|
||||||
# Only if the square is empty though
|
# 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
|
# Check that both squares are empty
|
||||||
if double.isValid() and self.grid[forward].color == None and self.grid[double].color == None:
|
if double.isValid() and self.grid[forward].color == None and self.grid[double].color == None:
|
||||||
directions.add(piece.color.doublePush())
|
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
|
# They can also move one square on either of their
|
||||||
# forward diagonals, but only for captures and en passant
|
# 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
|
let target = location + diagonal
|
||||||
if target.isValid():
|
if target.isValid():
|
||||||
let otherPiece = self.grid[target]
|
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
|
# En passant may be possible
|
||||||
let targetPawn = self.grid[enPassantPawn]
|
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(enPassantPawn, attack=false)
|
||||||
self.removePiece(location, attack=false)
|
self.removePiece(location, attack=false)
|
||||||
|
self.spawnPiece(target, piece)
|
||||||
self.updateAttackedSquares()
|
self.updateAttackedSquares()
|
||||||
if not self.inCheck(piece.color):
|
if not self.inCheck(piece.color):
|
||||||
# King is not in check after en passant: move is legal
|
# King is not in check after en passant: move is legal
|
||||||
directions.add(diagonal)
|
directions.add(diagonal)
|
||||||
|
enPassantLegal = true
|
||||||
# Reset what we just did and reupdate the attack metadata
|
# Reset what we just did and reupdate the attack metadata
|
||||||
|
self.removePiece(target, attack=false)
|
||||||
self.spawnPiece(location, piece)
|
self.spawnPiece(location, piece)
|
||||||
self.spawnPiece(enPassantPawn, targetPawn)
|
self.spawnPiece(enPassantPawn, targetPawn)
|
||||||
self.updateAttackedSquares()
|
self.updateAttackedSquares()
|
||||||
|
@ -875,7 +888,13 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
newDirections.add(direction)
|
newDirections.add(direction)
|
||||||
directions = newDirections
|
directions = newDirections
|
||||||
let checked = self.inCheck()
|
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
|
var targetPiece: Piece
|
||||||
for direction in directions:
|
for direction in directions:
|
||||||
let target = location + direction
|
let target = location + direction
|
||||||
|
@ -900,7 +919,7 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
## Generates moves for the sliding piece in the given location
|
## Generates moves for the sliding piece in the given location
|
||||||
let piece = self.grid[location.row, location.col]
|
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] = @[]
|
var directions: seq[Location] = @[]
|
||||||
|
|
||||||
# Only check in the right directions for the chosen piece
|
# 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
|
## Generates moves for the king in the given location
|
||||||
var
|
var
|
||||||
piece = self.grid[location.row, location.col]
|
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(),
|
var directions: seq[Location] = @[piece.color.topLeftDiagonal(),
|
||||||
piece.color.topRightDiagonal(),
|
piece.color.topRightDiagonal(),
|
||||||
piece.color.bottomRightDiagonal(),
|
piece.color.bottomRightDiagonal(),
|
||||||
|
@ -1008,7 +1027,7 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
## Generates moves for the knight in the given location
|
## Generates moves for the knight in the given location
|
||||||
var
|
var
|
||||||
piece = self.grid[location.row, location.col]
|
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(),
|
var directions: seq[Location] = @[piece.color.bottomLeftKnightMove(),
|
||||||
piece.color.bottomRightKnightMove(),
|
piece.color.bottomRightKnightMove(),
|
||||||
piece.color.topLeftKnightMove(),
|
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] =
|
proc getAttackFor*(self: ChessBoard, source, target: Location): tuple[source, target, direction: Location] =
|
||||||
## Returns the first attack of the piece in the given
|
## Returns the first attack from the given source to the
|
||||||
## source location that also attacks the target location
|
## given target square
|
||||||
|
result = (emptyLocation(), emptyLocation(), emptyLocation())
|
||||||
let piece = self.grid[source.row, source.col]
|
let piece = self.grid[source.row, source.col]
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of Black:
|
of Black:
|
||||||
|
@ -1158,6 +1178,10 @@ func addAttack(self: ChessBoard, attack: tuple[source, target, direction: Locati
|
||||||
|
|
||||||
|
|
||||||
proc getPinnedDirections(self: ChessBoard, loc: Location): seq[Location] =
|
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]
|
let piece = self.grid[loc.row, loc.col]
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of None:
|
of None:
|
||||||
|
@ -1176,8 +1200,8 @@ proc updatePawnAttacks(self: ChessBoard) =
|
||||||
## Internal helper of updateAttackedSquares
|
## Internal helper of updateAttackedSquares
|
||||||
for loc in self.position.pieces.white.pawns:
|
for loc in self.position.pieces.white.pawns:
|
||||||
# Pawns are special in how they capture (i.e. the
|
# Pawns are special in how they capture (i.e. the
|
||||||
# squares they can move to do not match the squares
|
# squares they can regularly move to do not match
|
||||||
# they can capture on. Sneaky fucks)
|
# the squares they can capture on. Sneaky fucks)
|
||||||
self.addAttack((loc, loc + White.topRightDiagonal(), White.topRightDiagonal()), White)
|
self.addAttack((loc, loc + White.topRightDiagonal(), White.topRightDiagonal()), White)
|
||||||
self.addAttack((loc, loc + White.topLeftDiagonal(), White.topLeftDiagonal()), White)
|
self.addAttack((loc, loc + White.topLeftDiagonal(), White.topLeftDiagonal()), White)
|
||||||
# We do the same thing for black
|
# 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.topLeftDiagonal(), White.topLeftDiagonal()), White)
|
||||||
self.addAttack((king, king + White.bottomLeftDiagonal(), White.bottomLeftDiagonal()), White)
|
self.addAttack((king, king + White.bottomLeftDiagonal(), White.bottomLeftDiagonal()), White)
|
||||||
self.addAttack((king, king + White.bottomRightDiagonal(), White.bottomRightDiagonal()), 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
|
king = self.position.pieces.black.king
|
||||||
self.addAttack((king, king + Black.topRightDiagonal(), Black.topRightDiagonal()), Black)
|
self.addAttack((king, king + Black.topRightDiagonal(), Black.topRightDiagonal()), Black)
|
||||||
self.addAttack((king, king + Black.topLeftDiagonal(), Black.topLeftDiagonal()), Black)
|
self.addAttack((king, king + Black.topLeftDiagonal(), Black.topLeftDiagonal()), Black)
|
||||||
self.addAttack((king, king + Black.bottomLeftDiagonal(), Black.bottomLeftDiagonal()), Black)
|
self.addAttack((king, king + Black.bottomLeftDiagonal(), Black.bottomLeftDiagonal()), Black)
|
||||||
self.addAttack((king, king + Black.bottomRightDiagonal(), Black.bottomRightDiagonal()), 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) =
|
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
|
# we allow it to move two squares as well
|
||||||
if square.col == loc.col:
|
if square.col == loc.col:
|
||||||
# The pawn can only push two squares if it's being pinned from the
|
# 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()))
|
result.pins.add((loc, square, otherPiece.color.doublePush()))
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
@ -1300,7 +1333,6 @@ proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked
|
||||||
|
|
||||||
proc updateSlidingAttacks(self: ChessBoard) =
|
proc updateSlidingAttacks(self: ChessBoard) =
|
||||||
## Internal helper of updateAttackedSquares
|
## Internal helper of updateAttackedSquares
|
||||||
|
|
||||||
var data: tuple[attacks: Attacked, pins: Attacked]
|
var data: tuple[attacks: Attacked, pins: Attacked]
|
||||||
for loc in self.position.pieces.white.bishops:
|
for loc in self.position.pieces.white.bishops:
|
||||||
data = self.getSlidingAttacks(loc)
|
data = self.getSlidingAttacks(loc)
|
||||||
|
@ -1841,6 +1873,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
if not bulk:
|
if not bulk:
|
||||||
if len(moves) == 0 and self.inCheck():
|
if len(moves) == 0 and self.inCheck():
|
||||||
result.checkmates = 1
|
result.checkmates = 1
|
||||||
|
# TODO: Should we count stalemates?
|
||||||
if ply == 0:
|
if ply == 0:
|
||||||
result.nodes = 1
|
result.nodes = 1
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue