Initial work on checkmate detection and pins
This commit is contained in:
parent
942f195ddc
commit
17f15e682c
|
@ -100,6 +100,8 @@ func emptyLocation*: Location {.inline.} = (-1 , -1)
|
|||
func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White)
|
||||
proc algebraicToPosition*(s: string): Location {.inline.}
|
||||
proc getCapture*(self: ChessBoard, move: Move): Location
|
||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move
|
||||
proc makeMove*(self: ChessBoard, move: Move): Move
|
||||
func emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
|
||||
func `+`*(a, b: Location): Location = (a.row + b.row, a.col + b.col)
|
||||
func isValid*(a: Location): bool = a.row in 0..7 and a.col in 0..7
|
||||
|
@ -148,6 +150,13 @@ func getStartRow(piece: Piece): int {.inline.} =
|
|||
func getLastRow(color: PieceColor): int {.inline.} =
|
||||
## Retrieves the location of the last
|
||||
## row relative to the given color
|
||||
case color:
|
||||
of White:
|
||||
return 0
|
||||
of Black:
|
||||
return 7
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
proc newChessboard: ChessBoard =
|
||||
|
@ -509,7 +518,10 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
# A friendly piece is in the way
|
||||
if otherPiece.color == piece.color:
|
||||
break
|
||||
# Target square is empty or occupied by the enemy
|
||||
if otherPiece.color == piece.color.opposite:
|
||||
result.add(Move(startSquare: location, targetSquare: square, piece: piece))
|
||||
break
|
||||
# Target square is empty
|
||||
result.add(Move(startSquare: location, targetSquare: square, piece: piece))
|
||||
|
||||
|
||||
|
@ -551,6 +563,53 @@ proc testMoveOffsets(self: ChessBoard, move: Move): bool =
|
|||
return false
|
||||
|
||||
|
||||
proc getAttackers*(self: ChessBoard, square: string): seq[Piece] =
|
||||
## Returns all the attackers of the given square
|
||||
let loc = square.algebraicToPosition()
|
||||
for move in self.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
for move in self.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
|
||||
# We don't use getAttackers because this one only cares about whether
|
||||
# the square is attacked or not (and can therefore exit earlier than
|
||||
# getAttackers)
|
||||
proc isAttacked*(self: ChessBoard, loc: Location): bool =
|
||||
## Returns whether the given location is attacked
|
||||
## by the current opponent
|
||||
let piece = self.grid[loc.row, loc.col]
|
||||
case piece.color:
|
||||
of White:
|
||||
for move in self.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
of Black:
|
||||
for move in self.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
of None:
|
||||
case self.turn:
|
||||
of White:
|
||||
for move in self.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
of Black:
|
||||
for move in self.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
|
||||
proc isAttacked*(self: ChessBoard, square: string): bool =
|
||||
## Returns whether the given square is attacked
|
||||
## by the current inactive color
|
||||
return self.isAttacked(square.algebraicToPosition())
|
||||
|
||||
|
||||
proc updateAttackedSquares(self: ChessBoard) =
|
||||
## Updates internal metadata about which squares
|
||||
## are attacked. Called internally by doMove
|
||||
|
@ -580,7 +639,6 @@ proc updateAttackedSquares(self: ChessBoard) =
|
|||
for loc in self.pieces.black.bishops:
|
||||
for move in self.generateMoves(loc):
|
||||
self.attacked.black.add(move)
|
||||
|
||||
# White rooks
|
||||
for loc in self.pieces.white.rooks:
|
||||
for move in self.generateMoves(loc):
|
||||
|
@ -599,50 +657,6 @@ proc updateAttackedSquares(self: ChessBoard) =
|
|||
self.attacked.black.add(move)
|
||||
|
||||
|
||||
proc getAttackers*(self: ChessBoard, square: string): seq[Piece] =
|
||||
## Returns the attackers of the given square.
|
||||
## If the square has no attackers, an empty
|
||||
## seq is returned
|
||||
let loc = square.algebraicToPosition()
|
||||
case self.turn:
|
||||
of White:
|
||||
for move in self.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
of Black:
|
||||
for move in self.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
else:
|
||||
return @[]
|
||||
|
||||
# We don't use getAttackers because this one only cares about whether
|
||||
# the square is attacked or not (and can therefore exit earlier than
|
||||
# getAttackers)
|
||||
proc isAttacked*(self: ChessBoard, loc: Location): bool =
|
||||
## Returns whether the given location is attacked
|
||||
## by the current inactive color
|
||||
case self.turn:
|
||||
of White:
|
||||
for move in self.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
return false
|
||||
of Black:
|
||||
for move in self.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
return false
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc isAttacked*(self: ChessBoard, square: string): bool =
|
||||
## Returns whether the given square is attacked
|
||||
## by the current inactive color
|
||||
return self.isAttacked(square.algebraicToPosition())
|
||||
|
||||
|
||||
proc removePiece(self: ChessBoard, location: Location) =
|
||||
## Removes a piece from the board, updating necessary
|
||||
## metadata
|
||||
|
@ -749,6 +763,19 @@ proc updatePositions(self: ChessBoard, move: Move) =
|
|||
self.grid[move.targetSquare.row, move.targetSquare.col] = move.piece
|
||||
|
||||
|
||||
proc inCheck(self: ChessBoard): bool =
|
||||
## Returns whether the active color's
|
||||
## king is in check
|
||||
case self.turn:
|
||||
of White:
|
||||
return self.isAttacked(self.pieces.white.king)
|
||||
of Black:
|
||||
return self.isAttacked(self.pieces.black.king)
|
||||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
|
||||
|
||||
proc doMove(self: ChessBoard, move: Move) =
|
||||
## Internal function called by makeMove after
|
||||
## performing legality checks on the given move. Can
|
||||
|
@ -760,34 +787,57 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
# pawn has moved
|
||||
if self.enPassantSquare != emptyMove() and self.enPassantSquare.piece.color == self.turn.opposite():
|
||||
self.enPassantSquare = emptyMove()
|
||||
self.turn = self.turn.opposite()
|
||||
|
||||
|
||||
proc undoMove(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||
## Undoes the given move if possible
|
||||
result = Move(piece: move.piece, startSquare: move.targetSquare, targetSquare: move.startSquare)
|
||||
let castling = self.castling
|
||||
let enPassant = self.enPassantSquare
|
||||
# Swap start and target square and do the move in reverse
|
||||
self.doMove(result)
|
||||
# We need to reset the entire position when the move is undone!
|
||||
self.enPassantSquare = enPassant
|
||||
#self.turn = self.turn.opposite()
|
||||
self.castling = castling
|
||||
|
||||
proc checkMove(self: ChessBoard, startSquare, targetSquare: string): Move =
|
||||
|
||||
proc checkMove(self: ChessBoard, move: Move): bool =
|
||||
## Internal function called by makeMove to check a move for legality
|
||||
var pieceToMove = self.getPiece(startSquare)
|
||||
# Start square doesn't contain a piece (and it isn't the en passant square)
|
||||
# or it is of the wrong color for which turn it is to move
|
||||
if pieceToMove.kind == Empty or pieceToMove.color != self.turn:
|
||||
return emptyMove()
|
||||
var destination = self.getPiece(targetSquare)
|
||||
if move.piece.kind == Empty or move.piece.color != self.turn:
|
||||
return false
|
||||
var destination = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||
# Destination square is occupied by a piece of the same color as the piece
|
||||
# being moved: illegal!
|
||||
if destination.kind != Empty and destination.color == self.turn:
|
||||
return emptyMove()
|
||||
var
|
||||
startLocation = startSquare.algebraicToPosition()
|
||||
targetLocation = targetSquare.algebraicToPosition()
|
||||
result = Move(startSquare: startLocation, targetSquare: targetLocation, piece: pieceToMove)
|
||||
if not self.testMoveOffsets(result):
|
||||
return false
|
||||
if not self.testMoveOffsets(move):
|
||||
# Piece cannot arrive to destination (either
|
||||
# because it is blocked or because the moving
|
||||
# pattern is incorrect)
|
||||
return emptyMove()
|
||||
# TODO: Check for checks and pins (moves are currently pseudo-legal)
|
||||
return false
|
||||
echo move
|
||||
self.doMove(move)
|
||||
defer: self.undoMove(move)
|
||||
# Move would reveal an attack
|
||||
# on our king: not allowed
|
||||
if self.inCheck():
|
||||
echo "in check"
|
||||
return false
|
||||
# All checks have passed: move is legal
|
||||
result = true
|
||||
|
||||
|
||||
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||
## Like the other makeMove(), but with a Move object
|
||||
result = move
|
||||
if not self.checkMove(move):
|
||||
return emptyMove()
|
||||
self.doMove(result)
|
||||
self.turn = self.turn.opposite()
|
||||
|
||||
|
||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.discardable.} =
|
||||
## Makes a move on the board from the chosen start square to
|
||||
|
@ -797,11 +847,11 @@ proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.disc
|
|||
## For efficiency purposes, no exceptions are raised if the move is
|
||||
## illegal, but the move's piece kind will be Empty (its color will be None
|
||||
## too) and the locations will both be set to the tuple (-1, -1)
|
||||
|
||||
result = self.checkMove(startSquare, targetSquare)
|
||||
if result == emptyMove():
|
||||
return
|
||||
self.doMove(result)
|
||||
var
|
||||
startLocation = startSquare.algebraicToPosition()
|
||||
targetLocation = targetSquare.algebraicToPosition()
|
||||
result = Move(startSquare: startLocation, targetSquare: targetLocation, piece: self.grid[startLocation.row, startLocation.col])
|
||||
return self.makeMove(result)
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue