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)
|
func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White)
|
||||||
proc algebraicToPosition*(s: string): Location {.inline.}
|
proc algebraicToPosition*(s: string): Location {.inline.}
|
||||||
proc getCapture*(self: ChessBoard, move: Move): Location
|
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 emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
|
||||||
func `+`*(a, b: Location): Location = (a.row + b.row, a.col + b.col)
|
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
|
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.} =
|
func getLastRow(color: PieceColor): int {.inline.} =
|
||||||
## Retrieves the location of the last
|
## Retrieves the location of the last
|
||||||
## row relative to the given color
|
## row relative to the given color
|
||||||
|
case color:
|
||||||
|
of White:
|
||||||
|
return 0
|
||||||
|
of Black:
|
||||||
|
return 7
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
proc newChessboard: ChessBoard =
|
proc newChessboard: ChessBoard =
|
||||||
|
@ -509,7 +518,10 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
# A friendly piece is in the way
|
# A friendly piece is in the way
|
||||||
if otherPiece.color == piece.color:
|
if otherPiece.color == piece.color:
|
||||||
break
|
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))
|
result.add(Move(startSquare: location, targetSquare: square, piece: piece))
|
||||||
|
|
||||||
|
|
||||||
|
@ -551,6 +563,53 @@ proc testMoveOffsets(self: ChessBoard, move: Move): bool =
|
||||||
return false
|
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) =
|
proc updateAttackedSquares(self: ChessBoard) =
|
||||||
## Updates internal metadata about which squares
|
## Updates internal metadata about which squares
|
||||||
## are attacked. Called internally by doMove
|
## are attacked. Called internally by doMove
|
||||||
|
@ -580,7 +639,6 @@ proc updateAttackedSquares(self: ChessBoard) =
|
||||||
for loc in self.pieces.black.bishops:
|
for loc in self.pieces.black.bishops:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.attacked.black.add(move)
|
self.attacked.black.add(move)
|
||||||
|
|
||||||
# White rooks
|
# White rooks
|
||||||
for loc in self.pieces.white.rooks:
|
for loc in self.pieces.white.rooks:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
|
@ -599,50 +657,6 @@ proc updateAttackedSquares(self: ChessBoard) =
|
||||||
self.attacked.black.add(move)
|
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) =
|
proc removePiece(self: ChessBoard, location: Location) =
|
||||||
## Removes a piece from the board, updating necessary
|
## Removes a piece from the board, updating necessary
|
||||||
## metadata
|
## metadata
|
||||||
|
@ -749,6 +763,19 @@ proc updatePositions(self: ChessBoard, move: Move) =
|
||||||
self.grid[move.targetSquare.row, move.targetSquare.col] = move.piece
|
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) =
|
proc doMove(self: ChessBoard, move: Move) =
|
||||||
## Internal function called by makeMove after
|
## Internal function called by makeMove after
|
||||||
## performing legality checks on the given move. Can
|
## performing legality checks on the given move. Can
|
||||||
|
@ -760,34 +787,57 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
# pawn has moved
|
# pawn has moved
|
||||||
if self.enPassantSquare != emptyMove() and self.enPassantSquare.piece.color == self.turn.opposite():
|
if self.enPassantSquare != emptyMove() and self.enPassantSquare.piece.color == self.turn.opposite():
|
||||||
self.enPassantSquare = emptyMove()
|
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
|
## 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)
|
# 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
|
# or it is of the wrong color for which turn it is to move
|
||||||
if pieceToMove.kind == Empty or pieceToMove.color != self.turn:
|
if move.piece.kind == Empty or move.piece.color != self.turn:
|
||||||
return emptyMove()
|
return false
|
||||||
var destination = self.getPiece(targetSquare)
|
var destination = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||||
# Destination square is occupied by a piece of the same color as the piece
|
# Destination square is occupied by a piece of the same color as the piece
|
||||||
# being moved: illegal!
|
# being moved: illegal!
|
||||||
if destination.kind != Empty and destination.color == self.turn:
|
if destination.kind != Empty and destination.color == self.turn:
|
||||||
return emptyMove()
|
return false
|
||||||
var
|
if not self.testMoveOffsets(move):
|
||||||
startLocation = startSquare.algebraicToPosition()
|
|
||||||
targetLocation = targetSquare.algebraicToPosition()
|
|
||||||
result = Move(startSquare: startLocation, targetSquare: targetLocation, piece: pieceToMove)
|
|
||||||
if not self.testMoveOffsets(result):
|
|
||||||
# Piece cannot arrive to destination (either
|
# Piece cannot arrive to destination (either
|
||||||
# because it is blocked or because the moving
|
# because it is blocked or because the moving
|
||||||
# pattern is incorrect)
|
# pattern is incorrect)
|
||||||
return emptyMove()
|
return false
|
||||||
# TODO: Check for checks and pins (moves are currently pseudo-legal)
|
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.} =
|
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.discardable.} =
|
||||||
## Makes a move on the board from the chosen start square to
|
## 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
|
## 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
|
## 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)
|
## too) and the locations will both be set to the tuple (-1, -1)
|
||||||
|
var
|
||||||
result = self.checkMove(startSquare, targetSquare)
|
startLocation = startSquare.algebraicToPosition()
|
||||||
if result == emptyMove():
|
targetLocation = targetSquare.algebraicToPosition()
|
||||||
return
|
result = Move(startSquare: startLocation, targetSquare: targetLocation, piece: self.grid[startLocation.row, startLocation.col])
|
||||||
self.doMove(result)
|
return self.makeMove(result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue