Refactoring, cleanup, improve modularity
This commit is contained in:
parent
70e938d6c0
commit
78b2d95497
|
@ -20,111 +20,41 @@ import nimfishpkg/bitboards
|
||||||
import nimfishpkg/magics
|
import nimfishpkg/magics
|
||||||
import nimfishpkg/pieces
|
import nimfishpkg/pieces
|
||||||
import nimfishpkg/moves
|
import nimfishpkg/moves
|
||||||
|
import nimfishpkg/position
|
||||||
|
|
||||||
export bitboards, magics, pieces, moves
|
|
||||||
|
export bitboards, magics, pieces, moves, position
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
Position* = object
|
Chessboard* = ref object
|
||||||
## A chess position
|
## A chessboard
|
||||||
|
|
||||||
# Castling metadata. Updated on every move
|
|
||||||
castlingRights: array[64, uint8]
|
|
||||||
# Number of half-moves that were performed
|
|
||||||
# to reach this position starting from the
|
|
||||||
# root of the tree
|
|
||||||
plyFromRoot: int8
|
|
||||||
# Number of half moves since
|
|
||||||
# last piece capture or pawn movement.
|
|
||||||
# Used for the 50-move rule
|
|
||||||
halfMoveClock: int8
|
|
||||||
# Full move counter. Increments
|
|
||||||
# every 2 ply (half-moves)
|
|
||||||
fullMoveCount: int8
|
|
||||||
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
|
||||||
enPassantSquare*: Square
|
|
||||||
|
|
||||||
# The side to move
|
|
||||||
sideToMove: PieceColor
|
|
||||||
# Positional bitboards for all pieces
|
|
||||||
pieces: array[2, array[6, Bitboard]]
|
|
||||||
# Pinned pieces for the current side to move
|
|
||||||
pins: Bitboard
|
|
||||||
# Pieces checking the current side to move
|
|
||||||
checkers: Bitboard
|
|
||||||
|
|
||||||
|
|
||||||
ChessBoard* = ref object
|
|
||||||
## A chess board object
|
|
||||||
|
|
||||||
# The actual board where pieces live
|
# The actual board where pieces live
|
||||||
grid: array[64, Piece]
|
grid: array[64, Piece]
|
||||||
# The current position
|
# The current position
|
||||||
position: Position
|
position*: Position
|
||||||
# List of all previously reached positions
|
# List of all previously reached positions
|
||||||
positions: seq[Position]
|
positions: seq[Position]
|
||||||
|
|
||||||
|
|
||||||
# A bunch of simple utility functions and forward declarations
|
# A bunch of simple utility functions and forward declarations
|
||||||
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.}
|
proc makeMove*(self: Chessboard, move: Move): Move {.discardable.}
|
||||||
proc isLegal(self: ChessBoard, move: Move): bool {.inline.}
|
proc isLegal(self: Chessboard, move: Move): bool {.inline.}
|
||||||
proc doMove*(self: ChessBoard, move: Move)
|
proc doMove*(self: Chessboard, move: Move)
|
||||||
proc pretty*(self: ChessBoard): string
|
proc pretty*(self: Chessboard): string
|
||||||
proc spawnPiece(self: ChessBoard, square: Square, piece: Piece)
|
proc spawnPiece(self: Chessboard, square: Square, piece: Piece)
|
||||||
proc toFEN*(self: ChessBoard): string
|
proc toFEN*(self: Chessboard): string
|
||||||
proc unmakeMove*(self: ChessBoard)
|
proc unmakeMove*(self: Chessboard)
|
||||||
proc movePiece(self: ChessBoard, move: Move)
|
proc movePiece(self: Chessboard, move: Move)
|
||||||
proc removePiece(self: ChessBoard, square: Square)
|
proc removePiece(self: Chessboard, square: Square)
|
||||||
|
proc update*(self: Chessboard)
|
||||||
|
func inCheck*(self: Chessboard): bool {.inline.}
|
||||||
|
proc fromChar*(c: char): Piece
|
||||||
|
|
||||||
|
|
||||||
proc update*(self: ChessBoard)
|
|
||||||
|
|
||||||
|
|
||||||
func setSideToMove*(self: ChessBoard, side: PieceColor) {.inline.} =
|
|
||||||
self.position.sideToMove = side
|
|
||||||
|
|
||||||
# A bunch of getters
|
|
||||||
func getSideToMove*(self: ChessBoard): PieceColor {.inline.} =
|
|
||||||
## Returns the currently side to move
|
|
||||||
return self.position.sideToMove
|
|
||||||
|
|
||||||
|
|
||||||
func getEnPassantTarget*(self: ChessBoard): Square {.inline.} =
|
|
||||||
## Returns the current en passant target square
|
|
||||||
return self.position.enPassantSquare
|
|
||||||
|
|
||||||
|
|
||||||
func getPlyFromRoot*(self: ChessBoard): int8 {.inline.} =
|
|
||||||
## Returns the current distance from the root in plys
|
|
||||||
return self.position.plyFromRoot
|
|
||||||
|
|
||||||
|
|
||||||
func getMoveCount*(self: ChessBoard): int {.inline.} =
|
|
||||||
## Returns the number of full moves that
|
|
||||||
## have been played
|
|
||||||
return self.position.fullMoveCount
|
|
||||||
|
|
||||||
|
|
||||||
func getHalfMoveCount*(self: ChessBoard): int {.inline.} =
|
|
||||||
## Returns the current number of half-moves
|
|
||||||
## since the last irreversible move
|
|
||||||
return self.position.halfMoveClock
|
|
||||||
|
|
||||||
|
|
||||||
func getKingStartingSquare*(color: PieceColor): Square {.inline.} =
|
|
||||||
## Retrieves the starting square of the king
|
|
||||||
## for the given color
|
|
||||||
case color:
|
|
||||||
of White:
|
|
||||||
return "e1".toSquare()
|
|
||||||
of Black:
|
|
||||||
return "e8".toSquare()
|
|
||||||
else:
|
|
||||||
discard
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: Check this shit.
|
|
||||||
func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
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 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 longCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "c1".toSquare() else: "c8".toSquare())
|
||||||
|
@ -133,18 +63,12 @@ func longCastleRook*(color: PieceColor): Square {.inline.} = (if color == White:
|
||||||
func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "f1".toSquare() else: "f8".toSquare())
|
func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "f1".toSquare() else: "f8".toSquare())
|
||||||
|
|
||||||
|
|
||||||
proc inCheck*(self: ChessBoard): bool
|
proc newChessboard: Chessboard =
|
||||||
proc fromChar*(c: char): Piece
|
|
||||||
|
|
||||||
|
|
||||||
proc newChessboard: ChessBoard =
|
|
||||||
## Returns a new, empty chessboard
|
## Returns a new, empty chessboard
|
||||||
new(result)
|
new(result)
|
||||||
for i in 0..63:
|
for i in 0..63:
|
||||||
result.grid[i] = nullPiece()
|
result.grid[i] = nullPiece()
|
||||||
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White, pieces: [
|
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White)
|
||||||
[Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)],
|
|
||||||
[Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)]])
|
|
||||||
|
|
||||||
|
|
||||||
# Indexing operations
|
# Indexing operations
|
||||||
|
@ -152,23 +76,17 @@ func `[]`(self: array[64, Piece], square: Square): Piece {.inline.} = self[squar
|
||||||
func `[]=`(self: var array[64, Piece], square: Square, piece: Piece) {.inline.} = self[square.int8] = piece
|
func `[]=`(self: var array[64, Piece], square: Square, piece: Piece) {.inline.} = self[square.int8] = piece
|
||||||
|
|
||||||
|
|
||||||
func `[]`(self: array[2, array[6, Bitboard]], color: PieceColor): ptr array[6, Bitboard] {.inline.} = addr self[color.int]
|
func getBitboard*(self: Chessboard, kind: PieceKind, color: PieceColor): Bitboard {.inline.} =
|
||||||
func `[]`(self: array[6, Bitboard], kind: PieceKind): Bitboard {.inline.} = self[kind.int]
|
|
||||||
func `[]=`(self: var array[6, Bitboard], kind: PieceKind, bitboard: Bitboard) {.inline.} = self[kind.int] = bitboard
|
|
||||||
|
|
||||||
|
|
||||||
func getBitboard*(self: ChessBoard, kind: PieceKind, color: PieceColor): Bitboard =
|
|
||||||
## Returns the positional bitboard for the given piece kind and color
|
## Returns the positional bitboard for the given piece kind and color
|
||||||
return self.position.pieces[color.int][kind.int]
|
return self.position.getBitboard(kind, color)
|
||||||
|
|
||||||
|
|
||||||
|
func getBitboard*(self: Chessboard, piece: Piece): Bitboard {.inline.} =
|
||||||
func getBitboard*(self: ChessBoard, piece: Piece): Bitboard =
|
|
||||||
## Returns the positional bitboard for the given piece type
|
## Returns the positional bitboard for the given piece type
|
||||||
return self.getBitboard(piece.kind, piece.color)
|
return self.getBitboard(piece.kind, piece.color)
|
||||||
|
|
||||||
|
|
||||||
proc newChessboardFromFEN*(fen: string): ChessBoard =
|
proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||||
## Initializes a chessboard with the
|
## Initializes a chessboard with the
|
||||||
## position encoded by the given FEN string
|
## position encoded by the given FEN string
|
||||||
result = newChessboard()
|
result = newChessboard()
|
||||||
|
@ -196,11 +114,9 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
|
||||||
case c.toLowerAscii():
|
case c.toLowerAscii():
|
||||||
# Piece
|
# Piece
|
||||||
of 'r', 'n', 'b', 'q', 'k', 'p':
|
of 'r', 'n', 'b', 'q', 'k', 'p':
|
||||||
let square: Square = makeSquare(row, column)
|
let square = makeSquare(row, column)
|
||||||
piece = c.fromChar()
|
piece = c.fromChar()
|
||||||
var b = result.position.pieces[piece.color][piece.kind]
|
result.position.pieces[piece.color][piece.kind][].setBit(square)
|
||||||
b.setBit(square)
|
|
||||||
result.position.pieces[piece.color][piece.kind] = b
|
|
||||||
result.grid[square] = piece
|
result.grid[square] = piece
|
||||||
inc(column)
|
inc(column)
|
||||||
of '/':
|
of '/':
|
||||||
|
@ -232,16 +148,12 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
|
||||||
discard
|
discard
|
||||||
of 'K':
|
of 'K':
|
||||||
discard
|
discard
|
||||||
# result.position.castlingRightsAvailable.white.king = true
|
|
||||||
of 'Q':
|
of 'Q':
|
||||||
discard
|
discard
|
||||||
# result.position.castlingRightsAvailable.white.queen = true
|
|
||||||
of 'k':
|
of 'k':
|
||||||
discard
|
discard
|
||||||
# result.position.castlingRightsAvailable.black.king = true
|
|
||||||
of 'q':
|
of 'q':
|
||||||
discard
|
discard
|
||||||
# result.position.castlingRightsAvailable.black.queen = true
|
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||||
of 3:
|
of 3:
|
||||||
|
@ -276,114 +188,50 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
|
||||||
inc(index)
|
inc(index)
|
||||||
|
|
||||||
|
|
||||||
proc newDefaultChessboard*: ChessBoard {.inline.} =
|
proc newDefaultChessboard*: Chessboard {.inline.} =
|
||||||
## Initializes a chessboard with the
|
## Initializes a chessboard with the
|
||||||
## starting position
|
## starting position
|
||||||
return newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
return newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||||
|
|
||||||
|
|
||||||
proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int =
|
func countPieces*(self: Chessboard, kind: PieceKind, color: PieceColor): int {.inline.} =
|
||||||
## Returns the number of pieces with
|
## Returns the number of pieces with
|
||||||
## the given color and type in the
|
## the given color and type in the
|
||||||
## current position
|
## current position
|
||||||
return self.position.pieces[color][kind].countSetBits()
|
return self.position.pieces[color][kind][].countSquares()
|
||||||
|
|
||||||
|
|
||||||
|
func countPieces*(self: Chessboard, piece: Piece): int {.inline.} =
|
||||||
func countPieces*(self: ChessBoard, piece: Piece): int {.inline.} =
|
|
||||||
## Returns the number of pieces on the board that
|
## Returns the number of pieces on the board that
|
||||||
## are of the same type and color as the given piece
|
## are of the same type and color as the given piece
|
||||||
return self.countPieces(piece.kind, piece.color)
|
return self.countPieces(piece.kind, piece.color)
|
||||||
|
|
||||||
|
|
||||||
proc getPiece*(self: ChessBoard, square: Square): Piece {.inline.} =
|
func getPiece*(self: Chessboard, square: Square): Piece {.inline.} =
|
||||||
## Gets the piece at the given square
|
## Gets the piece at the given square
|
||||||
return self.grid[square]
|
return self.grid[square]
|
||||||
|
|
||||||
|
|
||||||
proc getPiece*(self: ChessBoard, square: string): Piece {.inline.} =
|
func getPiece*(self: Chessboard, square: string): Piece {.inline.} =
|
||||||
## Gets the piece on the given square
|
## Gets the piece on the given square
|
||||||
## in algebraic notation
|
## in algebraic notation
|
||||||
return self.getPiece(square.toSquare())
|
return self.getPiece(square.toSquare())
|
||||||
|
|
||||||
|
|
||||||
func isPromotion*(move: Move): bool {.inline.} =
|
func getOccupancyFor(self: Chessboard, color: PieceColor): Bitboard =
|
||||||
## Returns whether the given move is a
|
|
||||||
## pawn promotion
|
|
||||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
|
||||||
if (move.flags and promotion.uint16) != 0:
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func getPromotionType*(move: Move): MoveFlag {.inline.} =
|
|
||||||
## Returns the promotion type of the given move.
|
|
||||||
## The return value of this function is only valid
|
|
||||||
## if isPromotion() returns true
|
|
||||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
|
||||||
if (move.flags and promotion.uint16) != 0:
|
|
||||||
return promotion
|
|
||||||
|
|
||||||
|
|
||||||
func isCapture*(move: Move): bool {.inline.} =
|
|
||||||
## Returns whether the given move is a
|
|
||||||
## cature
|
|
||||||
result = (move.flags and Capture.uint16) == Capture.uint16
|
|
||||||
|
|
||||||
|
|
||||||
func isCastling*(move: Move): bool {.inline.} =
|
|
||||||
## Returns whether the given move is a
|
|
||||||
## castle
|
|
||||||
for flag in [CastleLong, CastleShort]:
|
|
||||||
if (move.flags and flag.uint16) != 0:
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
func getCastlingType*(move: Move): MoveFlag {.inline.} =
|
|
||||||
## Returns the castlingRights type of the given move.
|
|
||||||
## The return value of this function is only valid
|
|
||||||
## if isCastling() returns true
|
|
||||||
for flag in [CastleLong, CastleShort]:
|
|
||||||
if (move.flags and flag.uint16) != 0:
|
|
||||||
return flag
|
|
||||||
|
|
||||||
|
|
||||||
func isEnPassant*(move: Move): bool {.inline.} =
|
|
||||||
## Returns whether the given move is an
|
|
||||||
## en passant capture
|
|
||||||
result = (move.flags and EnPassant.uint16) != 0
|
|
||||||
|
|
||||||
|
|
||||||
func isDoublePush*(move: Move): bool {.inline.} =
|
|
||||||
## Returns whether the given move is a
|
|
||||||
## double pawn push
|
|
||||||
result = (move.flags and DoublePush.uint16) != 0
|
|
||||||
|
|
||||||
|
|
||||||
func getFlags*(move: Move): seq[MoveFlag] =
|
|
||||||
## Gets all the flags of this move
|
|
||||||
for flag in [EnPassant, Capture, DoublePush, CastleLong, CastleShort,
|
|
||||||
PromoteToBishop, PromoteToKnight, PromoteToQueen,
|
|
||||||
PromoteToRook]:
|
|
||||||
if (move.flags and flag.uint16) == flag.uint16:
|
|
||||||
result.add(flag)
|
|
||||||
if result.len() == 0:
|
|
||||||
result.add(Default)
|
|
||||||
|
|
||||||
|
|
||||||
proc getOccupancyFor(self: ChessBoard, color: PieceColor): Bitboard =
|
|
||||||
## Get the occupancy bitboard for every piece of the given color
|
## Get the occupancy bitboard for every piece of the given color
|
||||||
result = Bitboard(0)
|
result = Bitboard(0)
|
||||||
for b in self.position.pieces[color][]:
|
for b in self.position.pieces[color][]:
|
||||||
result = result or b
|
result = result or b
|
||||||
|
|
||||||
|
|
||||||
proc getOccupancy(self: ChessBoard): Bitboard =
|
func getOccupancy(self: Chessboard): Bitboard {.inline.} =
|
||||||
## Get the occupancy bitboard for every piece on
|
## Get the occupancy bitboard for every piece on
|
||||||
## the chessboard
|
## the chessboard
|
||||||
result = self.getOccupancyFor(Black) or self.getOccupancyFor(White)
|
result = self.getOccupancyFor(Black) or self.getOccupancyFor(White)
|
||||||
|
|
||||||
|
|
||||||
proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
func getPawnAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||||
## Returns the attack bitboard for the given square from
|
## Returns the attack bitboard for the given square from
|
||||||
## the pawns of the given side
|
## the pawns of the given side
|
||||||
let
|
let
|
||||||
|
@ -394,17 +242,17 @@ proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bit
|
||||||
return pawns and (bottomLeft or bottomRight)
|
return pawns and (bottomLeft or bottomRight)
|
||||||
|
|
||||||
|
|
||||||
proc getKingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
func getKingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||||
## Returns the attack bitboard for the given square from
|
## Returns the attack bitboard for the given square from
|
||||||
## the king of the given side
|
## the king of the given side
|
||||||
result = Bitboard(0)
|
result = Bitboard(0)
|
||||||
let
|
let
|
||||||
king = self.getBitboard(King, attacker)
|
king = self.getBitboard(King, attacker)
|
||||||
if (KING_BITBOARDS[square.uint] and king) != 0:
|
if (getKingBitboard(square) and king) != 0:
|
||||||
result = result or king
|
result = result or king
|
||||||
|
|
||||||
|
|
||||||
proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
func getKnightAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||||
## Returns the attack bitboard for the given square from
|
## Returns the attack bitboard for the given square from
|
||||||
## the knights of the given side
|
## the knights of the given side
|
||||||
let
|
let
|
||||||
|
@ -412,11 +260,11 @@ proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): B
|
||||||
result = Bitboard(0)
|
result = Bitboard(0)
|
||||||
for knight in knights:
|
for knight in knights:
|
||||||
let knightBB = knight.toBitboard()
|
let knightBB = knight.toBitboard()
|
||||||
if (KNIGHT_BITBOARDS[square.uint] and knightBB) != 0:
|
if (getKnightBitboard(knight) and knightBB) != 0:
|
||||||
result = result or knightBB
|
result = result or knightBB
|
||||||
|
|
||||||
|
|
||||||
proc getSlidingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
proc getSlidingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||||
## Returns the attack bitboard for the given square from
|
## Returns the attack bitboard for the given square from
|
||||||
## the sliding pieces of the given side
|
## the sliding pieces of the given side
|
||||||
let
|
let
|
||||||
|
@ -437,7 +285,7 @@ proc getSlidingAttacks(self: ChessBoard, square: Square, attacker: PieceColor):
|
||||||
result = result or bishopBB
|
result = result or bishopBB
|
||||||
|
|
||||||
|
|
||||||
proc getAttacksTo*(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
proc getAttacksTo*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||||
## Computes the attack bitboard for the given square from
|
## Computes the attack bitboard for the given square from
|
||||||
## the given side
|
## the given side
|
||||||
result = Bitboard(0)
|
result = Bitboard(0)
|
||||||
|
@ -447,28 +295,195 @@ proc getAttacksTo*(self: ChessBoard, square: Square, attacker: PieceColor): Bitb
|
||||||
result = result or self.getSlidingAttacks(square, attacker)
|
result = result or self.getSlidingAttacks(square, attacker)
|
||||||
|
|
||||||
|
|
||||||
proc updateChecksAndPins(self: ChessBoard) =
|
proc updateChecksAndPins(self: Chessboard) =
|
||||||
|
## Updates internal metadata about checks and
|
||||||
|
## pinned pieces
|
||||||
let
|
let
|
||||||
side = self.getSideToMove()
|
sideToMove = self.position.sideToMove
|
||||||
king = self.getBitboard(King, side).toSquare()
|
nonSideToMove = sideToMove.opposite()
|
||||||
self.position.checkers = self.getAttacksTo(king, side.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)
|
||||||
|
|
||||||
|
|
||||||
proc inCheck(self: ChessBoard): bool =
|
func inCheck(self: Chessboard): bool {.inline.} =
|
||||||
## Returns if the current side to move is in check
|
## Returns if the current side to move is in check
|
||||||
return self.position.checkers != 0
|
return self.position.checkers != 0
|
||||||
|
|
||||||
|
|
||||||
proc canCastle*(self: ChessBoard, side: PieceColor): tuple[king, queen: bool] =
|
proc canCastle*(self: Chessboard, side: PieceColor): tuple[king, queen: bool] =
|
||||||
## Returns if the current side to move can castle
|
## Returns if the current side to move can castle
|
||||||
return (false, false) # TODO
|
return (false, false) # TODO
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
|
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 generatePawnMovements(self: Chessboard, moves: var MoveList) =
|
||||||
## Helper of generatePawnMoves for generating all non-capture
|
## Helper of generatePawnMoves for generating all non-capture
|
||||||
## and non-promotion pawn moves
|
## and non-promotion pawn moves
|
||||||
let
|
let
|
||||||
sideToMove = self.getSideToMove()
|
sideToMove = self.position.sideToMove
|
||||||
pawns = self.getBitboard(Pawn, sideToMove)
|
pawns = self.getBitboard(Pawn, sideToMove)
|
||||||
# We can only move to squares that are *not* occupied by another piece.
|
# We can only move to squares that are *not* occupied by another piece.
|
||||||
# We also cannot move to the last rank, as that will result in a promotion
|
# We also cannot move to the last rank, as that will result in a promotion
|
||||||
|
@ -483,11 +498,11 @@ proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
|
||||||
moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush))
|
moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush))
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
|
proc generatePawnCaptures(self: Chessboard, moves: var MoveList) =
|
||||||
## Helper of generatePawnMoves for generating all capture
|
## Helper of generatePawnMoves for generating all capture
|
||||||
## pawn moves
|
## pawn moves
|
||||||
let
|
let
|
||||||
sideToMove = self.getSideToMove()
|
sideToMove = self.position.sideToMove
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
pawns = self.getBitboard(Pawn, sideToMove)
|
pawns = self.getBitboard(Pawn, sideToMove)
|
||||||
# We can only capture enemy pieces (except the king)
|
# We can only capture enemy pieces (except the king)
|
||||||
|
@ -495,7 +510,7 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
|
||||||
enemyPawns = self.getBitboard(Pawn, nonSideToMove)
|
enemyPawns = self.getBitboard(Pawn, nonSideToMove)
|
||||||
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
||||||
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
||||||
epTarget = self.getEnPassantTarget()
|
epTarget = self.position.enPassantSquare
|
||||||
var epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
|
var epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
|
||||||
epBitboard = epBitboard and enemyPawns
|
epBitboard = epBitboard and enemyPawns
|
||||||
# Top right attacks
|
# Top right attacks
|
||||||
|
@ -514,11 +529,11 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
|
||||||
moves.add(createMove(epBitboard.forwardRightRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
moves.add(createMove(epBitboard.forwardRightRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) =
|
proc generatePawnPromotions(self: Chessboard, moves: var MoveList) =
|
||||||
## Helper of generatePawnMoves for generating all pawn promotion
|
## Helper of generatePawnMoves for generating all pawn promotion
|
||||||
## moves
|
## moves
|
||||||
let
|
let
|
||||||
sideToMove = self.getSideToMove()
|
sideToMove = self.position.sideToMove
|
||||||
pawns = self.getBitboard(Pawn, sideToMove)
|
pawns = self.getBitboard(Pawn, sideToMove)
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
for square in pawns.forwardRelativeTo(sideToMove) and not occupancy and sideToMove.getLastRank():
|
for square in pawns.forwardRelativeTo(sideToMove) and not occupancy and sideToMove.getLastRank():
|
||||||
|
@ -526,17 +541,17 @@ proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) =
|
||||||
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion))
|
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion))
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnMoves(self: ChessBoard, moves: var MoveList) =
|
proc generatePawnMoves(self: Chessboard, moves: var MoveList) =
|
||||||
## Generates all the legal pawn moves for the side to move
|
## Generates all the legal pawn moves for the side to move
|
||||||
self.generatePawnMovements(moves)
|
self.generatePawnMovements(moves)
|
||||||
self.generatePawnCaptures(moves)
|
self.generatePawnCaptures(moves)
|
||||||
self.generatePawnPromotions(moves)
|
self.generatePawnPromotions(moves)
|
||||||
|
|
||||||
|
|
||||||
proc generateRookMoves(self: ChessBoard, moves: var MoveList) =
|
proc generateRookMoves(self: Chessboard, moves: var MoveList) =
|
||||||
## Helper of generateSlidingMoves to generate rook moves
|
## Helper of generateSlidingMoves to generate rook moves
|
||||||
let
|
let
|
||||||
sideToMove = self.getSideToMove()
|
sideToMove = self.position.sideToMove
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
||||||
|
@ -552,10 +567,10 @@ proc generateRookMoves(self: ChessBoard, moves: var MoveList) =
|
||||||
moves.add(createMove(square, target, Capture))
|
moves.add(createMove(square, target, Capture))
|
||||||
|
|
||||||
|
|
||||||
proc generateBishopMoves(self: ChessBoard, moves: var MoveList) =
|
proc generateBishopMoves(self: Chessboard, moves: var MoveList) =
|
||||||
## Helper of generateSlidingMoves to generate bishop moves
|
## Helper of generateSlidingMoves to generate bishop moves
|
||||||
let
|
let
|
||||||
sideToMove = self.getSideToMove()
|
sideToMove = self.position.sideToMove
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
|
@ -570,18 +585,17 @@ proc generateBishopMoves(self: ChessBoard, moves: var MoveList) =
|
||||||
moves.add(createMove(square, target, Capture))
|
moves.add(createMove(square, target, Capture))
|
||||||
|
|
||||||
|
|
||||||
|
proc generateSlidingMoves(self: Chessboard, moves: var MoveList) =
|
||||||
proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) =
|
|
||||||
## Generates all legal sliding moves for the side to move
|
## Generates all legal sliding moves for the side to move
|
||||||
self.generateRookMoves(moves)
|
self.generateRookMoves(moves)
|
||||||
self.generateBishopMoves(moves)
|
self.generateBishopMoves(moves)
|
||||||
# Queens are just handled rooks + bishops
|
# Queens are just handled rooks + bishops
|
||||||
|
|
||||||
|
|
||||||
proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
|
proc generateKingMoves(self: Chessboard, moves: var MoveList) =
|
||||||
## Generates all legal king moves for the side to move
|
## Generates all legal king moves for the side to move
|
||||||
let
|
let
|
||||||
sideToMove = self.getSideToMove()
|
sideToMove = self.position.sideToMove
|
||||||
king = self.getBitboard(King, sideToMove)
|
king = self.getBitboard(King, sideToMove)
|
||||||
moveIdx = king.toSquare().uint64
|
moveIdx = king.toSquare().uint64
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
|
@ -595,10 +609,10 @@ proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
|
||||||
moves.add(createMove(king, square, Capture))
|
moves.add(createMove(king, square, Capture))
|
||||||
|
|
||||||
|
|
||||||
proc generateKnightMoves(self: ChessBoard, moves: var MoveList)=
|
proc generateKnightMoves(self: Chessboard, moves: var MoveList)=
|
||||||
## Generates all the legal knight moves for the side to move
|
## Generates all the legal knight moves for the side to move
|
||||||
let
|
let
|
||||||
sideToMove = self.getSideToMove()
|
sideToMove = self.position.sideToMove
|
||||||
knights = self.getBitboard(Knight, sideToMove)
|
knights = self.getBitboard(Knight, sideToMove)
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
|
@ -612,7 +626,7 @@ proc generateKnightMoves(self: ChessBoard, moves: var MoveList)=
|
||||||
moves.add(createMove(square, target, Capture))
|
moves.add(createMove(square, target, Capture))
|
||||||
|
|
||||||
|
|
||||||
proc generateMoves*(self: ChessBoard, moves: var MoveList) =
|
proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
||||||
## Generates the list of all possible legal moves
|
## Generates the list of all possible legal moves
|
||||||
## in the current position
|
## in the current position
|
||||||
if self.position.halfMoveClock >= 100:
|
if self.position.halfMoveClock >= 100:
|
||||||
|
@ -626,174 +640,14 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) =
|
||||||
self.generateSlidingMoves(moves)
|
self.generateSlidingMoves(moves)
|
||||||
|
|
||||||
|
|
||||||
proc removePieceFromBitboard(self: ChessBoard, square: Square) =
|
proc isLegal(self: Chessboard, move: Move): bool {.inline.} =
|
||||||
## Removes a piece at the given square in the chessboard from
|
|
||||||
## its respective bitboard
|
|
||||||
let piece = self.grid[square]
|
|
||||||
var b = self.position.pieces[piece.color][piece.kind]
|
|
||||||
b.clearBit(square)
|
|
||||||
self.position.pieces[piece.color][piece.kind] = b
|
|
||||||
|
|
||||||
|
|
||||||
proc addPieceToBitboard(self: ChessBoard, square: Square, piece: Piece) =
|
|
||||||
## Adds the given piece at the given square in the chessboard to
|
|
||||||
## its respective bitboard
|
|
||||||
var b = self.position.pieces[piece.color][piece.kind]
|
|
||||||
b.setBit(square)
|
|
||||||
self.position.pieces[piece.color][piece.kind] = b
|
|
||||||
|
|
||||||
|
|
||||||
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. If attack
|
|
||||||
## is set to false, then this function does
|
|
||||||
## not update attacked squares metadata, just
|
|
||||||
## positional info and the grid itself
|
|
||||||
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
|
|
||||||
|
|
||||||
# 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():
|
|
||||||
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.getSideToMove().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)
|
|
||||||
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
|
|
||||||
self.updateChecksAndPins()
|
|
||||||
|
|
||||||
|
|
||||||
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 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 isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
|
||||||
## Returns whether the given move is legal
|
## Returns whether the given move is legal
|
||||||
var moves = MoveList()
|
var moves = MoveList()
|
||||||
self.generateMoves(moves)
|
self.generateMoves(moves)
|
||||||
return move in moves
|
return move in moves
|
||||||
|
|
||||||
|
|
||||||
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} =
|
||||||
## Makes a move on the board
|
## Makes a move on the board
|
||||||
result = move
|
result = move
|
||||||
if not self.isLegal(move):
|
if not self.isLegal(move):
|
||||||
|
@ -845,7 +699,7 @@ proc fromChar*(c: char): Piece =
|
||||||
result = Piece(kind: kind, color: color)
|
result = Piece(kind: kind, color: color)
|
||||||
|
|
||||||
|
|
||||||
proc `$`*(self: ChessBoard): string =
|
proc `$`*(self: Chessboard): string =
|
||||||
result &= "- - - - - - - -"
|
result &= "- - - - - - - -"
|
||||||
var file = 8
|
var file = 8
|
||||||
for i in 0..7:
|
for i in 0..7:
|
||||||
|
@ -900,7 +754,7 @@ proc toPretty*(piece: Piece): string =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
proc pretty*(self: ChessBoard): string =
|
proc pretty*(self: Chessboard): string =
|
||||||
## Returns a colored version of the
|
## Returns a colored version of the
|
||||||
## board for easier visualization
|
## board for easier visualization
|
||||||
var file = 8
|
var file = 8
|
||||||
|
@ -926,7 +780,7 @@ proc pretty*(self: ChessBoard): string =
|
||||||
result &= "\x1b[0m"
|
result &= "\x1b[0m"
|
||||||
|
|
||||||
|
|
||||||
proc toFEN*(self: ChessBoard): string =
|
proc toFEN*(self: Chessboard): string =
|
||||||
## Returns a FEN string of the current
|
## Returns a FEN string of the current
|
||||||
## position in the chessboard
|
## position in the chessboard
|
||||||
var skip: int
|
var skip: int
|
||||||
|
@ -948,7 +802,7 @@ proc toFEN*(self: ChessBoard): string =
|
||||||
result &= "/"
|
result &= "/"
|
||||||
result &= " "
|
result &= " "
|
||||||
# Active color
|
# Active color
|
||||||
result &= (if self.getSideToMove() == White: "w" else: "b")
|
result &= (if self.position.sideToMove == White: "w" else: "b")
|
||||||
result &= " "
|
result &= " "
|
||||||
# Castling availability
|
# Castling availability
|
||||||
result &= "-"
|
result &= "-"
|
||||||
|
@ -967,16 +821,16 @@ proc toFEN*(self: ChessBoard): string =
|
||||||
# result &= "q"
|
# result &= "q"
|
||||||
result &= " "
|
result &= " "
|
||||||
# En passant target
|
# En passant target
|
||||||
if self.getEnPassantTarget() == nullSquare():
|
if self.position.enPassantSquare == nullSquare():
|
||||||
result &= "-"
|
result &= "-"
|
||||||
else:
|
else:
|
||||||
result &= self.getEnPassantTarget().toAlgebraic()
|
result &= self.position.enPassantSquare.toAlgebraic()
|
||||||
result &= " "
|
result &= " "
|
||||||
# Halfmove clock
|
# Halfmove clock
|
||||||
result &= $self.getHalfMoveCount()
|
result &= $self.position.halfMoveClock
|
||||||
result &= " "
|
result &= " "
|
||||||
# Fullmove number
|
# Fullmove number
|
||||||
result &= $self.getMoveCount()
|
result &= $self.position.fullMoveCount
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
|
|
|
@ -54,12 +54,19 @@ func clearBit*(a: var Bitboard, bit: Square) = a.uint64.clearBit(bit.int)
|
||||||
func setBit*(a: var Bitboard, bit: Square) = a.uint64.setBit(bit.int)
|
func setBit*(a: var Bitboard, bit: Square) = a.uint64.setBit(bit.int)
|
||||||
|
|
||||||
|
|
||||||
|
func countSquares*(self: Bitboard): int {.inline.} =
|
||||||
|
## Returns the number of active squares
|
||||||
|
## in the bitboard
|
||||||
|
result = self.countSetBits()
|
||||||
|
|
||||||
|
|
||||||
func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64
|
func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64
|
||||||
func getRankMask*(rank: int): Bitboard = Bitboard(0xff) shl uint64(8 * rank)
|
func getRankMask*(rank: int): Bitboard = Bitboard(0xff) shl uint64(8 * rank)
|
||||||
func toBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64
|
func toBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64
|
||||||
func toBitboard*(square: Square): Bitboard = toBitboard(square.int8)
|
func toBitboard*(square: Square): Bitboard = toBitboard(square.int8)
|
||||||
|
|
||||||
proc toSquare*(b: Bitboard): Square = Square(b.uint64.countTrailingZeroBits())
|
proc toSquare*(b: Bitboard): Square = Square(b.uint64.countTrailingZeroBits())
|
||||||
|
|
||||||
func createMove*(startSquare: Bitboard, targetSquare: Square, flags: varargs[MoveFlag]): Move =
|
func createMove*(startSquare: Bitboard, targetSquare: Square, flags: varargs[MoveFlag]): Move =
|
||||||
result = createMove(startSquare.toSquare(), targetSquare, flags)
|
result = createMove(startSquare.toSquare(), targetSquare, flags)
|
||||||
|
|
||||||
|
@ -236,7 +243,7 @@ func shortKnightDownRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard
|
||||||
# We precompute as much stuff as possible: lookup tables are fast!
|
# We precompute as much stuff as possible: lookup tables are fast!
|
||||||
|
|
||||||
|
|
||||||
func computeKingBitboards: array[64, Bitboard] =
|
func computeKingBitboards: array[64, Bitboard] {.compileTime.} =
|
||||||
## Precomputes all the movement bitboards for the king
|
## Precomputes all the movement bitboards for the king
|
||||||
for i in 0'u64..63:
|
for i in 0'u64..63:
|
||||||
let king = i.toBitboard()
|
let king = i.toBitboard()
|
||||||
|
@ -258,7 +265,7 @@ func computeKingBitboards: array[64, Bitboard] =
|
||||||
result[i] = movements
|
result[i] = movements
|
||||||
|
|
||||||
|
|
||||||
func computeKnightBitboards: array[64, Bitboard] =
|
func computeKnightBitboards: array[64, Bitboard] {.compileTime.} =
|
||||||
## Precomputes all the movement bitboards for knights
|
## Precomputes all the movement bitboards for knights
|
||||||
for i in 0'u64..63:
|
for i in 0'u64..63:
|
||||||
let knight = i.toBitboard()
|
let knight = i.toBitboard()
|
||||||
|
@ -276,8 +283,10 @@ func computeKnightBitboards: array[64, Bitboard] =
|
||||||
result[i] = movements
|
result[i] = movements
|
||||||
|
|
||||||
|
|
||||||
# For some reason nim freaks out if I try to call computeKingBitboards()
|
const
|
||||||
# at compile-time. ¯\_(ツ)_/¯
|
|
||||||
let
|
|
||||||
KING_BITBOARDS* = computeKingBitboards()
|
KING_BITBOARDS* = computeKingBitboards()
|
||||||
KNIGHT_BITBOARDS* = computeKnightBitboards()
|
KNIGHT_BITBOARDS* = computeKnightBitboards()
|
||||||
|
|
||||||
|
|
||||||
|
func getKingBitboard*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square.int]
|
||||||
|
func getKnightBitboard*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square.int]
|
|
@ -1,12 +1,13 @@
|
||||||
import ../nimfish
|
import ../nimfish
|
||||||
|
|
||||||
|
|
||||||
import std/strformat
|
import std/strformat
|
||||||
|
|
||||||
|
|
||||||
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
|
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
|
||||||
doAssert piece.kind == kind and piece.color == color, &"expected piece of kind {kind} and color {color}, got {piece.kind} / {piece.color} instead"
|
doAssert piece.kind == kind and piece.color == color, &"expected piece of kind {kind} and color {color}, got {piece.kind} / {piece.color} instead"
|
||||||
|
|
||||||
proc testPieceCount(board: ChessBoard, kind: PieceKind, color: PieceColor, count: int) =
|
proc testPieceCount(board: Chessboard, kind: PieceKind, color: PieceColor, count: int) =
|
||||||
let pieces = board.countPieces(kind, color)
|
let pieces = board.countPieces(kind, color)
|
||||||
doAssert pieces == count, &"expected {count} pieces of kind {kind} and color {color}, got {pieces} instead"
|
doAssert pieces == count, &"expected {count} pieces of kind {kind} and color {color}, got {pieces} instead"
|
||||||
|
|
||||||
|
|
|
@ -87,3 +87,65 @@ func createMove*(startSquare: Square, targetSquare: SomeInteger, flags: varargs[
|
||||||
|
|
||||||
|
|
||||||
func nullMove*: Move {.inline.} = createMove(nullSquare(), nullSquare())
|
func nullMove*: Move {.inline.} = createMove(nullSquare(), nullSquare())
|
||||||
|
|
||||||
|
func isPromotion*(move: Move): bool {.inline.} =
|
||||||
|
## Returns whether the given move is a
|
||||||
|
## pawn promotion
|
||||||
|
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
||||||
|
if (move.flags and promotion.uint16) != 0:
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func getPromotionType*(move: Move): MoveFlag {.inline.} =
|
||||||
|
## Returns the promotion type of the given move.
|
||||||
|
## The return value of this function is only valid
|
||||||
|
## if isPromotion() returns true
|
||||||
|
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
||||||
|
if (move.flags and promotion.uint16) != 0:
|
||||||
|
return promotion
|
||||||
|
|
||||||
|
|
||||||
|
func isCapture*(move: Move): bool {.inline.} =
|
||||||
|
## Returns whether the given move is a
|
||||||
|
## cature
|
||||||
|
result = (move.flags and Capture.uint16) == Capture.uint16
|
||||||
|
|
||||||
|
|
||||||
|
func isCastling*(move: Move): bool {.inline.} =
|
||||||
|
## Returns whether the given move is a
|
||||||
|
## castle
|
||||||
|
for flag in [CastleLong, CastleShort]:
|
||||||
|
if (move.flags and flag.uint16) != 0:
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func getCastlingType*(move: Move): MoveFlag {.inline.} =
|
||||||
|
## Returns the castlingRights type of the given move.
|
||||||
|
## The return value of this function is only valid
|
||||||
|
## if isCastling() returns true
|
||||||
|
for flag in [CastleLong, CastleShort]:
|
||||||
|
if (move.flags and flag.uint16) != 0:
|
||||||
|
return flag
|
||||||
|
|
||||||
|
|
||||||
|
func isEnPassant*(move: Move): bool {.inline.} =
|
||||||
|
## Returns whether the given move is an
|
||||||
|
## en passant capture
|
||||||
|
result = (move.flags and EnPassant.uint16) != 0
|
||||||
|
|
||||||
|
|
||||||
|
func isDoublePush*(move: Move): bool {.inline.} =
|
||||||
|
## Returns whether the given move is a
|
||||||
|
## double pawn push
|
||||||
|
result = (move.flags and DoublePush.uint16) != 0
|
||||||
|
|
||||||
|
|
||||||
|
func getFlags*(move: Move): seq[MoveFlag] =
|
||||||
|
## Gets all the flags of this move
|
||||||
|
for flag in [EnPassant, Capture, DoublePush, CastleLong, CastleShort,
|
||||||
|
PromoteToBishop, PromoteToKnight, PromoteToQueen,
|
||||||
|
PromoteToRook]:
|
||||||
|
if (move.flags and flag.uint16) == flag.uint16:
|
||||||
|
result.add(flag)
|
||||||
|
if result.len() == 0:
|
||||||
|
result.add(Default)
|
|
@ -13,14 +13,14 @@ type
|
||||||
CountData = tuple[nodes: uint64, captures: uint64, castles: uint64, checks: uint64, promotions: uint64, enPassant: uint64, checkmates: uint64]
|
CountData = tuple[nodes: uint64, captures: uint64, castles: uint64, checks: uint64, promotions: uint64, enPassant: uint64, checkmates: uint64]
|
||||||
|
|
||||||
|
|
||||||
proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = false, bulk: bool = false): CountData =
|
proc perft*(board: Chessboard, ply: int, verbose: bool = false, divide: bool = false, bulk: bool = false): CountData =
|
||||||
## Counts (and debugs) the number of legal positions reached after
|
## Counts (and debugs) the number of legal positions reached after
|
||||||
## the given number of ply
|
## the given number of ply
|
||||||
|
|
||||||
var moves = MoveList()
|
var moves = MoveList()
|
||||||
self.generateMoves(moves)
|
board.generateMoves(moves)
|
||||||
if not bulk:
|
if not bulk:
|
||||||
if len(moves) == 0 and self.inCheck():
|
if len(moves) == 0 and board.inCheck():
|
||||||
result.checkmates = 1
|
result.checkmates = 1
|
||||||
# TODO: Should we count stalemates/draws?
|
# TODO: Should we count stalemates/draws?
|
||||||
if ply == 0:
|
if ply == 0:
|
||||||
|
@ -48,22 +48,22 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
|
|
||||||
for move in moves:
|
for move in moves:
|
||||||
if verbose:
|
if verbose:
|
||||||
let canCastle = self.canCastle(self.getSideToMove())
|
let canCastle = board.canCastle(board.position.sideToMove)
|
||||||
echo &"Ply (from root): {self.getPlyFromRoot()}"
|
echo &"Ply (from root): {board.position.plyFromRoot}"
|
||||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
||||||
echo &"Turn: {self.getSideToMove()}"
|
echo &"Turn: {board.position.sideToMove}"
|
||||||
echo &"Piece: {self.getPiece(move.startSquare).kind}"
|
echo &"Piece: {board.getPiece(move.startSquare).kind}"
|
||||||
echo &"Flags: {move.getFlags()}"
|
echo &"Flags: {move.getFlags()}"
|
||||||
echo &"In check: {(if self.inCheck(): \"yes\" else: \"no\")}"
|
echo &"In check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||||
echo &"Position before move: {self.toFEN()}"
|
echo &"Position before move: {board.toFEN()}"
|
||||||
stdout.write("En Passant target: ")
|
stdout.write("En Passant target: ")
|
||||||
if self.getEnPassantTarget() != nullSquare():
|
if board.position.enPassantSquare != nullSquare():
|
||||||
echo self.getEnPassantTarget().toAlgebraic()
|
echo board.position.enPassantSquare.toAlgebraic()
|
||||||
else:
|
else:
|
||||||
echo "None"
|
echo "None"
|
||||||
echo "\n", self.pretty()
|
echo "\n", board.pretty()
|
||||||
self.doMove(move)
|
board.doMove(move)
|
||||||
if ply == 1:
|
if ply == 1:
|
||||||
if move.isCapture():
|
if move.isCapture():
|
||||||
inc(result.captures)
|
inc(result.captures)
|
||||||
|
@ -73,16 +73,16 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
inc(result.promotions)
|
inc(result.promotions)
|
||||||
if move.isEnPassant():
|
if move.isEnPassant():
|
||||||
inc(result.enPassant)
|
inc(result.enPassant)
|
||||||
if self.inCheck():
|
if board.inCheck():
|
||||||
# Opponent king is in check
|
# Opponent king is in check
|
||||||
inc(result.checks)
|
inc(result.checks)
|
||||||
if verbose:
|
if verbose:
|
||||||
let canCastle = self.canCastle(self.getSideToMove())
|
let canCastle = board.canCastle(board.position.sideToMove)
|
||||||
echo "\n"
|
echo "\n"
|
||||||
echo &"Opponent in check: {(if self.inCheck(): \"yes\" else: \"no\")}"
|
echo &"Opponent in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||||
echo &"Opponent can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
echo &"Opponent can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||||
echo &"Position after move: {self.toFEN()}"
|
echo &"Position after move: {board.toFEN()}"
|
||||||
echo "\n", self.pretty()
|
echo "\n", board.pretty()
|
||||||
stdout.write("nextpos>> ")
|
stdout.write("nextpos>> ")
|
||||||
try:
|
try:
|
||||||
discard readLine(stdin)
|
discard readLine(stdin)
|
||||||
|
@ -90,8 +90,8 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
discard
|
discard
|
||||||
except EOFError:
|
except EOFError:
|
||||||
discard
|
discard
|
||||||
let next = self.perft(ply - 1, verbose, bulk=bulk)
|
let next = board.perft(ply - 1, verbose, bulk=bulk)
|
||||||
self.unmakeMove()
|
board.unmakeMove()
|
||||||
if divide and (not bulk or ply > 1):
|
if divide and (not bulk or ply > 1):
|
||||||
var postfix = ""
|
var postfix = ""
|
||||||
if move.isPromotion():
|
if move.isPromotion():
|
||||||
|
@ -118,7 +118,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
result.checkmates += next.checkmates
|
result.checkmates += next.checkmates
|
||||||
|
|
||||||
|
|
||||||
proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
proc handleGoCommand(board: Chessboard, command: seq[string]) =
|
||||||
if len(command) < 2:
|
if len(command) < 2:
|
||||||
echo &"Error: go: invalid number of arguments"
|
echo &"Error: go: invalid number of arguments"
|
||||||
return
|
return
|
||||||
|
@ -172,7 +172,7 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
||||||
echo &"Error: go: unknown subcommand '{command[1]}'"
|
echo &"Error: go: unknown subcommand '{command[1]}'"
|
||||||
|
|
||||||
|
|
||||||
proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discardable.} =
|
proc handleMoveCommand(board: Chessboard, command: seq[string]): Move {.discardable.} =
|
||||||
if len(command) != 2:
|
if len(command) != 2:
|
||||||
echo &"Error: move: invalid number of arguments"
|
echo &"Error: move: invalid number of arguments"
|
||||||
return
|
return
|
||||||
|
@ -224,25 +224,25 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discarda
|
||||||
|
|
||||||
var move = createMove(startSquare, targetSquare, flags)
|
var move = createMove(startSquare, targetSquare, flags)
|
||||||
let piece = board.getPiece(move.startSquare)
|
let piece = board.getPiece(move.startSquare)
|
||||||
if piece.kind == King and move.startSquare == board.getSideToMove().getKingStartingSquare():
|
if piece.kind == King and move.startSquare == board.position.sideToMove.getKingStartingSquare():
|
||||||
if move.targetSquare == longCastleKing(piece.color):
|
if move.targetSquare == longCastleKing(piece.color):
|
||||||
move.flags = move.flags or CastleLong.uint16
|
move.flags = move.flags or CastleLong.uint16
|
||||||
elif move.targetSquare == shortCastleKing(piece.color):
|
elif move.targetSquare == shortCastleKing(piece.color):
|
||||||
move.flags = move.flags or CastleShort.uint16
|
move.flags = move.flags or CastleShort.uint16
|
||||||
if move.targetSquare == board.getEnPassantTarget():
|
if move.targetSquare == board.position.enPassantSquare:
|
||||||
move.flags = move.flags or EnPassant.uint16
|
move.flags = move.flags or EnPassant.uint16
|
||||||
result = board.makeMove(move)
|
result = board.makeMove(move)
|
||||||
if result == nullMove():
|
if result == nullMove():
|
||||||
echo &"Error: move: {moveString} is illegal"
|
echo &"Error: move: {moveString} is illegal"
|
||||||
|
|
||||||
|
|
||||||
proc handlePositionCommand(board: var ChessBoard, command: seq[string]) =
|
proc handlePositionCommand(board: var Chessboard, command: seq[string]) =
|
||||||
if len(command) < 2:
|
if len(command) < 2:
|
||||||
echo "Error: position: invalid number of arguments"
|
echo "Error: position: invalid number of arguments"
|
||||||
return
|
return
|
||||||
# Makes sure we don't leave the board in an invalid state if
|
# Makes sure we don't leave the board in an invalid state if
|
||||||
# some error occurs
|
# some error occurs
|
||||||
var tempBoard: ChessBoard
|
var tempBoard: Chessboard
|
||||||
case command[1]:
|
case command[1]:
|
||||||
of "startpos":
|
of "startpos":
|
||||||
tempBoard = newDefaultChessboard()
|
tempBoard = newDefaultChessboard()
|
||||||
|
@ -302,7 +302,7 @@ proc handlePositionCommand(board: var ChessBoard, command: seq[string]) =
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
proc handleUCICommand(board: var ChessBoard, command: seq[string]) =
|
proc handleUCICommand(board: var Chessboard, command: seq[string]) =
|
||||||
echo "id name Nimfish 0.1"
|
echo "id name Nimfish 0.1"
|
||||||
echo "id author Nocturn9x & Contributors (see LICENSE)"
|
echo "id author Nocturn9x & Contributors (see LICENSE)"
|
||||||
# TODO
|
# TODO
|
||||||
|
@ -379,7 +379,7 @@ proc commandLoop*: int =
|
||||||
of "help":
|
of "help":
|
||||||
echo HELP_TEXT
|
echo HELP_TEXT
|
||||||
of "skip":
|
of "skip":
|
||||||
board.setSideToMove(board.getSideToMove().opposite())
|
board.position.sideToMove = board.position.sideToMove.opposite()
|
||||||
of "go":
|
of "go":
|
||||||
handleGoCommand(board, cmd)
|
handleGoCommand(board, cmd)
|
||||||
of "position", "pos":
|
of "position", "pos":
|
||||||
|
@ -391,18 +391,18 @@ proc commandLoop*: int =
|
||||||
of "unmove", "u":
|
of "unmove", "u":
|
||||||
board.unmakeMove()
|
board.unmakeMove()
|
||||||
of "stm":
|
of "stm":
|
||||||
echo &"Side to move: {board.getSideToMove()}"
|
echo &"Side to move: {board.position.sideToMove}"
|
||||||
of "atk":
|
of "atk":
|
||||||
if len(cmd) != 2:
|
if len(cmd) != 2:
|
||||||
echo "error: atk: invalid number of arguments"
|
echo "error: atk: invalid number of arguments"
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
echo board.getAttacksTo(cmd[1].toSquare(), board.getSideToMove())
|
echo board.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo "error: atk: invalid square"
|
echo "error: atk: invalid square"
|
||||||
continue
|
continue
|
||||||
of "ep":
|
of "ep":
|
||||||
let target = board.getEnPassantTarget()
|
let target = board.position.enPassantSquare
|
||||||
if target != nullSquare():
|
if target != nullSquare():
|
||||||
echo &"En passant target: {target.toAlgebraic()}"
|
echo &"En passant target: {target.toAlgebraic()}"
|
||||||
else:
|
else:
|
||||||
|
@ -417,10 +417,10 @@ proc commandLoop*: int =
|
||||||
echo "error: get: invalid square"
|
echo "error: get: invalid square"
|
||||||
continue
|
continue
|
||||||
of "castle":
|
of "castle":
|
||||||
let canCastle = board.canCastle(board.getSideToMove())
|
let canCastle = board.canCastle(board.position.sideToMove)
|
||||||
echo &"Castling rights for {($board.getSideToMove()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
echo &"Castling rights for {($board.position.sideToMove).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||||
of "check":
|
of "check":
|
||||||
echo &"{board.getSideToMove()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
echo &"{board.position.sideToMove} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||||
of "quit":
|
of "quit":
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue