Castling functional
This commit is contained in:
parent
afff1db88f
commit
31f77fa22d
|
@ -26,7 +26,6 @@ type
|
|||
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
|
||||
bishops: seq[Location], knights: seq[Location],
|
||||
pawns: seq[Location]]
|
||||
Castling* = tuple[white, black: tuple[queen, king: bool]]
|
||||
|
||||
PieceColor* = enum
|
||||
## A piece color enumeration
|
||||
|
@ -52,6 +51,9 @@ type
|
|||
MoveFlag* = enum
|
||||
## An enumeration of move flags
|
||||
Default, # Move is a regular move
|
||||
# Castling
|
||||
CastleLong,
|
||||
CastleShort,
|
||||
XRay, # Move is an X-ray attack
|
||||
# Move is a pawn promotion
|
||||
PromoteToQueen,
|
||||
|
@ -69,8 +71,8 @@ type
|
|||
Position* = ref object
|
||||
## A chess position
|
||||
move: Move
|
||||
# Stores castling metadata
|
||||
castling: Castling
|
||||
# Did the rooks on either side/the king move?
|
||||
castlingAvailable: tuple[white, black: tuple[queen, king: bool]]
|
||||
# Number of half-moves that were performed
|
||||
# to reach this position starting from the
|
||||
# root of the tree
|
||||
|
@ -114,75 +116,85 @@ func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
|
|||
func emptyLocation*: Location {.inline.} = (-1 , -1)
|
||||
func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White)
|
||||
proc algebraicToLocation*(s: string): Location {.inline.}
|
||||
proc getCapture*(self: ChessBoard, move: Move): Location
|
||||
func getCapture*(self: ChessBoard, move: Move): Location
|
||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move
|
||||
proc makeMove*(self: ChessBoard, move: Move): Move
|
||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: Location): 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
|
||||
proc generateMoves(self: ChessBoard, location: Location): seq[Move]
|
||||
proc isAttacked*(self: ChessBoard, loc: Location): bool
|
||||
proc undoLastMove*(self: ChessBoard): Move
|
||||
proc isLegal(self: ChessBoard, move: Move): bool
|
||||
|
||||
# Due to our board layout, directions of movement are reversed for white/black so
|
||||
# we need these helpers to avoid going mad with integer tuples and minus signs
|
||||
# everywhere
|
||||
func topLeftDiagonal(piece: Piece): Location {.inline.} = (if piece.color == White: (-1, -1) else: (1, 1))
|
||||
func topRightDiagonal(piece: Piece): Location {.inline.} = (if piece.color == White: (-1, 1) else: (1, -1))
|
||||
func bottomLeftDiagonal(piece: Piece): Location {.inline.} = (if piece.color == White: (1, -1) else: (-1, 1))
|
||||
func bottomRightDiagonal(piece: Piece): Location {.inline.} = (if piece.color == White: (1, 1) else: (-1, -1))
|
||||
func leftSide(piece: Piece): Location {.inline.} = (if piece.color == White: (0, -1) else: (0, 1))
|
||||
func rightSide(piece: Piece): Location {.inline.} = (if piece.color == White: (0, 1) else: (0, -1))
|
||||
func topSide(piece: Piece): Location {.inline.} = (if piece.color == White: (-1, 0) else: (1, 0))
|
||||
func bottomSide(piece: Piece): Location {.inline.} = (if piece.color == White: (1, 0) else: (-1, 0))
|
||||
func forward(piece: Piece): Location {.inline.} = (if piece.color == White: (-1, 0) else: (1, 0))
|
||||
func doublePush(piece: Piece): Location {.inline.} = (if piece.color == White: (-2, 0) else: (2, 0))
|
||||
func topLeftDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (-1, -1) else: (1, 1))
|
||||
func topRightDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (-1, 1) else: (1, -1))
|
||||
func bottomLeftDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (1, -1) else: (-1, 1))
|
||||
func bottomRightDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (1, 1) else: (-1, -1))
|
||||
func leftSide(color: PieceColor): Location {.inline.} = (if color == White: (0, -1) else: (0, 1))
|
||||
func rightSide(color: PieceColor): Location {.inline.} = (if color == White: (0, 1) else: (0, -1))
|
||||
func topSide(color: PieceColor): Location {.inline.} = (if color == White: (-1, 0) else: (1, 0))
|
||||
func bottomSide(color: PieceColor): Location {.inline.} = (if color == White: (1, 0) else: (-1, 0))
|
||||
func forward(color: PieceColor): Location {.inline.} = (if color == White: (-1, 0) else: (1, 0))
|
||||
func doublePush(color: PieceColor): Location {.inline.} = (if color == White: (-2, 0) else: (2, 0))
|
||||
func longCastleKing(color: PieceColor): Location {.inline.} = (0, -2)
|
||||
func shortCastleKing(color: PieceColor): Location {.inline.} = (0, 2)
|
||||
func longCastleRook(color: PieceColor): Location {.inline.} = (0, 3)
|
||||
func shortCastleRook(color: PieceColor): Location {.inline.} = (0, -2)
|
||||
func kingSideRook(color: PieceColor): Location {.inline.} = (if color == White: (7, 7) else: (0, 7))
|
||||
func queenSideRook(color: PieceColor): Location {.inline.} = (if color == White: (7, 0) else: (0, 0))
|
||||
|
||||
|
||||
func bottomLeftKnightMove(piece: Piece, long: bool = true): Location {.inline.} =
|
||||
if piece.color == White:
|
||||
func bottomLeftKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
|
||||
if color == White:
|
||||
if long:
|
||||
return (-2, 1)
|
||||
else:
|
||||
return (1, -2)
|
||||
elif piece.color == Black:
|
||||
elif color == Black:
|
||||
if long:
|
||||
return (2, -1)
|
||||
else:
|
||||
return (1, -2)
|
||||
|
||||
|
||||
func bottomRightKnightMove(piece: Piece, long: bool = true): Location {.inline.} =
|
||||
if piece.color == White:
|
||||
func bottomRightKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
|
||||
if color == White:
|
||||
if long:
|
||||
return (2, -1)
|
||||
else:
|
||||
return (1, 2)
|
||||
elif piece.color == Black:
|
||||
elif color == Black:
|
||||
if long:
|
||||
return (2, 1)
|
||||
else:
|
||||
return (1, 2)
|
||||
|
||||
|
||||
func topLeftKnightMove(piece: Piece, long: bool = true): Location {.inline.} =
|
||||
if piece.color == White:
|
||||
func topLeftKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
|
||||
if color == White:
|
||||
if long:
|
||||
return (-2, -1)
|
||||
else:
|
||||
return (-1, -2)
|
||||
elif piece.color == Black:
|
||||
elif color == Black:
|
||||
if long:
|
||||
return (2, 1)
|
||||
else:
|
||||
return (1, 2)
|
||||
|
||||
|
||||
func topRightKnightMove(piece: Piece, long: bool = true): Location {.inline.} =
|
||||
if piece.color == White:
|
||||
func topRightKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
|
||||
if color == White:
|
||||
if long:
|
||||
return (-2, 1)
|
||||
else:
|
||||
return (-1, 2)
|
||||
elif piece.color == Black:
|
||||
elif color == Black:
|
||||
if long:
|
||||
return (2, -1)
|
||||
else:
|
||||
|
@ -195,18 +207,6 @@ proc getActiveColor*(self: ChessBoard): PieceColor =
|
|||
return self.position.turn
|
||||
|
||||
|
||||
proc getCastlingInformation*(self: ChessBoard): tuple[queen, king: bool] =
|
||||
## Returns whether castling is possible
|
||||
## for the given color
|
||||
case self.getActiveColor():
|
||||
of White:
|
||||
return self.position.castling.white
|
||||
of Black:
|
||||
return self.position.castling.black
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc getEnPassantTarget*(self: ChessBoard): Location =
|
||||
## Returns the current en passant target square
|
||||
return self.position.enPassantSquare.targetSquare
|
||||
|
@ -236,7 +236,7 @@ func getStartRow(piece: Piece): int {.inline.} =
|
|||
of Pawn:
|
||||
return 6
|
||||
else:
|
||||
return 5
|
||||
return 7
|
||||
of Black:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
|
@ -370,13 +370,13 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
|||
# by default
|
||||
discard
|
||||
of 'K':
|
||||
result.position.castling.white.king = true
|
||||
result.position.castlingAvailable.white.king = true
|
||||
of 'Q':
|
||||
result.position.castling.white.queen = true
|
||||
result.position.castlingAvailable.white.queen = true
|
||||
of 'k':
|
||||
result.position.castling.black.king = true
|
||||
result.position.castlingAvailable.black.king = true
|
||||
of 'q':
|
||||
result.position.castling.black.queen = true
|
||||
result.position.castlingAvailable.black.queen = true
|
||||
else:
|
||||
raise newException(ValueError, "invalid castling availability in FEN string")
|
||||
of 3:
|
||||
|
@ -503,20 +503,20 @@ proc algebraicToLocation*(s: string): Location =
|
|||
return (file, rank)
|
||||
|
||||
|
||||
proc locationToAlgebraic*(loc: Location): string =
|
||||
func locationToAlgebraic*(loc: Location): string {.inline.} =
|
||||
## Converts a location from our internal row, column
|
||||
## notation to a square in algebraic notation
|
||||
return &"{char(uint8(loc.col) + uint8('a'))}{rowToRank(loc.row)}"
|
||||
|
||||
|
||||
proc getPiece*(self: ChessBoard, square: string): Piece =
|
||||
func getPiece*(self: ChessBoard, square: string): Piece =
|
||||
## Gets the piece on the given square
|
||||
## in algebraic notation
|
||||
let loc = square.algebraicToLocation()
|
||||
return self.grid[loc.row, loc.col]
|
||||
|
||||
|
||||
proc getCapture*(self: ChessBoard, move: Move): Location =
|
||||
func getCapture*(self: ChessBoard, move: Move): Location =
|
||||
## Returns the location that would be captured if this
|
||||
## move were played on the board, taking en passant and
|
||||
## other things into account (the move is assumed to be
|
||||
|
@ -533,12 +533,113 @@ proc getCapture*(self: ChessBoard, move: Move): Location =
|
|||
return move.targetSquare
|
||||
|
||||
|
||||
proc isCapture*(self: ChessBoard, move: Move): bool {.inline.} =
|
||||
func isCapture*(self: ChessBoard, move: Move): bool {.inline.} =
|
||||
## Returns whether the given move is a capture
|
||||
## or not
|
||||
return self.getCapture(move) != emptyLocation()
|
||||
|
||||
|
||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
||||
## Returns whether the given color's
|
||||
## king is in check. If the color is
|
||||
## set to None, checks are checked
|
||||
## for the active color's king
|
||||
case color:
|
||||
of White:
|
||||
return self.isAttacked(self.position.pieces.white.king)
|
||||
of Black:
|
||||
return self.isAttacked(self.position.pieces.black.king)
|
||||
of None:
|
||||
case self.getActiveColor():
|
||||
of White:
|
||||
return self.isAttacked(self.position.pieces.white.king)
|
||||
of Black:
|
||||
return self.isAttacked(self.position.pieces.black.king)
|
||||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
|
||||
|
||||
proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king: bool] {.inline.} =
|
||||
## Returns the sides on which castling is allowed
|
||||
## for the given color. If the color is Empty, the
|
||||
## currently active color is used
|
||||
var color = color
|
||||
if color == None:
|
||||
color = self.getActiveColor()
|
||||
# If the rooks or king have been moved, castling
|
||||
# rights have been lost
|
||||
case color:
|
||||
of White:
|
||||
result.king = self.position.castlingAvailable.white.king
|
||||
result.queen = self.position.castlingAvailable.white.queen
|
||||
of Black:
|
||||
result.king = self.position.castlingAvailable.black.king
|
||||
result.queen = self.position.castlingAvailable.black.queen
|
||||
of None:
|
||||
# Unreachable
|
||||
discard
|
||||
var
|
||||
loc: Location
|
||||
queenSide: Location
|
||||
kingSide: Location
|
||||
# If the path between the king and a rook is blocked, then castling
|
||||
# is prohibited on that side
|
||||
case color:
|
||||
of White:
|
||||
loc = self.position.pieces.white.king
|
||||
queenSide = color.leftSide()
|
||||
kingSide = color.rightSide()
|
||||
of Black:
|
||||
loc = self.position.pieces.black.king
|
||||
queenSide = color.rightSide()
|
||||
kingSide = color.leftSide()
|
||||
of None:
|
||||
# Unreachable
|
||||
discard
|
||||
|
||||
if result.king:
|
||||
# Short castle
|
||||
var location = loc
|
||||
while true:
|
||||
location = location + kingSide
|
||||
if not location.isValid():
|
||||
break
|
||||
if self.grid[location.row, location.col].kind == Empty:
|
||||
continue
|
||||
if location == color.kingSideRook() and self.grid[location.row, location.col].kind == Rook:
|
||||
break
|
||||
# Blocked by a piece
|
||||
result.king = false
|
||||
break
|
||||
|
||||
if result.queen:
|
||||
# Long castle
|
||||
var location = loc
|
||||
while true:
|
||||
location = location + queenSide
|
||||
if not location.isValid():
|
||||
break
|
||||
if self.grid[location.row, location.col].kind == Empty:
|
||||
continue
|
||||
if location == color.queenSideRook() and self.grid[location.row, location.col].kind == Rook:
|
||||
break
|
||||
# Blocked by a piece
|
||||
result.queen = false
|
||||
break
|
||||
|
||||
# If the castling king would walk into, through or out of check
|
||||
# while castling on a given side, then it is not possible to castle
|
||||
# on that side until the threat exists
|
||||
|
||||
if (result.king or result.queen) and self.inCheck(color):
|
||||
# Only check for checks if castling is still available
|
||||
# by this point (if we can avoid calls to generateMoves,
|
||||
# we should)
|
||||
return
|
||||
# TODO: Check for attacks in the various other squares
|
||||
|
||||
|
||||
proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
## Generates the possible moves for the pawn in the given
|
||||
## location
|
||||
|
@ -547,13 +648,13 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
locations: seq[Location] = @[]
|
||||
doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}"
|
||||
# Pawns can move forward one square
|
||||
let forwardOffset = piece.forward()
|
||||
let forwardOffset = piece.color.forward()
|
||||
let forward = (forwardOffset + location)
|
||||
if forward.isValid() and self.grid[forward.row, forward.col].color == None:
|
||||
locations.add(forwardOffset)
|
||||
# If the pawn is on its first rank, it can push two squares
|
||||
if location.row == piece.getStartRow():
|
||||
locations.add(piece.doublePush())
|
||||
locations.add(piece.color.doublePush())
|
||||
if self.position.enPassantSquare.piece.color == piece.color.opposite:
|
||||
if abs(self.position.enPassantSquare.targetSquare.col - location.col) == 1 and abs(self.position.enPassantSquare.targetSquare.row - location.row) == 1:
|
||||
# Only viable if the piece is on the diagonal of the target
|
||||
|
@ -562,19 +663,19 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
# square, but only to capture
|
||||
if location.col in 1..6:
|
||||
# Top right diagonal
|
||||
locations.add(piece.topRightDiagonal())
|
||||
locations.add(piece.color.topRightDiagonal())
|
||||
if location.row in 1..6:
|
||||
# Top left diagonal
|
||||
locations.add(piece.topLeftDiagonal())
|
||||
locations.add(piece.color.topLeftDiagonal())
|
||||
|
||||
# Pawn is at the right side, can only capture
|
||||
# on the left one
|
||||
if location.col == 7 and location.row < 7:
|
||||
locations.add(piece.topLeftDiagonal())
|
||||
locations.add(piece.color.topLeftDiagonal())
|
||||
# Pawn is at the left side, can only capture
|
||||
# on the right one
|
||||
if location.col == 0 and location.row < 7:
|
||||
locations.add(piece.topRightDiagonal())
|
||||
locations.add(piece.color.topRightDiagonal())
|
||||
var
|
||||
newLocation: Location
|
||||
targetPiece: Piece
|
||||
|
@ -606,15 +707,15 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
var directions: seq[Location] = @[]
|
||||
# Only check in the right directions for the chosen piece
|
||||
if piece.kind in [Bishop, Queen]:
|
||||
directions.add(piece.topLeftDiagonal())
|
||||
directions.add(piece.topRightDiagonal())
|
||||
directions.add(piece.bottomLeftDiagonal())
|
||||
directions.add(piece.bottomRightDiagonal())
|
||||
directions.add(piece.color.topLeftDiagonal())
|
||||
directions.add(piece.color.topRightDiagonal())
|
||||
directions.add(piece.color.bottomLeftDiagonal())
|
||||
directions.add(piece.color.bottomRightDiagonal())
|
||||
if piece.kind in [Queen, Rook]:
|
||||
directions.add(piece.topSide())
|
||||
directions.add(piece.bottomSide())
|
||||
directions.add(piece.rightSide())
|
||||
directions.add(piece.leftSide())
|
||||
directions.add(piece.color.topSide())
|
||||
directions.add(piece.color.bottomSide())
|
||||
directions.add(piece.color.rightSide())
|
||||
directions.add(piece.color.leftSide())
|
||||
for direction in directions:
|
||||
# Slide in this direction as long as it's possible
|
||||
var
|
||||
|
@ -643,31 +744,39 @@ proc generateKingMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
var
|
||||
piece = self.grid[location.row, location.col]
|
||||
doAssert piece.kind == King, &"generateKingMoves called on a {piece.kind}"
|
||||
var directions: seq[Location] = @[piece.topLeftDiagonal(),
|
||||
piece.topRightDiagonal(),
|
||||
piece.bottomRightDiagonal(),
|
||||
piece.bottomLeftDiagonal(),
|
||||
piece.topSide(),
|
||||
piece.bottomSide(),
|
||||
piece.leftSide(),
|
||||
piece.rightSide()]
|
||||
var directions: seq[Location] = @[piece.color.topLeftDiagonal(),
|
||||
piece.color.topRightDiagonal(),
|
||||
piece.color.bottomRightDiagonal(),
|
||||
piece.color.bottomLeftDiagonal(),
|
||||
piece.color.topSide(),
|
||||
piece.color.bottomSide(),
|
||||
piece.color.leftSide(),
|
||||
piece.color.rightSide()]
|
||||
# Castling
|
||||
let canCastle = self.canCastle()
|
||||
if canCastle.queen:
|
||||
directions.add(piece.color.longCastleKing())
|
||||
if canCastle.king:
|
||||
directions.add(piece.color.shortCastleKing())
|
||||
var flag = Default
|
||||
for direction in directions:
|
||||
if direction == piece.color.longCastleKing():
|
||||
flag = CastleLong
|
||||
elif direction == piece.color.shortCastleKing():
|
||||
flag = CastleShort
|
||||
# Step in this direction once
|
||||
let square: Location = location + direction
|
||||
# End of board reached
|
||||
if not square.isValid():
|
||||
continue
|
||||
let otherPiece = self.grid[square.row, square.col]
|
||||
# A friendly piece is in the way
|
||||
# A friendly piece is in the way, move onto the next
|
||||
# direction
|
||||
if otherPiece.color == piece.color:
|
||||
continue
|
||||
if otherPiece.color == piece.color.opposite:
|
||||
# Target square contains an enemy piece: capture
|
||||
# it
|
||||
result.add(Move(startSquare: location, targetSquare: square, piece: piece))
|
||||
continue
|
||||
# Target square is empty
|
||||
result.add(Move(startSquare: location, targetSquare: square, piece: piece))
|
||||
# Target square is empty or contains an enemy piece:
|
||||
# All good for us!
|
||||
result.add(Move(startSquare: location, targetSquare: square, piece: piece, flag: flag))
|
||||
|
||||
|
||||
proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
|
@ -675,14 +784,14 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
var
|
||||
piece = self.grid[location.row, location.col]
|
||||
doAssert piece.kind == Knight, &"generateKnightMoves called on a {piece.kind}"
|
||||
var directions: seq[Location] = @[piece.bottomLeftKnightMove(),
|
||||
piece.bottomRightKnightMove(),
|
||||
piece.topLeftKnightMove(),
|
||||
piece.topRightKnightMove(),
|
||||
piece.bottomLeftKnightMove(long=false),
|
||||
piece.bottomRightKnightMove(long=false),
|
||||
piece.topLeftKnightMove(long=false),
|
||||
piece.topRightKnightMove(long=false)]
|
||||
var directions: seq[Location] = @[piece.color.bottomLeftKnightMove(),
|
||||
piece.color.bottomRightKnightMove(),
|
||||
piece.color.topLeftKnightMove(),
|
||||
piece.color.topRightKnightMove(),
|
||||
piece.color.bottomLeftKnightMove(long=false),
|
||||
piece.color.bottomRightKnightMove(long=false),
|
||||
piece.color.topLeftKnightMove(long=false),
|
||||
piece.color.topRightKnightMove(long=false)]
|
||||
for direction in directions:
|
||||
# Jump to this square
|
||||
let square: Location = location + direction
|
||||
|
@ -703,7 +812,7 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
|
||||
|
||||
proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
## Returns the list of possible legal chess moves for the
|
||||
## Returns the list of possible pseudo-legal chess moves for the
|
||||
## piece in the given location
|
||||
let piece = self.grid[location.row, location.col]
|
||||
case piece.kind:
|
||||
|
@ -719,41 +828,29 @@ proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
return @[]
|
||||
|
||||
|
||||
proc getAttackers*(self: ChessBoard, square: string): seq[Piece] =
|
||||
proc generateLegalMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
## Returns the list of possible legal chess moves for the
|
||||
## piece in the given location
|
||||
for move in self.generateMoves(location):
|
||||
if self.isLegal(move):
|
||||
result.add(move)
|
||||
|
||||
|
||||
proc getAttackers*(self: ChessBoard, square: Location): seq[Piece] =
|
||||
## Returns all the attackers of the given square
|
||||
let loc = square.algebraicToLocation()
|
||||
for move in self.position.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
if move.targetSquare == square:
|
||||
result.add(move.piece)
|
||||
for move in self.position.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
if move.targetSquare == square:
|
||||
result.add(move.piece)
|
||||
|
||||
|
||||
proc getAttackersFor*(self: ChessBoard, square: string, color: PieceColor): seq[Piece] =
|
||||
## Returns all the attackers of the given square
|
||||
## for the given color
|
||||
let loc = square.algebraicToLocation()
|
||||
case color:
|
||||
of White:
|
||||
for move in self.position.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
of Black:
|
||||
for move in self.position.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
else:
|
||||
discard
|
||||
|
||||
# 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 opponent. If the location is empty, this
|
||||
## function returns true regardless of which color
|
||||
## the attackers are
|
||||
## by the current opponent
|
||||
let piece = self.grid[loc.row, loc.col]
|
||||
case piece.color:
|
||||
of White:
|
||||
|
@ -778,6 +875,7 @@ proc isAttacked*(self: ChessBoard, loc: Location): bool =
|
|||
discard
|
||||
|
||||
|
||||
|
||||
proc isAttacked*(self: ChessBoard, square: string): bool =
|
||||
## Returns whether the given square is attacked
|
||||
## by its opponent
|
||||
|
@ -805,6 +903,10 @@ proc updateAttackedSquares(self: ChessBoard) =
|
|||
for loc in self.position.pieces.white.bishops:
|
||||
for move in self.generateMoves(loc):
|
||||
self.position.attacked.white.add(move)
|
||||
# Knights
|
||||
for loc in self.position.pieces.white.knights:
|
||||
for move in self.generateMoves(loc):
|
||||
self.position.attacked.white.add(move)
|
||||
# Rooks
|
||||
for loc in self.position.pieces.white.rooks:
|
||||
for move in self.generateMoves(loc):
|
||||
|
@ -824,6 +926,9 @@ proc updateAttackedSquares(self: ChessBoard) =
|
|||
for loc in self.position.pieces.black.bishops:
|
||||
for move in self.generateMoves(loc):
|
||||
self.position.attacked.black.add(move)
|
||||
for loc in self.position.pieces.black.knights:
|
||||
for move in self.generateMoves(loc):
|
||||
self.position.attacked.white.add(move)
|
||||
for loc in self.position.pieces.black.rooks:
|
||||
for move in self.generateMoves(loc):
|
||||
self.position.attacked.black.add(move)
|
||||
|
@ -898,12 +1003,7 @@ proc movePiece(self: ChessBoard, move: Move) =
|
|||
self.position.pieces.white.rooks.delete(self.position.pieces.white.rooks.find(move.startSquare))
|
||||
self.position.pieces.white.rooks.add(move.targetSquare)
|
||||
of Queen:
|
||||
try:
|
||||
self.position.pieces.white.queens.delete(self.position.pieces.white.queens.find(move.startSquare))
|
||||
except:
|
||||
echo self.position.pieces.white.queens
|
||||
echo move.startSquare
|
||||
raise getCurrentException()
|
||||
self.position.pieces.white.queens.delete(self.position.pieces.white.queens.find(move.startSquare))
|
||||
self.position.pieces.white.queens.add(move.targetSquare)
|
||||
of King:
|
||||
self.position.pieces.white.king = move.targetSquare
|
||||
|
@ -944,31 +1044,13 @@ proc updatePositions(self: ChessBoard, move: Move) =
|
|||
let capture = self.getCapture(move)
|
||||
if capture != emptyLocation():
|
||||
self.position.captured = self.grid[capture.row, capture.col]
|
||||
if capture != self.getEnPassantTarget():
|
||||
# En passant is handled elsewhere
|
||||
self.removePiece(capture)
|
||||
# Update the positional metadata of the moving piece
|
||||
self.movePiece(move)
|
||||
|
||||
|
||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
||||
## Returns whether the given color's
|
||||
## king is in check. If the color is
|
||||
## set to None, checks are checked
|
||||
## for the active color's king
|
||||
case color:
|
||||
of White:
|
||||
return self.isAttacked(self.position.pieces.white.king)
|
||||
of Black:
|
||||
return self.isAttacked(self.position.pieces.black.king)
|
||||
of None:
|
||||
case self.getActiveColor():
|
||||
of White:
|
||||
return self.isAttacked(self.position.pieces.white.king)
|
||||
of Black:
|
||||
return self.isAttacked(self.position.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
|
||||
|
@ -977,25 +1059,74 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
|
||||
# Final checks
|
||||
|
||||
# Record the move in the position
|
||||
self.position.move = move
|
||||
|
||||
# Needed to detect draw by the 50 move rule
|
||||
var
|
||||
halfMoveClock = self.position.halfMoveClock
|
||||
fullMoveCount = self.position.fullMoveCount
|
||||
castlingAvailable = self.position.castlingAvailable
|
||||
if move.piece.kind == Pawn or self.isCapture(move):
|
||||
halfMoveClock = 0
|
||||
else:
|
||||
inc(halfMoveClock)
|
||||
if move.piece.color == Black:
|
||||
inc(fullMoveCount)
|
||||
# TODO: Castling
|
||||
|
||||
# Record the move in the position
|
||||
self.position.move = move
|
||||
|
||||
# Castling check: have the rooks moved?
|
||||
if move.piece.kind == Rook:
|
||||
case move.piece.color:
|
||||
of White:
|
||||
if move.startSquare.row == move.piece.getStartRow():
|
||||
if move.startSquare.col == 0:
|
||||
# Queen side
|
||||
castlingAvailable.white.queen = false
|
||||
elif move.startSquare.col == 7:
|
||||
# King side
|
||||
castlingAvailable.white.king = false
|
||||
of Black:
|
||||
if move.startSquare.row == move.piece.getStartRow():
|
||||
if move.startSquare.col == 0:
|
||||
# Queen side
|
||||
castlingAvailable.black.queen = false
|
||||
elif move.startSquare.col == 7:
|
||||
# King side
|
||||
castlingAvailable.black.king = false
|
||||
else:
|
||||
discard
|
||||
# Has the king moved?
|
||||
if move.piece.kind == King:
|
||||
case move.piece.color:
|
||||
of White:
|
||||
castlingAvailable.white.king = false
|
||||
castlingAvailable.white.queen = false
|
||||
of Black:
|
||||
castlingAvailable.black.king = false
|
||||
castlingAvailable.black.queen = false
|
||||
else:
|
||||
discard
|
||||
# Update position and attack metadata
|
||||
self.updatePositions(move)
|
||||
self.updateAttackedSquares()
|
||||
|
||||
var location: Location
|
||||
if move.flag in [CastleShort, CastleLong]:
|
||||
# Move the rook onto the
|
||||
# correct file
|
||||
var
|
||||
location: Location
|
||||
target: Location
|
||||
if move.flag == CastleShort:
|
||||
location = move.piece.color.kingSideRook()
|
||||
target = move.piece.color.shortCastleRook()
|
||||
else:
|
||||
location = move.piece.color.queenSideRook()
|
||||
target = move.piece.color.longCastleRook()
|
||||
let rook = self.grid[location.row, location.col]
|
||||
let move = Move(startSquare: location, targetSquare: location + target, piece: rook, flag: move.flag)
|
||||
self.updatePositions(move)
|
||||
self.updateAttackedSquares()
|
||||
|
||||
# Record final position for future reference
|
||||
self.positions.add(self.position)
|
||||
# Create new position with
|
||||
|
@ -1004,19 +1135,19 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
fullMoveCount: fullMoveCount,
|
||||
captured: emptyPiece(),
|
||||
turn: self.getActiveColor().opposite,
|
||||
# Inherit values from current position
|
||||
# (they are already up to date by this point)
|
||||
castling: self.position.castling,
|
||||
attacked: self.position.attacked,
|
||||
castlingAvailable: castlingAvailable,
|
||||
# Updated at the next call to doMove()
|
||||
move: emptyMove(),
|
||||
# Inherit values from current position
|
||||
# (they are updated later anyway)
|
||||
pieces: self.position.pieces,
|
||||
attacked: self.position.attacked,
|
||||
)
|
||||
# Check for double pawn push
|
||||
if move.piece.kind == Pawn and abs(move.startSquare.row - move.targetSquare.row) == 2:
|
||||
newPos.enPassantSquare = Move(piece: move.piece,
|
||||
startSquare: (move.startSquare.row, move.startSquare.col),
|
||||
targetSquare: move.targetSquare + move.piece.bottomSide())
|
||||
targetSquare: move.targetSquare + move.piece.color.bottomSide())
|
||||
else:
|
||||
newPos.enPassantSquare = emptyMove()
|
||||
|
||||
|
@ -1029,7 +1160,6 @@ proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
|||
## was previously located there: use with caution. Does
|
||||
## not automatically update the attacked square metadata
|
||||
## or other positional information
|
||||
doAssert piece.kind != King, "spawnPiece: cannot spawn a king"
|
||||
case piece.color:
|
||||
of White:
|
||||
case piece.kind:
|
||||
|
@ -1043,6 +1173,8 @@ proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
|||
self.position.pieces.white.rooks.add(location)
|
||||
of Queen:
|
||||
self.position.pieces.white.queens.add(location)
|
||||
of King:
|
||||
self.position.pieces.white.king = location
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
|
@ -1057,6 +1189,8 @@ proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
|||
self.position.pieces.black.rooks.add(location)
|
||||
of Queen:
|
||||
self.position.pieces.black.queens.add(location)
|
||||
of King:
|
||||
self.position.pieces.black.king = location
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
|
@ -1089,7 +1223,8 @@ proc undoLastMove*(self: ChessBoard): Move {.discardable.} =
|
|||
|
||||
proc isLegal(self: ChessBoard, move: Move): bool =
|
||||
## Returns whether the given move is legal
|
||||
|
||||
|
||||
var move = move
|
||||
# Start square doesn't contain a piece (and it isn't the en passant square)
|
||||
# or it's not this player's turn to move
|
||||
if (move.piece.kind == Empty and move.targetSquare != self.getEnPassantTarget()) or move.piece.color != self.getActiveColor():
|
||||
|
@ -1098,12 +1233,24 @@ proc isLegal(self: ChessBoard, move: Move): bool =
|
|||
# Destination square is occupied by a friendly piece
|
||||
if destination.kind != Empty and destination.color == self.getActiveColor():
|
||||
return false
|
||||
|
||||
echo "A"
|
||||
echo move.piece.color.longCastleKing()
|
||||
echo move.startSquare
|
||||
echo move.targetSquare
|
||||
echo "B"
|
||||
if move.piece.kind == King and move.piece.color.longCastleKing() + move.startSquare == move.targetSquare:
|
||||
move.flag = CastleLong
|
||||
elif move.piece.kind == King and move.piece.color.shortCastleKing() + move.startSquare == move.targetSquare:
|
||||
move.flag = CastleShort
|
||||
echo move
|
||||
echo self.generateMoves(move.startSquare)
|
||||
if move notin self.generateMoves(move.startSquare):
|
||||
# Piece cannot arrive to destination (blocked,
|
||||
# pinned or otherwise invalid move)
|
||||
# Piece cannot arrive to destination (blocked
|
||||
# or otherwise invalid move)
|
||||
return false
|
||||
self.doMove(move)
|
||||
defer: self.undoLastMove()
|
||||
defer: discard self.undoLastMove()
|
||||
# Move would reveal an attack
|
||||
# on our king: not allowed
|
||||
if self.inCheck(move.piece.color):
|
||||
|
@ -1114,6 +1261,7 @@ proc isLegal(self: ChessBoard, move: Move): bool =
|
|||
|
||||
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||
## Like the other makeMove(), but with a Move object
|
||||
|
||||
result = move
|
||||
if not self.isLegal(move):
|
||||
return emptyMove()
|
||||
|
|
|
@ -8,16 +8,20 @@ import std/strutils
|
|||
|
||||
when isMainModule:
|
||||
setControlCHook(proc () {.noconv.} = echo ""; quit(0))
|
||||
const fen = "rnbqkbnr/2p/8/8/8/8/P7/RNBQKBNR w KQkq - 0 1"
|
||||
var
|
||||
board = newChessboardFromFEN("rnbqkbnr/2p/8/8/8/8/P7/RNBQKBNR w KQkq - 0 1")
|
||||
board = newChessboardFromFEN(fen)
|
||||
canCastle: tuple[queen, king: bool]
|
||||
data: string
|
||||
move: Move
|
||||
|
||||
echo "\x1Bc"
|
||||
while true:
|
||||
canCastle = board.canCastle()
|
||||
echo &"{board.pretty()}"
|
||||
echo &"Turn: {board.getActiveColor()}"
|
||||
echo &"Moves: {board.getMoveCount()} full, {board.getHalfMoveCount()} half"
|
||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
stdout.write(&"En passant target: ")
|
||||
if board.getEnPassantTarget() != emptyLocation():
|
||||
echo board.getEnPassantTarget().locationToAlgebraic()
|
||||
|
@ -28,7 +32,7 @@ when isMainModule:
|
|||
echo &"Yes"
|
||||
else:
|
||||
echo "No"
|
||||
stdout.write("\nMove -> ")
|
||||
stdout.write("\nMove(s) -> ")
|
||||
try:
|
||||
data = readLine(stdin).strip(chars={'\0', ' '})
|
||||
except IOError:
|
||||
|
@ -37,14 +41,20 @@ when isMainModule:
|
|||
if data == "undo":
|
||||
echo &"\x1BcUndo: {board.undoLastMove()}"
|
||||
continue
|
||||
if len(data) != 4:
|
||||
echo "\x1BcError: invalid move"
|
||||
if data == "reset":
|
||||
echo &"\x1BcBoard reset"
|
||||
board = newChessboardFromFEN(fen)
|
||||
continue
|
||||
try:
|
||||
move = board.makeMove(data[0..1], data[2..3])
|
||||
except ValueError:
|
||||
echo &"\x1BcError: {getCurrentExceptionMsg()}"
|
||||
if move == emptyMove():
|
||||
echo &"\x1BcError: move is illegal"
|
||||
else:
|
||||
echo "\x1Bc"
|
||||
for moveChars in data.split(" "):
|
||||
if len(moveChars) != 4:
|
||||
echo "\x1BcError: invalid move"
|
||||
break
|
||||
try:
|
||||
move = board.makeMove(moveChars[0..1], moveChars[2..3])
|
||||
except ValueError:
|
||||
echo &"\x1BcError: {getCurrentExceptionMsg()}"
|
||||
if move == emptyMove():
|
||||
echo &"\x1BcError: move '{moveChars}' is illegal"
|
||||
break
|
||||
else:
|
||||
echo "\x1Bc"
|
||||
|
|
Loading…
Reference in New Issue