Further improve modularity
This commit is contained in:
parent
c072576b23
commit
4404ce10b9
|
@ -0,0 +1,486 @@
|
|||
import std/strformat
|
||||
import std/strutils
|
||||
|
||||
|
||||
import pieces
|
||||
import magics
|
||||
import moves
|
||||
import rays
|
||||
import bitboards
|
||||
import position
|
||||
|
||||
|
||||
export pieces, position, bitboards, moves, magics, rays
|
||||
|
||||
|
||||
|
||||
|
||||
type
|
||||
Chessboard* = ref object
|
||||
## A chessboard
|
||||
|
||||
# The actual board where pieces live
|
||||
grid*: array[64, Piece]
|
||||
# The current position
|
||||
position*: Position
|
||||
# List of all previously reached positions
|
||||
positions*: seq[Position]
|
||||
|
||||
|
||||
# A bunch of simple utility functions and forward declarations
|
||||
proc toFEN*(self: Chessboard): string
|
||||
|
||||
|
||||
proc newChessboard: Chessboard =
|
||||
## Returns a new, empty chessboard
|
||||
new(result)
|
||||
for i in 0..63:
|
||||
result.grid[i] = nullPiece()
|
||||
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White)
|
||||
|
||||
|
||||
# Indexing operations
|
||||
func `[]`*(self: array[64, Piece], square: Square): Piece {.inline.} = self[square.int8]
|
||||
func `[]=`*(self: var array[64, Piece], square: Square, piece: Piece) {.inline.} = self[square.int8] = piece
|
||||
|
||||
|
||||
func getBitboard*(self: Chessboard, kind: PieceKind, color: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the positional bitboard for the given piece kind and color
|
||||
return self.position.getBitboard(kind, color)
|
||||
|
||||
|
||||
func getBitboard*(self: Chessboard, piece: Piece): Bitboard {.inline.} =
|
||||
## Returns the positional bitboard for the given piece type
|
||||
return self.getBitboard(piece.kind, piece.color)
|
||||
|
||||
|
||||
proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||
## Initializes a chessboard with the
|
||||
## position encoded by the given FEN string
|
||||
result = newChessboard()
|
||||
var
|
||||
# Current square in the grid
|
||||
row: int8 = 0
|
||||
column: int8 = 0
|
||||
# Current section in the FEN string
|
||||
section = 0
|
||||
# Current index into the FEN string
|
||||
index = 0
|
||||
# Temporary variable to store a piece
|
||||
piece: Piece
|
||||
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
|
||||
while index <= fen.high():
|
||||
var c = fen[index]
|
||||
if c == ' ':
|
||||
# Next section
|
||||
inc(section)
|
||||
inc(index)
|
||||
continue
|
||||
case section:
|
||||
of 0:
|
||||
# Piece placement data
|
||||
case c.toLowerAscii():
|
||||
# Piece
|
||||
of 'r', 'n', 'b', 'q', 'k', 'p':
|
||||
let square = makeSquare(row, column)
|
||||
piece = c.fromChar()
|
||||
result.position.pieces[piece.color][piece.kind][].setBit(square)
|
||||
result.grid[square] = piece
|
||||
inc(column)
|
||||
of '/':
|
||||
# Next row
|
||||
inc(row)
|
||||
column = 0
|
||||
of '0'..'9':
|
||||
# Skip x columns
|
||||
let x = int(uint8(c) - uint8('0'))
|
||||
if x > 8:
|
||||
raise newException(ValueError, &"invalid FEN: invalid column skip size ({x} > 8)")
|
||||
column += int8(x)
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown piece identifier '{c}'")
|
||||
of 1:
|
||||
# Active color
|
||||
case c:
|
||||
of 'w':
|
||||
result.position.sideToMove = White
|
||||
of 'b':
|
||||
result.position.sideToMove = Black
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: invalid active color identifier '{c}'")
|
||||
of 2:
|
||||
# Castling availability
|
||||
case c:
|
||||
# TODO
|
||||
of '-':
|
||||
discard
|
||||
of 'K':
|
||||
result.position.castlingAvailability.white.king = true
|
||||
of 'Q':
|
||||
result.position.castlingAvailability.white.queen = true
|
||||
of 'k':
|
||||
result.position.castlingAvailability.black.king = true
|
||||
of 'q':
|
||||
result.position.castlingAvailability.black.queen = true
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||
of 3:
|
||||
# En passant target square
|
||||
case c:
|
||||
of '-':
|
||||
# Field is already uninitialized to the correct state
|
||||
discard
|
||||
else:
|
||||
result.position.enPassantSquare = fen[index..index+1].toSquare()
|
||||
# Square metadata is 2 bytes long
|
||||
inc(index)
|
||||
of 4:
|
||||
# Halfmove clock
|
||||
var s = ""
|
||||
while not fen[index].isSpaceAscii():
|
||||
s.add(fen[index])
|
||||
inc(index)
|
||||
# Backtrack so the space is seen by the
|
||||
# next iteration of the loop
|
||||
dec(index)
|
||||
result.position.halfMoveClock = parseInt(s).int8
|
||||
of 5:
|
||||
# Fullmove number
|
||||
var s = ""
|
||||
while index <= fen.high():
|
||||
s.add(fen[index])
|
||||
inc(index)
|
||||
result.position.fullMoveCount = parseInt(s).int8
|
||||
else:
|
||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||
inc(index)
|
||||
|
||||
|
||||
proc newDefaultChessboard*: Chessboard {.inline.} =
|
||||
## Initializes a chessboard with the
|
||||
## starting position
|
||||
return newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
|
||||
|
||||
func countPieces*(self: Chessboard, kind: PieceKind, color: PieceColor): int {.inline.} =
|
||||
## Returns the number of pieces with
|
||||
## the given color and type in the
|
||||
## current position
|
||||
return self.position.pieces[color][kind][].countSquares()
|
||||
|
||||
|
||||
func countPieces*(self: Chessboard, piece: Piece): int {.inline.} =
|
||||
## Returns the number of pieces on the board that
|
||||
## are of the same type and color as the given piece
|
||||
return self.countPieces(piece.kind, piece.color)
|
||||
|
||||
|
||||
func getPiece*(self: Chessboard, square: Square): Piece {.inline.} =
|
||||
## Gets the piece at the given square
|
||||
return self.grid[square]
|
||||
|
||||
|
||||
func getPiece*(self: Chessboard, square: string): Piece {.inline.} =
|
||||
## Gets the piece on the given square
|
||||
## in algebraic notation
|
||||
return self.getPiece(square.toSquare())
|
||||
|
||||
|
||||
func getOccupancyFor*(self: Chessboard, color: PieceColor): Bitboard =
|
||||
## Get the occupancy bitboard for every piece of the given color
|
||||
result = Bitboard(0)
|
||||
for b in self.position.pieces[color][]:
|
||||
result = result or b
|
||||
|
||||
|
||||
func getOccupancy*(self: Chessboard): Bitboard {.inline.} =
|
||||
## Get the occupancy bitboard for every piece on
|
||||
## the chessboard
|
||||
result = self.getOccupancyFor(Black) or self.getOccupancyFor(White)
|
||||
|
||||
|
||||
func getPawnAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the locations of the pawns attacking the given square
|
||||
let
|
||||
sq = square.toBitboard()
|
||||
pawns = self.getBitboard(Pawn, attacker)
|
||||
bottomLeft = sq.backwardLeftRelativeTo(attacker)
|
||||
bottomRight = sq.backwardRightRelativeTo(attacker)
|
||||
return pawns and (bottomLeft or bottomRight)
|
||||
|
||||
|
||||
func getKingAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the location of the king if it is attacking the given square
|
||||
result = Bitboard(0)
|
||||
let
|
||||
king = self.getBitboard(King, attacker)
|
||||
if (getKingAttacks(square) and king) != 0:
|
||||
result = result or king
|
||||
|
||||
|
||||
func getKnightAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the locations of the knights attacking the given square
|
||||
let
|
||||
knights = self.getBitboard(Knight, attacker)
|
||||
result = Bitboard(0)
|
||||
for knight in knights:
|
||||
let knightBB = knight.toBitboard()
|
||||
if (getKnightAttacks(knight) and knightBB) != 0:
|
||||
result = result or knightBB
|
||||
|
||||
|
||||
proc getSlidingAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the locations of the sliding pieces attacking the given square
|
||||
let
|
||||
queens = self.getBitboard(Queen, attacker)
|
||||
rooks = self.getBitboard(Rook, attacker) or queens
|
||||
bishops = self.getBitboard(Bishop, attacker) or queens
|
||||
occupancy = self.getOccupancy()
|
||||
squareBB = square.toBitboard()
|
||||
result = Bitboard(0)
|
||||
for rook in rooks:
|
||||
let
|
||||
blockers = occupancy and Rook.getRelevantBlockers(rook)
|
||||
moves = getRookMoves(rook, blockers)
|
||||
# Attack set intersects our chosen square
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or rook.toBitboard()
|
||||
for bishop in bishops:
|
||||
let
|
||||
blockers = occupancy and Bishop.getRelevantBlockers(bishop)
|
||||
moves = getBishopMoves(bishop, blockers)
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or bishop.toBitboard()
|
||||
|
||||
|
||||
proc getAttacksTo*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Computes the attack bitboard for the given square from
|
||||
## the given side
|
||||
result = Bitboard(0)
|
||||
result = result or self.getPawnAttacks(square, attacker)
|
||||
result = result or self.getKingAttacks(square, attacker)
|
||||
result = result or self.getKnightAttacks(square, attacker)
|
||||
result = result or self.getSlidingAttacks(square, attacker)
|
||||
|
||||
|
||||
proc isOccupancyAttacked*(self: Chessboard, square: Square, occupancy: Bitboard): bool =
|
||||
## Returns whether the given square would be attacked by the
|
||||
## enemy side if the board had the given occupancy. This function
|
||||
## is necessary mostly to make sure sliding attacks can check the
|
||||
## king properly: due to how we generate our attack bitboards, if
|
||||
## the king moved backwards along a ray from a slider we would not
|
||||
## consider it to be in check (because the ray stops at the first
|
||||
## blocker). In order to fix that, in generateKingMoves() we use this
|
||||
## function and pass in the board's occupancy without the moving king so
|
||||
## that we can pick the correct magic bitboard and ray. Also, since this
|
||||
## function doesn't need to generate all the attacks to know whether a
|
||||
## given square is unsafe, it can short circuit at the first attack and
|
||||
## exit early, unlike getAttacksTo
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
knights = self.getBitboard(Knight, nonSideToMove)
|
||||
|
||||
# Let's do the cheap ones first (the ones which are precomputed)
|
||||
if (getKnightAttacks(square) and knights) != 0:
|
||||
return true
|
||||
|
||||
let king = self.getBitboard(King, nonSideToMove)
|
||||
|
||||
if (getKingAttacks(square) and king) != 0:
|
||||
return true
|
||||
|
||||
let
|
||||
queens = self.getBitboard(Queen, nonSideToMove)
|
||||
bishops = self.getBitboard(Bishop, nonSideToMove) or queens
|
||||
|
||||
if (getBishopMoves(square, occupancy) and bishops) != 0:
|
||||
return true
|
||||
|
||||
let rooks = self.getBitboard(Rook, nonSideToMove) or queens
|
||||
|
||||
if (getRookMoves(square, occupancy) and rooks) != 0:
|
||||
return true
|
||||
|
||||
# TODO: Precompute pawn moves as well?
|
||||
let pawns = self.getBitboard(Pawn, nonSideToMove)
|
||||
|
||||
if (self.getPawnAttacks(square, nonSideToMove) and pawns) != 0:
|
||||
return true
|
||||
|
||||
|
||||
proc updateChecksAndPins*(self: Chessboard) =
|
||||
## Updates internal metadata about checks and
|
||||
## pinned pieces
|
||||
|
||||
# *Ahem*, stolen from https://github.com/Ciekce/voidstar/blob/424ac4624011271c4d1dbd743602c23f6dbda1de/src/position.rs
|
||||
# Can you tell I'm a *great* coder?
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
|
||||
# Update checks
|
||||
self.position.checkers = self.getAttacksTo(friendlyKing, nonSideToMove)
|
||||
# Update pins
|
||||
self.position.diagonalPins = Bitboard(0)
|
||||
self.position.orthogonalPins = Bitboard(0)
|
||||
|
||||
let
|
||||
diagonalAttackers = self.getBitboard(Queen, nonSideToMove) or self.getBitboard(Bishop, nonSideToMove)
|
||||
orthogonalAttackers = self.getBitboard(Queen, nonSideToMove) or self.getBitboard(Rook, nonSideToMove)
|
||||
canPinDiagonally = diagonalAttackers and getBishopMoves(friendlyKing, enemyPieces)
|
||||
canPinOrthogonally = orthogonalAttackers and getRookMoves(friendlyKing, enemyPieces)
|
||||
|
||||
for piece in canPinDiagonally:
|
||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||
|
||||
# Is the pinning ray obstructed by any of our friendly pieces? If so, the
|
||||
# piece is pinned
|
||||
if (pinningRay and friendlyPieces).countSquares() > 0:
|
||||
self.position.diagonalPins = self.position.diagonalPins or pinningRay
|
||||
|
||||
for piece in canPinOrthogonally:
|
||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||
if (pinningRay and friendlyPieces).countSquares() > 0:
|
||||
self.position.orthogonalPins = self.position.orthogonalPins or pinningRay
|
||||
|
||||
|
||||
func inCheck*(self: Chessboard): bool {.inline.} =
|
||||
## Returns if the current side to move is in check
|
||||
return self.position.checkers != 0
|
||||
|
||||
|
||||
proc canCastle*(self: Chessboard, side: PieceColor): tuple[king, queen: bool] =
|
||||
## Returns if the current side to move can castle
|
||||
return (false, false) # TODO
|
||||
|
||||
|
||||
proc update*(self: Chessboard) =
|
||||
## Updates the internal grid representation
|
||||
## according to the positional data stored
|
||||
## in the chessboard
|
||||
for i in 0..63:
|
||||
self.grid[i] = nullPiece()
|
||||
for sq in self.position.pieces[White][Pawn][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Pawn)
|
||||
for sq in self.position.pieces[Black][Pawn][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Pawn)
|
||||
for sq in self.position.pieces[White][Bishop][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Bishop)
|
||||
for sq in self.position.pieces[Black][Bishop][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Bishop)
|
||||
for sq in self.position.pieces[White][Knight][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Knight)
|
||||
for sq in self.position.pieces[Black][Knight][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Knight)
|
||||
for sq in self.position.pieces[White][Rook][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Rook)
|
||||
for sq in self.position.pieces[Black][Rook][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Rook)
|
||||
for sq in self.position.pieces[White][Queen][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Queen)
|
||||
for sq in self.position.pieces[Black][Queen][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Queen)
|
||||
for sq in self.position.pieces[White][King][]:
|
||||
self.grid[sq] = Piece(color: White, kind: King)
|
||||
for sq in self.position.pieces[Black][King][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: King)
|
||||
|
||||
|
||||
|
||||
proc `$`*(self: Chessboard): string =
|
||||
result &= "- - - - - - - -"
|
||||
var file = 8
|
||||
for i in 0..7:
|
||||
result &= "\n"
|
||||
for j in 0..7:
|
||||
let piece = self.grid[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
result &= "x "
|
||||
continue
|
||||
result &= &"{piece.toChar()} "
|
||||
result &= &"{file}"
|
||||
dec(file)
|
||||
result &= "\n- - - - - - - -"
|
||||
result &= "\na b c d e f g h"
|
||||
|
||||
|
||||
proc pretty*(self: Chessboard): string =
|
||||
## Returns a colored version of the
|
||||
## board for easier visualization
|
||||
var file = 8
|
||||
for i in 0..7:
|
||||
if i > 0:
|
||||
result &= "\n"
|
||||
for j in 0..7:
|
||||
# Equivalent to (i + j) mod 2
|
||||
# (I'm just evil)
|
||||
if ((i + j) and 1) == 0:
|
||||
result &= "\x1b[39;44;1m"
|
||||
else:
|
||||
result &= "\x1b[39;40;1m"
|
||||
let piece = self.grid[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
result &= " \x1b[0m"
|
||||
else:
|
||||
result &= &"{piece.toPretty()} \x1b[0m"
|
||||
result &= &" \x1b[33;1m{file}\x1b[0m"
|
||||
dec(file)
|
||||
|
||||
result &= "\n\x1b[31;1ma b c d e f g h"
|
||||
result &= "\x1b[0m"
|
||||
|
||||
|
||||
proc toFEN*(self: Chessboard): string =
|
||||
## Returns a FEN string of the current
|
||||
## position in the chessboard
|
||||
var skip: int
|
||||
# Piece placement data
|
||||
for i in 0..7:
|
||||
skip = 0
|
||||
for j in 0..7:
|
||||
let piece = self.grid[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
inc(skip)
|
||||
elif skip > 0:
|
||||
result &= &"{skip}{piece.toChar()}"
|
||||
skip = 0
|
||||
else:
|
||||
result &= piece.toChar()
|
||||
if skip > 0:
|
||||
result &= $skip
|
||||
if i < 7:
|
||||
result &= "/"
|
||||
result &= " "
|
||||
# Active color
|
||||
result &= (if self.position.sideToMove == White: "w" else: "b")
|
||||
result &= " "
|
||||
# Castling availability
|
||||
let castleWhite = self.position.castlingAvailability.white
|
||||
let castleBlack = self.position.castlingAvailability.black
|
||||
if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||
result &= "-"
|
||||
else:
|
||||
if castleWhite.king:
|
||||
result &= "K"
|
||||
if castleWhite.queen:
|
||||
result &= "Q"
|
||||
if castleBlack.king:
|
||||
result &= "k"
|
||||
if castleBlack.queen:
|
||||
result &= "q"
|
||||
result &= " "
|
||||
# En passant target
|
||||
if self.position.enPassantSquare == nullSquare():
|
||||
result &= "-"
|
||||
else:
|
||||
result &= self.position.enPassantSquare.toAlgebraic()
|
||||
result &= " "
|
||||
# Halfmove clock
|
||||
result &= $self.position.halfMoveClock
|
||||
result &= " "
|
||||
# Fullmove number
|
||||
result &= $self.position.fullMoveCount
|
|
@ -1,4 +1,4 @@
|
|||
import movegen
|
||||
import board
|
||||
|
||||
|
||||
import std/strformat
|
||||
|
|
|
@ -12,542 +12,22 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import std/strutils
|
||||
import std/strformat
|
||||
|
||||
|
||||
import bitboards
|
||||
import board
|
||||
import magics
|
||||
import pieces
|
||||
import moves
|
||||
import position
|
||||
import rays
|
||||
import misc
|
||||
|
||||
|
||||
export bitboards, magics, pieces, moves, position, rays
|
||||
export bitboards, magics, pieces, moves, position, rays, misc, board
|
||||
|
||||
|
||||
type
|
||||
|
||||
Chessboard* = ref object
|
||||
## A chessboard
|
||||
|
||||
# The actual board where pieces live
|
||||
grid: array[64, Piece]
|
||||
# The current position
|
||||
position*: Position
|
||||
# List of all previously reached positions
|
||||
positions*: seq[Position]
|
||||
|
||||
|
||||
# A bunch of simple utility functions and forward declarations
|
||||
proc makeMove*(self: Chessboard, move: Move): Move {.discardable.}
|
||||
proc isLegal(self: Chessboard, move: Move): bool {.inline.}
|
||||
proc doMove*(self: Chessboard, move: Move)
|
||||
proc pretty*(self: Chessboard): string
|
||||
proc spawnPiece(self: Chessboard, square: Square, piece: Piece)
|
||||
proc toFEN*(self: Chessboard): string
|
||||
proc unmakeMove*(self: Chessboard)
|
||||
proc movePiece(self: Chessboard, move: Move)
|
||||
proc removePiece(self: Chessboard, square: Square)
|
||||
proc update*(self: Chessboard)
|
||||
func inCheck*(self: Chessboard): bool {.inline.}
|
||||
proc fromChar*(c: char): Piece
|
||||
proc updateChecksAndPins*(self: Chessboard)
|
||||
|
||||
|
||||
func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
||||
func queenSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "a8".toSquare() else: "a1".toSquare())
|
||||
func longCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "c1".toSquare() else: "c8".toSquare())
|
||||
func shortCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "g1".toSquare() else: "g8".toSquare())
|
||||
func longCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "d1".toSquare() else: "d8".toSquare())
|
||||
func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "f1".toSquare() else: "f8".toSquare())
|
||||
|
||||
|
||||
proc newChessboard: Chessboard =
|
||||
## Returns a new, empty chessboard
|
||||
new(result)
|
||||
for i in 0..63:
|
||||
result.grid[i] = nullPiece()
|
||||
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White)
|
||||
|
||||
|
||||
# Indexing operations
|
||||
func `[]`(self: array[64, Piece], square: Square): Piece {.inline.} = self[square.int8]
|
||||
func `[]=`(self: var array[64, Piece], square: Square, piece: Piece) {.inline.} = self[square.int8] = piece
|
||||
|
||||
|
||||
func getBitboard*(self: Chessboard, kind: PieceKind, color: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the positional bitboard for the given piece kind and color
|
||||
return self.position.getBitboard(kind, color)
|
||||
|
||||
|
||||
func getBitboard*(self: Chessboard, piece: Piece): Bitboard {.inline.} =
|
||||
## Returns the positional bitboard for the given piece type
|
||||
return self.getBitboard(piece.kind, piece.color)
|
||||
|
||||
|
||||
proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||
## Initializes a chessboard with the
|
||||
## position encoded by the given FEN string
|
||||
result = newChessboard()
|
||||
var
|
||||
# Current square in the grid
|
||||
row: int8 = 0
|
||||
column: int8 = 0
|
||||
# Current section in the FEN string
|
||||
section = 0
|
||||
# Current index into the FEN string
|
||||
index = 0
|
||||
# Temporary variable to store a piece
|
||||
piece: Piece
|
||||
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
|
||||
while index <= fen.high():
|
||||
var c = fen[index]
|
||||
if c == ' ':
|
||||
# Next section
|
||||
inc(section)
|
||||
inc(index)
|
||||
continue
|
||||
case section:
|
||||
of 0:
|
||||
# Piece placement data
|
||||
case c.toLowerAscii():
|
||||
# Piece
|
||||
of 'r', 'n', 'b', 'q', 'k', 'p':
|
||||
let square = makeSquare(row, column)
|
||||
piece = c.fromChar()
|
||||
result.position.pieces[piece.color][piece.kind][].setBit(square)
|
||||
result.grid[square] = piece
|
||||
inc(column)
|
||||
of '/':
|
||||
# Next row
|
||||
inc(row)
|
||||
column = 0
|
||||
of '0'..'9':
|
||||
# Skip x columns
|
||||
let x = int(uint8(c) - uint8('0'))
|
||||
if x > 8:
|
||||
raise newException(ValueError, &"invalid FEN: invalid column skip size ({x} > 8)")
|
||||
column += int8(x)
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown piece identifier '{c}'")
|
||||
of 1:
|
||||
# Active color
|
||||
case c:
|
||||
of 'w':
|
||||
result.position.sideToMove = White
|
||||
of 'b':
|
||||
result.position.sideToMove = Black
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: invalid active color identifier '{c}'")
|
||||
of 2:
|
||||
# Castling availability
|
||||
case c:
|
||||
# TODO
|
||||
of '-':
|
||||
discard
|
||||
of 'K':
|
||||
result.position.castlingAvailability.white.king = true
|
||||
of 'Q':
|
||||
result.position.castlingAvailability.white.queen = true
|
||||
of 'k':
|
||||
result.position.castlingAvailability.black.king = true
|
||||
of 'q':
|
||||
result.position.castlingAvailability.black.queen = true
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||
of 3:
|
||||
# En passant target square
|
||||
case c:
|
||||
of '-':
|
||||
# Field is already uninitialized to the correct state
|
||||
discard
|
||||
else:
|
||||
result.position.enPassantSquare = fen[index..index+1].toSquare()
|
||||
# Square metadata is 2 bytes long
|
||||
inc(index)
|
||||
of 4:
|
||||
# Halfmove clock
|
||||
var s = ""
|
||||
while not fen[index].isSpaceAscii():
|
||||
s.add(fen[index])
|
||||
inc(index)
|
||||
# Backtrack so the space is seen by the
|
||||
# next iteration of the loop
|
||||
dec(index)
|
||||
result.position.halfMoveClock = parseInt(s).int8
|
||||
of 5:
|
||||
# Fullmove number
|
||||
var s = ""
|
||||
while index <= fen.high():
|
||||
s.add(fen[index])
|
||||
inc(index)
|
||||
result.position.fullMoveCount = parseInt(s).int8
|
||||
else:
|
||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||
inc(index)
|
||||
result.updateChecksAndPins()
|
||||
|
||||
|
||||
proc newDefaultChessboard*: Chessboard {.inline.} =
|
||||
## Initializes a chessboard with the
|
||||
## starting position
|
||||
return newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
|
||||
|
||||
func countPieces*(self: Chessboard, kind: PieceKind, color: PieceColor): int {.inline.} =
|
||||
## Returns the number of pieces with
|
||||
## the given color and type in the
|
||||
## current position
|
||||
return self.position.pieces[color][kind][].countSquares()
|
||||
|
||||
|
||||
func countPieces*(self: Chessboard, piece: Piece): int {.inline.} =
|
||||
## Returns the number of pieces on the board that
|
||||
## are of the same type and color as the given piece
|
||||
return self.countPieces(piece.kind, piece.color)
|
||||
|
||||
|
||||
func getPiece*(self: Chessboard, square: Square): Piece {.inline.} =
|
||||
## Gets the piece at the given square
|
||||
return self.grid[square]
|
||||
|
||||
|
||||
func getPiece*(self: Chessboard, square: string): Piece {.inline.} =
|
||||
## Gets the piece on the given square
|
||||
## in algebraic notation
|
||||
return self.getPiece(square.toSquare())
|
||||
|
||||
|
||||
func getOccupancyFor(self: Chessboard, color: PieceColor): Bitboard =
|
||||
## Get the occupancy bitboard for every piece of the given color
|
||||
result = Bitboard(0)
|
||||
for b in self.position.pieces[color][]:
|
||||
result = result or b
|
||||
|
||||
|
||||
func getOccupancy(self: Chessboard): Bitboard {.inline.} =
|
||||
## Get the occupancy bitboard for every piece on
|
||||
## the chessboard
|
||||
result = self.getOccupancyFor(Black) or self.getOccupancyFor(White)
|
||||
|
||||
|
||||
func getPawnAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the locations of the pawns attacking the given square
|
||||
let
|
||||
sq = square.toBitboard()
|
||||
pawns = self.getBitboard(Pawn, attacker)
|
||||
bottomLeft = sq.backwardLeftRelativeTo(attacker)
|
||||
bottomRight = sq.backwardRightRelativeTo(attacker)
|
||||
return pawns and (bottomLeft or bottomRight)
|
||||
|
||||
|
||||
func getKingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the location of the king if it is attacking the given square
|
||||
result = Bitboard(0)
|
||||
let
|
||||
king = self.getBitboard(King, attacker)
|
||||
if (getKingAttacks(square) and king) != 0:
|
||||
result = result or king
|
||||
|
||||
|
||||
func getKnightAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the locations of the knights attacking the given square
|
||||
let
|
||||
knights = self.getBitboard(Knight, attacker)
|
||||
result = Bitboard(0)
|
||||
for knight in knights:
|
||||
let knightBB = knight.toBitboard()
|
||||
if (getKnightAttacks(knight) and knightBB) != 0:
|
||||
result = result or knightBB
|
||||
|
||||
|
||||
proc getSlidingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the locations of the sliding pieces attacking the given square
|
||||
let
|
||||
queens = self.getBitboard(Queen, attacker)
|
||||
rooks = self.getBitboard(Rook, attacker) or queens
|
||||
bishops = self.getBitboard(Bishop, attacker) or queens
|
||||
occupancy = self.getOccupancy()
|
||||
squareBB = square.toBitboard()
|
||||
result = Bitboard(0)
|
||||
for rook in rooks:
|
||||
let
|
||||
blockers = occupancy and Rook.getRelevantBlockers(rook)
|
||||
moves = getRookMoves(rook, blockers)
|
||||
# Attack set intersects our chosen square
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or rook.toBitboard()
|
||||
for bishop in bishops:
|
||||
let
|
||||
blockers = occupancy and Bishop.getRelevantBlockers(bishop)
|
||||
moves = getBishopMoves(bishop, blockers)
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or bishop.toBitboard()
|
||||
|
||||
|
||||
proc getAttacksTo*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Computes the attack bitboard for the given square from
|
||||
## the given side
|
||||
result = Bitboard(0)
|
||||
result = result or self.getPawnAttacks(square, attacker)
|
||||
result = result or self.getKingAttacks(square, attacker)
|
||||
result = result or self.getKnightAttacks(square, attacker)
|
||||
result = result or self.getSlidingAttacks(square, attacker)
|
||||
|
||||
|
||||
proc isOccupancyAttacked*(self: Chessboard, square: Square, occupancy: Bitboard): bool =
|
||||
## Returns whether the given square would be attacked by the
|
||||
## enemy side if the board had the given occupancy. This function
|
||||
## is necessary mostly to make sure sliding attacks can check the
|
||||
## king properly: due to how we generate our attack bitboards, if
|
||||
## the king moved backwards along a ray from a slider we would not
|
||||
## consider it to be in check (because the ray stops at the first
|
||||
## blocker). In order to fix that, in generateKingMoves() we use this
|
||||
## function and pass in the board's occupancy without the moving king so
|
||||
## that we can pick the correct magic bitboard and ray. Also, since this
|
||||
## function doesn't need to generate all the attacks to know whether a
|
||||
## given square is unsafe, it can short circuit at the first attack and
|
||||
## exit early, unlike getAttacksTo
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
knights = self.getBitboard(Knight, nonSideToMove)
|
||||
|
||||
# Let's do the cheap ones first (the ones which are precomputed)
|
||||
if (getKnightAttacks(square) and knights) != 0:
|
||||
return true
|
||||
|
||||
let king = self.getBitboard(King, nonSideToMove)
|
||||
|
||||
if (getKingAttacks(square) and king) != 0:
|
||||
return true
|
||||
|
||||
let
|
||||
queens = self.getBitboard(Queen, nonSideToMove)
|
||||
bishops = self.getBitboard(Bishop, nonSideToMove) or queens
|
||||
|
||||
if (getBishopMoves(square, occupancy) and bishops) != 0:
|
||||
return true
|
||||
|
||||
let rooks = self.getBitboard(Rook, nonSideToMove) or queens
|
||||
|
||||
if (getRookMoves(square, occupancy) and rooks) != 0:
|
||||
return true
|
||||
|
||||
# TODO: Precompute pawn moves as well?
|
||||
let pawns = self.getBitboard(Pawn, nonSideToMove)
|
||||
|
||||
if (self.getPawnAttacks(square, nonSideToMove) and pawns) != 0:
|
||||
return true
|
||||
|
||||
|
||||
proc updateChecksAndPins*(self: Chessboard) =
|
||||
## Updates internal metadata about checks and
|
||||
## pinned pieces
|
||||
|
||||
# *Ahem*, stolen from https://github.com/Ciekce/voidstar/blob/424ac4624011271c4d1dbd743602c23f6dbda1de/src/position.rs
|
||||
# Can you tell I'm a *great* coder?
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
|
||||
# Update checks
|
||||
self.position.checkers = self.getAttacksTo(friendlyKing, nonSideToMove)
|
||||
# Update pins
|
||||
self.position.diagonalPins = Bitboard(0)
|
||||
self.position.orthogonalPins = Bitboard(0)
|
||||
|
||||
let
|
||||
diagonalAttackers = self.getBitboard(Queen, nonSideToMove) or self.getBitboard(Bishop, nonSideToMove)
|
||||
orthogonalAttackers = self.getBitboard(Queen, nonSideToMove) or self.getBitboard(Rook, nonSideToMove)
|
||||
canPinDiagonally = diagonalAttackers and getBishopMoves(friendlyKing, enemyPieces)
|
||||
canPinOrthogonally = orthogonalAttackers and getRookMoves(friendlyKing, enemyPieces)
|
||||
|
||||
for piece in canPinDiagonally:
|
||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||
|
||||
# Is the pinning ray obstructed by any of our friendly pieces? If so, the
|
||||
# piece is pinned
|
||||
if (pinningRay and friendlyPieces).countSquares() > 0:
|
||||
self.position.diagonalPins = self.position.diagonalPins or pinningRay
|
||||
|
||||
for piece in canPinOrthogonally:
|
||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||
if (pinningRay and friendlyPieces).countSquares() > 0:
|
||||
self.position.orthogonalPins = self.position.orthogonalPins or pinningRay
|
||||
|
||||
|
||||
func inCheck(self: Chessboard): bool {.inline.} =
|
||||
## Returns if the current side to move is in check
|
||||
return self.position.checkers != 0
|
||||
|
||||
|
||||
proc canCastle*(self: Chessboard, side: PieceColor): tuple[king, queen: bool] =
|
||||
## Returns if the current side to move can castle
|
||||
return (false, false) # TODO
|
||||
|
||||
|
||||
proc removePieceFromBitboard(self: Chessboard, square: Square) =
|
||||
## Removes a piece at the given square in the chessboard from
|
||||
## its respective bitboard
|
||||
let piece = self.grid[square]
|
||||
self.position.pieces[piece.color][piece.kind][].clearBit(square)
|
||||
|
||||
|
||||
proc addPieceToBitboard(self: Chessboard, square: Square, piece: Piece) =
|
||||
## Adds the given piece at the given square in the chessboard to
|
||||
## its respective bitboard
|
||||
self.position.pieces[piece.color][piece.kind][].setBit(square)
|
||||
|
||||
|
||||
proc spawnPiece(self: Chessboard, square: Square, piece: Piece) =
|
||||
## Internal helper to "spawn" a given piece at the given
|
||||
## square
|
||||
when not defined(danger):
|
||||
doAssert self.grid[square].kind == Empty
|
||||
self.addPieceToBitboard(square, piece)
|
||||
self.grid[square] = piece
|
||||
|
||||
|
||||
proc removePiece(self: Chessboard, square: Square) =
|
||||
## Removes a piece from the board, updating necessary
|
||||
## metadata
|
||||
var piece = self.grid[square]
|
||||
when not defined(danger):
|
||||
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
||||
self.removePieceFromBitboard(square)
|
||||
self.grid[square] = nullPiece()
|
||||
|
||||
|
||||
proc movePiece(self: Chessboard, move: Move) =
|
||||
## Internal helper to move a piece from
|
||||
## its current square to a target square
|
||||
let piece = self.grid[move.startSquare]
|
||||
when not defined(danger):
|
||||
let targetSquare = self.getPiece(move.targetSquare)
|
||||
if targetSquare.color != None:
|
||||
raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}: {move}")
|
||||
# Update positional metadata
|
||||
self.removePiece(move.startSquare)
|
||||
self.spawnPiece(move.targetSquare, piece)
|
||||
|
||||
|
||||
proc doMove*(self: Chessboard, move: Move) =
|
||||
## Internal function called by makeMove after
|
||||
## performing legality checks. Can be used in
|
||||
## performance-critical paths where a move is
|
||||
## already known to be legal (i.e. during search)
|
||||
|
||||
# Record final position for future reference
|
||||
self.positions.add(self.position)
|
||||
|
||||
# Final checks
|
||||
let piece = self.grid[move.startSquare]
|
||||
when not defined(danger):
|
||||
doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}"
|
||||
|
||||
var
|
||||
halfMoveClock = self.position.halfMoveClock
|
||||
fullMoveCount = self.position.fullMoveCount
|
||||
castlingRights = self.position.castlingRights
|
||||
enPassantTarget = nullSquare()
|
||||
# Needed to detect draw by the 50 move rule
|
||||
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
|
||||
# Number of half-moves since the last reversible half-move
|
||||
halfMoveClock = 0
|
||||
else:
|
||||
inc(halfMoveClock)
|
||||
if piece.color == Black:
|
||||
inc(fullMoveCount)
|
||||
|
||||
if move.isDoublePush():
|
||||
enPassantTarget = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
|
||||
|
||||
# Create new position
|
||||
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||
halfMoveClock: halfMoveClock,
|
||||
fullMoveCount: fullMoveCount,
|
||||
sideToMove: self.position.sideToMove.opposite(),
|
||||
castlingRights: castlingRights,
|
||||
enPassantSquare: enPassantTarget,
|
||||
pieces: self.position.pieces
|
||||
)
|
||||
# Update position metadata
|
||||
|
||||
if move.isEnPassant():
|
||||
# Make the en passant pawn disappear
|
||||
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare())
|
||||
|
||||
if move.isCapture():
|
||||
# Get rid of captured pieces
|
||||
self.removePiece(move.targetSquare)
|
||||
|
||||
# Move the piece to its target square
|
||||
self.movePiece(move)
|
||||
# TODO: Castling!
|
||||
if move.isPromotion():
|
||||
# Move is a pawn promotion: get rid of the pawn
|
||||
# and spawn a new piece
|
||||
self.removePiece(move.targetSquare)
|
||||
case move.getPromotionType():
|
||||
of PromoteToBishop:
|
||||
self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color))
|
||||
of PromoteToKnight:
|
||||
self.spawnPiece(move.targetSquare, Piece(kind: Knight, color: piece.color))
|
||||
of PromoteToRook:
|
||||
self.spawnPiece(move.targetSquare, Piece(kind: Rook, color: piece.color))
|
||||
of PromoteToQueen:
|
||||
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
|
||||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
# Updates checks and pins for the side to move
|
||||
self.updateChecksAndPins()
|
||||
|
||||
|
||||
proc update*(self: Chessboard) =
|
||||
## Updates the internal grid representation
|
||||
## according to the positional data stored
|
||||
## in the chessboard
|
||||
for i in 0..63:
|
||||
self.grid[i] = nullPiece()
|
||||
for sq in self.position.pieces[White][Pawn][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Pawn)
|
||||
for sq in self.position.pieces[Black][Pawn][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Pawn)
|
||||
for sq in self.position.pieces[White][Bishop][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Bishop)
|
||||
for sq in self.position.pieces[Black][Bishop][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Bishop)
|
||||
for sq in self.position.pieces[White][Knight][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Knight)
|
||||
for sq in self.position.pieces[Black][Knight][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Knight)
|
||||
for sq in self.position.pieces[White][Rook][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Rook)
|
||||
for sq in self.position.pieces[Black][Rook][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Rook)
|
||||
for sq in self.position.pieces[White][Queen][]:
|
||||
self.grid[sq] = Piece(color: White, kind: Queen)
|
||||
for sq in self.position.pieces[Black][Queen][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: Queen)
|
||||
for sq in self.position.pieces[White][King][]:
|
||||
self.grid[sq] = Piece(color: White, kind: King)
|
||||
for sq in self.position.pieces[Black][King][]:
|
||||
self.grid[sq] = Piece(color: Black, kind: King)
|
||||
|
||||
|
||||
proc unmakeMove*(self: Chessboard) =
|
||||
## Reverts to the previous board position,
|
||||
## if one exists
|
||||
self.position = self.positions.pop()
|
||||
self.update()
|
||||
|
||||
|
||||
proc generatePawnMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
||||
let
|
||||
|
@ -726,7 +206,126 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
|||
# Queens are just handled rooks + bishops
|
||||
|
||||
|
||||
proc isLegal(self: Chessboard, move: Move): bool {.inline.} =
|
||||
|
||||
proc removePieceFromBitboard(self: Chessboard, square: Square) =
|
||||
## Removes a piece at the given square in the chessboard from
|
||||
## its respective bitboard
|
||||
let piece = self.grid[square]
|
||||
self.position.pieces[piece.color][piece.kind][].clearBit(square)
|
||||
|
||||
|
||||
proc addPieceToBitboard(self: Chessboard, square: Square, piece: Piece) =
|
||||
## Adds the given piece at the given square in the chessboard to
|
||||
## its respective bitboard
|
||||
self.position.pieces[piece.color][piece.kind][].setBit(square)
|
||||
|
||||
|
||||
proc spawnPiece(self: Chessboard, square: Square, piece: Piece) =
|
||||
## Internal helper to "spawn" a given piece at the given
|
||||
## square
|
||||
when not defined(danger):
|
||||
doAssert self.grid[square].kind == Empty
|
||||
self.addPieceToBitboard(square, piece)
|
||||
self.grid[square] = piece
|
||||
|
||||
|
||||
proc removePiece(self: Chessboard, square: Square) =
|
||||
## Removes a piece from the board, updating necessary
|
||||
## metadata
|
||||
var piece = self.grid[square]
|
||||
when not defined(danger):
|
||||
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
||||
self.removePieceFromBitboard(square)
|
||||
self.grid[square] = nullPiece()
|
||||
|
||||
|
||||
proc movePiece(self: Chessboard, move: Move) =
|
||||
## Internal helper to move a piece from
|
||||
## its current square to a target square
|
||||
let piece = self.grid[move.startSquare]
|
||||
when not defined(danger):
|
||||
let targetSquare = self.getPiece(move.targetSquare)
|
||||
if targetSquare.color != None:
|
||||
raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}: {move}")
|
||||
# Update positional metadata
|
||||
self.removePiece(move.startSquare)
|
||||
self.spawnPiece(move.targetSquare, piece)
|
||||
|
||||
|
||||
proc doMove*(self: Chessboard, move: Move) =
|
||||
## Internal function called by makeMove after
|
||||
## performing legality checks. Can be used in
|
||||
## performance-critical paths where a move is
|
||||
## already known to be legal (i.e. during search)
|
||||
|
||||
# Record final position for future reference
|
||||
self.positions.add(self.position)
|
||||
|
||||
# Final checks
|
||||
let piece = self.grid[move.startSquare]
|
||||
when not defined(danger):
|
||||
doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}"
|
||||
|
||||
var
|
||||
halfMoveClock = self.position.halfMoveClock
|
||||
fullMoveCount = self.position.fullMoveCount
|
||||
castlingRights = self.position.castlingRights
|
||||
enPassantTarget = nullSquare()
|
||||
# Needed to detect draw by the 50 move rule
|
||||
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
|
||||
# Number of half-moves since the last reversible half-move
|
||||
halfMoveClock = 0
|
||||
else:
|
||||
inc(halfMoveClock)
|
||||
if piece.color == Black:
|
||||
inc(fullMoveCount)
|
||||
|
||||
if move.isDoublePush():
|
||||
enPassantTarget = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
|
||||
|
||||
# Create new position
|
||||
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||
halfMoveClock: halfMoveClock,
|
||||
fullMoveCount: fullMoveCount,
|
||||
sideToMove: self.position.sideToMove.opposite(),
|
||||
castlingRights: castlingRights,
|
||||
enPassantSquare: enPassantTarget,
|
||||
pieces: self.position.pieces
|
||||
)
|
||||
# Update position metadata
|
||||
|
||||
if move.isEnPassant():
|
||||
# Make the en passant pawn disappear
|
||||
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare())
|
||||
|
||||
if move.isCapture():
|
||||
# Get rid of captured pieces
|
||||
self.removePiece(move.targetSquare)
|
||||
|
||||
# Move the piece to its target square
|
||||
self.movePiece(move)
|
||||
# TODO: Castling!
|
||||
if move.isPromotion():
|
||||
# Move is a pawn promotion: get rid of the pawn
|
||||
# and spawn a new piece
|
||||
self.removePiece(move.targetSquare)
|
||||
case move.getPromotionType():
|
||||
of PromoteToBishop:
|
||||
self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color))
|
||||
of PromoteToKnight:
|
||||
self.spawnPiece(move.targetSquare, Piece(kind: Knight, color: piece.color))
|
||||
of PromoteToRook:
|
||||
self.spawnPiece(move.targetSquare, Piece(kind: Rook, color: piece.color))
|
||||
of PromoteToQueen:
|
||||
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
|
||||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
# Updates checks and pins for the side to move
|
||||
self.updateChecksAndPins()
|
||||
|
||||
|
||||
proc isLegal*(self: Chessboard, move: Move): bool {.inline.} =
|
||||
## Returns whether the given move is legal
|
||||
var moves = MoveList()
|
||||
self.generateMoves(moves)
|
||||
|
@ -736,183 +335,15 @@ proc isLegal(self: Chessboard, move: Move): bool {.inline.} =
|
|||
proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} =
|
||||
## Makes a move on the board
|
||||
result = move
|
||||
# Updates checks and pins for the side to move
|
||||
self.updateChecksAndPins()
|
||||
if not self.isLegal(move):
|
||||
return nullMove()
|
||||
self.doMove(move)
|
||||
|
||||
|
||||
proc toChar*(piece: Piece): char =
|
||||
case piece.kind:
|
||||
of Bishop:
|
||||
result = 'b'
|
||||
of King:
|
||||
result = 'k'
|
||||
of Knight:
|
||||
result = 'n'
|
||||
of Pawn:
|
||||
result = 'p'
|
||||
of Queen:
|
||||
result = 'q'
|
||||
of Rook:
|
||||
result = 'r'
|
||||
else:
|
||||
discard
|
||||
if piece.color == White:
|
||||
result = result.toUpperAscii()
|
||||
|
||||
|
||||
proc fromChar*(c: char): Piece =
|
||||
var
|
||||
kind: PieceKind
|
||||
color = Black
|
||||
case c.toLowerAscii():
|
||||
of 'b':
|
||||
kind = Bishop
|
||||
of 'k':
|
||||
kind = King
|
||||
of 'n':
|
||||
kind = Knight
|
||||
of 'p':
|
||||
kind = Pawn
|
||||
of 'q':
|
||||
kind = Queen
|
||||
of 'r':
|
||||
kind = Rook
|
||||
else:
|
||||
discard
|
||||
if c.isUpperAscii():
|
||||
color = White
|
||||
result = Piece(kind: kind, color: color)
|
||||
|
||||
|
||||
proc `$`*(self: Chessboard): string =
|
||||
result &= "- - - - - - - -"
|
||||
var file = 8
|
||||
for i in 0..7:
|
||||
result &= "\n"
|
||||
for j in 0..7:
|
||||
let piece = self.grid[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
result &= "x "
|
||||
continue
|
||||
result &= &"{piece.toChar()} "
|
||||
result &= &"{file}"
|
||||
dec(file)
|
||||
result &= "\n- - - - - - - -"
|
||||
result &= "\na b c d e f g h"
|
||||
|
||||
|
||||
proc toPretty*(piece: Piece): string =
|
||||
case piece.color:
|
||||
of White:
|
||||
case piece.kind:
|
||||
of King:
|
||||
return "\U2654"
|
||||
of Queen:
|
||||
return "\U2655"
|
||||
of Rook:
|
||||
return "\U2656"
|
||||
of Bishop:
|
||||
return "\U2657"
|
||||
of Knight:
|
||||
return "\U2658"
|
||||
of Pawn:
|
||||
return "\U2659"
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
case piece.kind:
|
||||
of King:
|
||||
return "\U265A"
|
||||
of Queen:
|
||||
return "\U265B"
|
||||
of Rook:
|
||||
return "\U265C"
|
||||
of Bishop:
|
||||
return "\U265D"
|
||||
of Knight:
|
||||
return "\U265E"
|
||||
of Pawn:
|
||||
return "\240\159\168\133"
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc pretty*(self: Chessboard): string =
|
||||
## Returns a colored version of the
|
||||
## board for easier visualization
|
||||
var file = 8
|
||||
for i in 0..7:
|
||||
if i > 0:
|
||||
result &= "\n"
|
||||
for j in 0..7:
|
||||
# Equivalent to (i + j) mod 2
|
||||
# (I'm just evil)
|
||||
if ((i + j) and 1) == 0:
|
||||
result &= "\x1b[39;44;1m"
|
||||
else:
|
||||
result &= "\x1b[39;40;1m"
|
||||
let piece = self.grid[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
result &= " \x1b[0m"
|
||||
else:
|
||||
result &= &"{piece.toPretty()} \x1b[0m"
|
||||
result &= &" \x1b[33;1m{file}\x1b[0m"
|
||||
dec(file)
|
||||
|
||||
result &= "\n\x1b[31;1ma b c d e f g h"
|
||||
result &= "\x1b[0m"
|
||||
|
||||
|
||||
proc toFEN*(self: Chessboard): string =
|
||||
## Returns a FEN string of the current
|
||||
## position in the chessboard
|
||||
var skip: int
|
||||
# Piece placement data
|
||||
for i in 0..7:
|
||||
skip = 0
|
||||
for j in 0..7:
|
||||
let piece = self.grid[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
inc(skip)
|
||||
elif skip > 0:
|
||||
result &= &"{skip}{piece.toChar()}"
|
||||
skip = 0
|
||||
else:
|
||||
result &= piece.toChar()
|
||||
if skip > 0:
|
||||
result &= $skip
|
||||
if i < 7:
|
||||
result &= "/"
|
||||
result &= " "
|
||||
# Active color
|
||||
result &= (if self.position.sideToMove == White: "w" else: "b")
|
||||
result &= " "
|
||||
# Castling availability
|
||||
let castleWhite = self.position.castlingAvailability.white
|
||||
let castleBlack = self.position.castlingAvailability.black
|
||||
if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||
result &= "-"
|
||||
else:
|
||||
if castleWhite.king:
|
||||
result &= "K"
|
||||
if castleWhite.queen:
|
||||
result &= "Q"
|
||||
if castleBlack.king:
|
||||
result &= "k"
|
||||
if castleBlack.queen:
|
||||
result &= "q"
|
||||
result &= " "
|
||||
# En passant target
|
||||
if self.position.enPassantSquare == nullSquare():
|
||||
result &= "-"
|
||||
else:
|
||||
result &= self.position.enPassantSquare.toAlgebraic()
|
||||
result &= " "
|
||||
# Halfmove clock
|
||||
result &= $self.position.halfMoveClock
|
||||
result &= " "
|
||||
# Fullmove number
|
||||
result &= $self.position.fullMoveCount
|
||||
proc unmakeMove*(self: Chessboard) =
|
||||
## Reverts to the previous board position,
|
||||
## if one exists
|
||||
self.position = self.positions.pop()
|
||||
self.update()
|
||||
|
|
|
@ -76,4 +76,93 @@ proc toAlgebraic*(square: Square): string {.inline.} =
|
|||
return &"{file}{rank}"
|
||||
|
||||
|
||||
proc `$`*(square: Square): string = square.toAlgebraic()
|
||||
proc `$`*(square: Square): string = square.toAlgebraic()
|
||||
|
||||
func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
||||
func queenSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "a8".toSquare() else: "a1".toSquare())
|
||||
func longCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "c1".toSquare() else: "c8".toSquare())
|
||||
func shortCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "g1".toSquare() else: "g8".toSquare())
|
||||
func longCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "d1".toSquare() else: "d8".toSquare())
|
||||
func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "f1".toSquare() else: "f8".toSquare())
|
||||
|
||||
|
||||
proc toPretty*(piece: Piece): string =
|
||||
case piece.color:
|
||||
of White:
|
||||
case piece.kind:
|
||||
of King:
|
||||
return "\U2654"
|
||||
of Queen:
|
||||
return "\U2655"
|
||||
of Rook:
|
||||
return "\U2656"
|
||||
of Bishop:
|
||||
return "\U2657"
|
||||
of Knight:
|
||||
return "\U2658"
|
||||
of Pawn:
|
||||
return "\U2659"
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
case piece.kind:
|
||||
of King:
|
||||
return "\U265A"
|
||||
of Queen:
|
||||
return "\U265B"
|
||||
of Rook:
|
||||
return "\U265C"
|
||||
of Bishop:
|
||||
return "\U265D"
|
||||
of Knight:
|
||||
return "\U265E"
|
||||
of Pawn:
|
||||
return "\240\159\168\133"
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
func toChar*(piece: Piece): char =
|
||||
case piece.kind:
|
||||
of Bishop:
|
||||
result = 'b'
|
||||
of King:
|
||||
result = 'k'
|
||||
of Knight:
|
||||
result = 'n'
|
||||
of Pawn:
|
||||
result = 'p'
|
||||
of Queen:
|
||||
result = 'q'
|
||||
of Rook:
|
||||
result = 'r'
|
||||
else:
|
||||
discard
|
||||
if piece.color == White:
|
||||
result = result.toUpperAscii()
|
||||
|
||||
|
||||
func fromChar*(c: char): Piece =
|
||||
var
|
||||
kind: PieceKind
|
||||
color = Black
|
||||
case c.toLowerAscii():
|
||||
of 'b':
|
||||
kind = Bishop
|
||||
of 'k':
|
||||
kind = King
|
||||
of 'n':
|
||||
kind = Knight
|
||||
of 'p':
|
||||
kind = Pawn
|
||||
of 'q':
|
||||
kind = Queen
|
||||
of 'r':
|
||||
kind = Rook
|
||||
else:
|
||||
discard
|
||||
if c.isUpperAscii():
|
||||
color = White
|
||||
result = Piece(kind: kind, color: color)
|
Loading…
Reference in New Issue