2023-03-18 18:14:30 +01:00
|
|
|
# Copyright 2023 Mattia Giambirtone & All Contributors
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
import std/strutils
|
2023-10-12 10:14:37 +02:00
|
|
|
import std/strformat
|
2023-03-18 18:14:30 +01:00
|
|
|
|
2024-04-19 13:40:31 +02:00
|
|
|
|
2024-04-19 15:50:51 +02:00
|
|
|
import nimfishpkg/bitboards
|
|
|
|
import nimfishpkg/magics
|
|
|
|
import nimfishpkg/pieces
|
|
|
|
import nimfishpkg/moves
|
|
|
|
|
|
|
|
export bitboards, magics, pieces, moves
|
2023-10-30 14:46:27 +01:00
|
|
|
|
2023-10-25 22:41:04 +02:00
|
|
|
|
2024-04-16 15:24:31 +02:00
|
|
|
type
|
2023-10-25 22:41:04 +02:00
|
|
|
|
2024-04-15 12:04:50 +02:00
|
|
|
Position* = object
|
2023-10-17 15:08:46 +02:00
|
|
|
## A chess position
|
2024-04-19 15:50:51 +02:00
|
|
|
|
|
|
|
# Castling metadata. Updated on every move
|
2024-04-19 17:05:18 +02:00
|
|
|
castlingRights: array[64, uint8]
|
2023-10-17 15:08:46 +02:00
|
|
|
# Number of half-moves that were performed
|
|
|
|
# to reach this position starting from the
|
|
|
|
# root of the tree
|
2024-04-13 21:23:12 +02:00
|
|
|
plyFromRoot: int8
|
2023-03-18 18:14:30 +01:00
|
|
|
# Number of half moves since
|
2023-10-12 10:14:37 +02:00
|
|
|
# last piece capture or pawn movement.
|
|
|
|
# Used for the 50-move rule
|
2023-10-18 10:45:54 +02:00
|
|
|
halfMoveClock: int8
|
2023-03-18 18:14:30 +01:00
|
|
|
# Full move counter. Increments
|
2024-04-19 15:50:51 +02:00
|
|
|
# every 2 ply (half-moves)
|
2024-04-13 21:23:12 +02:00
|
|
|
fullMoveCount: int8
|
2023-03-18 18:14:30 +01:00
|
|
|
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
2024-04-15 12:04:50 +02:00
|
|
|
enPassantSquare*: Square
|
2024-04-16 08:50:42 +02:00
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
# The side to move
|
|
|
|
sideToMove: PieceColor
|
2024-04-16 15:24:31 +02:00
|
|
|
# Positional bitboards for all pieces
|
2024-04-20 13:28:14 +02:00
|
|
|
pieces: array[2, array[6, Bitboard]]
|
|
|
|
# Pinned pieces for the current side to move
|
|
|
|
pins: Bitboard
|
2024-04-19 23:28:46 +02:00
|
|
|
# Pieces checking the current side to move
|
|
|
|
checkers: Bitboard
|
2024-04-15 12:04:50 +02:00
|
|
|
|
|
|
|
|
2023-10-17 15:08:46 +02:00
|
|
|
ChessBoard* = ref object
|
|
|
|
## A chess board object
|
2023-11-01 19:07:09 +01:00
|
|
|
|
2024-04-08 20:28:31 +02:00
|
|
|
# The actual board where pieces live
|
2024-04-10 13:45:29 +02:00
|
|
|
grid: array[64, Piece]
|
2023-11-01 19:07:09 +01:00
|
|
|
# The current position
|
2023-10-17 15:08:46 +02:00
|
|
|
position: Position
|
2024-04-08 20:28:31 +02:00
|
|
|
# List of all previously reached positions
|
2023-10-17 15:08:46 +02:00
|
|
|
positions: seq[Position]
|
2023-10-12 11:55:12 +02:00
|
|
|
|
2023-03-18 18:14:30 +01:00
|
|
|
|
2024-04-10 13:45:29 +02:00
|
|
|
# A bunch of simple utility functions and forward declarations
|
2023-10-21 18:19:41 +02:00
|
|
|
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.}
|
2023-10-23 18:02:31 +02:00
|
|
|
proc isLegal(self: ChessBoard, move: Move): bool {.inline.}
|
2024-04-19 21:00:40 +02:00
|
|
|
proc doMove*(self: ChessBoard, move: Move)
|
2023-10-20 02:23:07 +02:00
|
|
|
proc pretty*(self: ChessBoard): string
|
2024-04-15 12:04:50 +02:00
|
|
|
proc spawnPiece(self: ChessBoard, square: Square, piece: Piece)
|
2024-04-08 20:28:31 +02:00
|
|
|
proc toFEN*(self: ChessBoard): string
|
2024-04-16 15:24:31 +02:00
|
|
|
proc unmakeMove*(self: ChessBoard)
|
2024-04-19 17:05:18 +02:00
|
|
|
proc movePiece(self: ChessBoard, move: Move)
|
|
|
|
proc removePiece(self: ChessBoard, square: Square)
|
2023-10-25 22:41:04 +02:00
|
|
|
|
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
proc update*(self: ChessBoard)
|
2023-10-16 22:14:58 +02:00
|
|
|
|
2024-04-15 12:04:50 +02:00
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
func setSideToMove*(self: ChessBoard, side: PieceColor) {.inline.} =
|
|
|
|
self.position.sideToMove = side
|
|
|
|
|
2023-11-01 19:07:09 +01:00
|
|
|
# A bunch of getters
|
2024-04-17 11:54:45 +02:00
|
|
|
func getSideToMove*(self: ChessBoard): PieceColor {.inline.} =
|
|
|
|
## Returns the currently side to move
|
2024-04-19 21:00:40 +02:00
|
|
|
return self.position.sideToMove
|
2023-10-17 15:08:46 +02:00
|
|
|
|
|
|
|
|
2024-04-15 12:04:50 +02:00
|
|
|
func getEnPassantTarget*(self: ChessBoard): Square {.inline.} =
|
2023-10-17 16:38:43 +02:00
|
|
|
## Returns the current en passant target square
|
2023-10-31 23:06:27 +01:00
|
|
|
return self.position.enPassantSquare
|
2023-10-17 16:38:43 +02:00
|
|
|
|
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
func getPlyFromRoot*(self: ChessBoard): int8 {.inline.} =
|
|
|
|
## Returns the current distance from the root in plys
|
|
|
|
return self.position.plyFromRoot
|
|
|
|
|
|
|
|
|
2023-10-28 02:32:50 +02:00
|
|
|
func getMoveCount*(self: ChessBoard): int {.inline.} =
|
2023-10-17 17:27:33 +02:00
|
|
|
## Returns the number of full moves that
|
|
|
|
## have been played
|
|
|
|
return self.position.fullMoveCount
|
|
|
|
|
|
|
|
|
2023-10-18 10:45:54 +02:00
|
|
|
func getHalfMoveCount*(self: ChessBoard): int {.inline.} =
|
2023-10-17 17:27:33 +02:00
|
|
|
## Returns the current number of half-moves
|
|
|
|
## since the last irreversible move
|
|
|
|
return self.position.halfMoveClock
|
|
|
|
|
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
func getKingStartingSquare*(color: PieceColor): Square {.inline.} =
|
2024-04-15 12:04:50 +02:00
|
|
|
## Retrieves the starting square of the king
|
2024-04-08 20:28:31 +02:00
|
|
|
## for the given color
|
|
|
|
case color:
|
|
|
|
of White:
|
2024-04-19 15:50:51 +02:00
|
|
|
return "e1".toSquare()
|
2024-04-08 20:28:31 +02:00
|
|
|
of Black:
|
2024-04-19 15:50:51 +02:00
|
|
|
return "e8".toSquare()
|
2024-04-08 20:28:31 +02:00
|
|
|
else:
|
|
|
|
discard
|
|
|
|
|
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
# FIXME: Check this shit.
|
|
|
|
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())
|
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
|
2024-04-19 23:28:46 +02:00
|
|
|
proc inCheck*(self: ChessBoard): bool
|
2024-04-20 13:28:14 +02:00
|
|
|
proc fromChar*(c: char): Piece
|
2024-04-19 14:38:35 +02:00
|
|
|
|
2023-10-16 15:25:48 +02:00
|
|
|
|
2023-10-12 11:55:12 +02:00
|
|
|
proc newChessboard: ChessBoard =
|
|
|
|
## Returns a new, empty chessboard
|
2023-03-18 18:14:30 +01:00
|
|
|
new(result)
|
2024-04-10 13:45:29 +02:00
|
|
|
for i in 0..63:
|
2024-04-16 15:24:31 +02:00
|
|
|
result.grid[i] = nullPiece()
|
2024-04-20 13:28:14 +02:00
|
|
|
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White, pieces: [
|
|
|
|
[Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)],
|
|
|
|
[Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)]])
|
|
|
|
|
2023-10-12 11:55:12 +02:00
|
|
|
|
2024-04-15 12:04:50 +02:00
|
|
|
# Indexing operations
|
2024-04-16 23:45:32 +02:00
|
|
|
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
|
2024-04-15 12:04:50 +02:00
|
|
|
|
|
|
|
|
2024-04-20 13:28:14 +02:00
|
|
|
func `[]`(self: array[2, array[6, Bitboard]], color: PieceColor): ptr array[6, Bitboard] {.inline.} = addr self[color.int]
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-04-19 23:28:46 +02:00
|
|
|
func getBitboard*(self: ChessBoard, kind: PieceKind, color: PieceColor): Bitboard =
|
2024-04-15 12:04:50 +02:00
|
|
|
## Returns the positional bitboard for the given piece kind and color
|
2024-04-20 13:28:14 +02:00
|
|
|
return self.position.pieces[color.int][kind.int]
|
|
|
|
|
2024-04-15 12:04:50 +02:00
|
|
|
|
|
|
|
|
2024-04-19 23:28:46 +02:00
|
|
|
func getBitboard*(self: ChessBoard, piece: Piece): Bitboard =
|
2024-04-15 12:04:50 +02:00
|
|
|
## Returns the positional bitboard for the given piece type
|
|
|
|
return self.getBitboard(piece.kind, piece.color)
|
2023-11-13 09:52:37 +01:00
|
|
|
|
2023-10-12 11:55:12 +02:00
|
|
|
|
2023-11-01 19:07:09 +01:00
|
|
|
proc newChessboardFromFEN*(fen: string): ChessBoard =
|
2023-10-12 11:55:12 +02:00
|
|
|
## Initializes a chessboard with the
|
2023-11-01 19:07:09 +01:00
|
|
|
## position encoded by the given FEN string
|
2023-10-12 11:55:12 +02:00
|
|
|
result = newChessboard()
|
2023-03-18 18:14:30 +01:00
|
|
|
var
|
2024-04-15 12:04:50 +02:00
|
|
|
# Current square in the grid
|
2023-10-18 10:45:54 +02:00
|
|
|
row: int8 = 0
|
|
|
|
column: int8 = 0
|
2023-03-18 18:14:30 +01:00
|
|
|
# Current section in the FEN string
|
|
|
|
section = 0
|
|
|
|
# Current index into the FEN string
|
|
|
|
index = 0
|
2023-11-01 19:07:09 +01:00
|
|
|
# Temporary variable to store a piece
|
2023-10-12 11:55:12 +02:00
|
|
|
piece: Piece
|
2023-03-18 18:14:30 +01:00
|
|
|
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
|
2023-11-01 19:07:09 +01:00
|
|
|
while index <= fen.high():
|
|
|
|
var c = fen[index]
|
2023-03-18 18:14:30 +01:00
|
|
|
if c == ' ':
|
|
|
|
# Next section
|
|
|
|
inc(section)
|
|
|
|
inc(index)
|
|
|
|
continue
|
|
|
|
case section:
|
|
|
|
of 0:
|
|
|
|
# Piece placement data
|
|
|
|
case c.toLowerAscii():
|
2023-10-12 11:55:12 +02:00
|
|
|
# Piece
|
2023-03-18 18:14:30 +01:00
|
|
|
of 'r', 'n', 'b', 'q', 'k', 'p':
|
2024-04-20 13:28:14 +02:00
|
|
|
let square: Square = makeSquare(row, column)
|
|
|
|
piece = c.fromChar()
|
|
|
|
var b = result.position.pieces[piece.color][piece.kind]
|
|
|
|
b.setBit(square)
|
|
|
|
result.position.pieces[piece.color][piece.kind] = b
|
2024-04-15 12:04:50 +02:00
|
|
|
result.grid[square] = piece
|
2023-03-18 18:14:30 +01:00
|
|
|
inc(column)
|
|
|
|
of '/':
|
2023-10-12 11:55:12 +02:00
|
|
|
# Next row
|
2023-03-18 18:14:30 +01:00
|
|
|
inc(row)
|
|
|
|
column = 0
|
|
|
|
of '0'..'9':
|
2023-10-12 11:55:12 +02:00
|
|
|
# Skip x columns
|
2023-10-20 02:23:07 +02:00
|
|
|
let x = int(uint8(c) - uint8('0'))
|
|
|
|
if x > 8:
|
2023-11-01 19:07:09 +01:00
|
|
|
raise newException(ValueError, &"invalid FEN: invalid column skip size ({x} > 8)")
|
2023-10-18 10:45:54 +02:00
|
|
|
column += int8(x)
|
2023-03-18 18:14:30 +01:00
|
|
|
else:
|
2023-11-01 19:07:09 +01:00
|
|
|
raise newException(ValueError, &"invalid FEN: unknown piece identifier '{c}'")
|
2023-03-18 18:14:30 +01:00
|
|
|
of 1:
|
|
|
|
# Active color
|
|
|
|
case c:
|
|
|
|
of 'w':
|
2024-04-19 21:00:40 +02:00
|
|
|
result.position.sideToMove = White
|
2023-03-18 18:14:30 +01:00
|
|
|
of 'b':
|
2024-04-19 21:00:40 +02:00
|
|
|
result.position.sideToMove = Black
|
2023-03-18 18:14:30 +01:00
|
|
|
else:
|
2023-11-01 19:07:09 +01:00
|
|
|
raise newException(ValueError, &"invalid FEN: invalid active color identifier '{c}'")
|
2023-03-18 18:14:30 +01:00
|
|
|
of 2:
|
|
|
|
# Castling availability
|
|
|
|
case c:
|
2024-04-19 15:50:51 +02:00
|
|
|
# TODO
|
2023-03-18 18:14:30 +01:00
|
|
|
of '-':
|
|
|
|
discard
|
|
|
|
of 'K':
|
2024-04-19 15:50:51 +02:00
|
|
|
discard
|
2024-04-19 17:05:18 +02:00
|
|
|
# result.position.castlingRightsAvailable.white.king = true
|
2023-03-18 18:14:30 +01:00
|
|
|
of 'Q':
|
2024-04-19 15:50:51 +02:00
|
|
|
discard
|
2024-04-19 17:05:18 +02:00
|
|
|
# result.position.castlingRightsAvailable.white.queen = true
|
2023-03-18 18:14:30 +01:00
|
|
|
of 'k':
|
2024-04-19 15:50:51 +02:00
|
|
|
discard
|
2024-04-19 17:05:18 +02:00
|
|
|
# result.position.castlingRightsAvailable.black.king = true
|
2023-03-18 18:14:30 +01:00
|
|
|
of 'q':
|
2024-04-19 15:50:51 +02:00
|
|
|
discard
|
2024-04-19 17:05:18 +02:00
|
|
|
# result.position.castlingRightsAvailable.black.queen = true
|
2023-03-18 18:14:30 +01:00
|
|
|
else:
|
2024-04-19 17:05:18 +02:00
|
|
|
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
2023-03-18 18:14:30 +01:00
|
|
|
of 3:
|
|
|
|
# En passant target square
|
|
|
|
case c:
|
|
|
|
of '-':
|
2023-10-12 11:55:12 +02:00
|
|
|
# Field is already uninitialized to the correct state
|
|
|
|
discard
|
2023-03-18 18:14:30 +01:00
|
|
|
else:
|
2024-04-16 23:45:32 +02:00
|
|
|
result.position.enPassantSquare = fen[index..index+1].toSquare()
|
2023-03-18 18:14:30 +01:00
|
|
|
# Square metadata is 2 bytes long
|
|
|
|
inc(index)
|
|
|
|
of 4:
|
|
|
|
# Halfmove clock
|
|
|
|
var s = ""
|
2023-11-01 19:07:09 +01:00
|
|
|
while not fen[index].isSpaceAscii():
|
|
|
|
s.add(fen[index])
|
2023-03-18 18:14:30 +01:00
|
|
|
inc(index)
|
|
|
|
# Backtrack so the space is seen by the
|
|
|
|
# next iteration of the loop
|
|
|
|
dec(index)
|
2023-10-18 10:45:54 +02:00
|
|
|
result.position.halfMoveClock = parseInt(s).int8
|
2023-03-18 18:14:30 +01:00
|
|
|
of 5:
|
|
|
|
# Fullmove number
|
|
|
|
var s = ""
|
2023-11-01 19:07:09 +01:00
|
|
|
while index <= fen.high():
|
|
|
|
s.add(fen[index])
|
2023-03-18 18:14:30 +01:00
|
|
|
inc(index)
|
2023-10-18 10:45:54 +02:00
|
|
|
result.position.fullMoveCount = parseInt(s).int8
|
2023-03-18 18:14:30 +01:00
|
|
|
else:
|
2023-11-01 19:07:09 +01:00
|
|
|
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
2023-03-18 18:14:30 +01:00
|
|
|
inc(index)
|
|
|
|
|
2023-10-12 10:14:37 +02:00
|
|
|
|
2023-10-28 02:32:50 +02:00
|
|
|
proc newDefaultChessboard*: ChessBoard {.inline.} =
|
2023-10-12 10:14:37 +02:00
|
|
|
## Initializes a chessboard with the
|
|
|
|
## starting position
|
2023-10-13 11:54:57 +02:00
|
|
|
return newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
|
|
|
|
|
|
|
|
2023-10-15 16:53:44 +02:00
|
|
|
proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int =
|
2023-10-28 02:32:50 +02:00
|
|
|
## Returns the number of pieces with
|
2024-04-08 20:28:31 +02:00
|
|
|
## the given color and type in the
|
|
|
|
## current position
|
2024-04-20 13:28:14 +02:00
|
|
|
return self.position.pieces[color][kind].countSetBits()
|
|
|
|
|
2023-10-15 16:53:44 +02:00
|
|
|
|
|
|
|
|
2023-10-28 02:32:50 +02:00
|
|
|
func countPieces*(self: ChessBoard, piece: Piece): int {.inline.} =
|
2023-10-15 16:53:44 +02:00
|
|
|
## Returns the number of pieces on the board that
|
2023-10-28 02:32:50 +02:00
|
|
|
## are of the same type and color as the given piece
|
2023-10-15 16:53:44 +02:00
|
|
|
return self.countPieces(piece.kind, piece.color)
|
|
|
|
|
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
proc getPiece*(self: ChessBoard, square: Square): Piece {.inline.} =
|
2024-04-15 12:04:50 +02:00
|
|
|
## Gets the piece at the given square
|
2024-04-16 23:45:32 +02:00
|
|
|
return self.grid[square]
|
2023-10-21 18:19:41 +02:00
|
|
|
|
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
proc getPiece*(self: ChessBoard, square: string): Piece {.inline.} =
|
2023-10-15 16:53:44 +02:00
|
|
|
## Gets the piece on the given square
|
|
|
|
## in algebraic notation
|
2024-04-16 23:45:32 +02:00
|
|
|
return self.getPiece(square.toSquare())
|
2023-10-15 16:53:44 +02:00
|
|
|
|
2023-10-16 22:14:58 +02:00
|
|
|
|
2023-10-23 18:02:31 +02:00
|
|
|
func isPromotion*(move: Move): bool {.inline.} =
|
2023-11-13 09:52:37 +01:00
|
|
|
## Returns whether the given move is a
|
|
|
|
## pawn promotion
|
|
|
|
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
2023-11-13 11:03:54 +01:00
|
|
|
if (move.flags and promotion.uint16) != 0:
|
2023-11-13 09:52:37 +01:00
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
func getPromotionType*(move: Move): MoveFlag {.inline.} =
|
|
|
|
## Returns the promotion type of the given move.
|
2023-11-13 11:03:54 +01:00
|
|
|
## The return value of this function is only valid
|
2023-11-13 09:52:37 +01:00
|
|
|
## if isPromotion() returns true
|
|
|
|
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
2023-11-13 11:03:54 +01:00
|
|
|
if (move.flags and promotion.uint16) != 0:
|
2023-11-13 09:52:37 +01:00
|
|
|
return promotion
|
|
|
|
|
|
|
|
|
|
|
|
func isCapture*(move: Move): bool {.inline.} =
|
|
|
|
## Returns whether the given move is a
|
|
|
|
## cature
|
2024-04-08 20:28:31 +02:00
|
|
|
result = (move.flags and Capture.uint16) == Capture.uint16
|
2023-11-13 09:52:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
func isCastling*(move: Move): bool {.inline.} =
|
|
|
|
## Returns whether the given move is a
|
|
|
|
## castle
|
|
|
|
for flag in [CastleLong, CastleShort]:
|
2023-11-13 11:03:54 +01:00
|
|
|
if (move.flags and flag.uint16) != 0:
|
2023-11-13 09:52:37 +01:00
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
func getCastlingType*(move: Move): MoveFlag {.inline.} =
|
2024-04-19 17:05:18 +02:00
|
|
|
## Returns the castlingRights type of the given move.
|
2023-11-13 09:52:37 +01:00
|
|
|
## The return value of this function is only valid
|
|
|
|
## if isCastling() returns true
|
|
|
|
for flag in [CastleLong, CastleShort]:
|
2023-11-13 11:03:54 +01:00
|
|
|
if (move.flags and flag.uint16) != 0:
|
2023-11-13 09:52:37 +01:00
|
|
|
return flag
|
|
|
|
|
|
|
|
|
|
|
|
func isEnPassant*(move: Move): bool {.inline.} =
|
|
|
|
## Returns whether the given move is an
|
|
|
|
## en passant capture
|
2023-11-13 11:03:54 +01:00
|
|
|
result = (move.flags and EnPassant.uint16) != 0
|
2023-11-13 09:52:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
func isDoublePush*(move: Move): bool {.inline.} =
|
|
|
|
## Returns whether the given move is a
|
|
|
|
## double pawn push
|
2023-11-13 11:03:54 +01:00
|
|
|
result = (move.flags and DoublePush.uint16) != 0
|
2023-10-16 22:14:58 +02:00
|
|
|
|
|
|
|
|
2024-04-08 20:28:31 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
proc getOccupancyFor(self: ChessBoard, color: PieceColor): Bitboard =
|
|
|
|
## Get the occupancy bitboard for every piece of the given color
|
2024-04-20 13:28:14 +02:00
|
|
|
result = Bitboard(0)
|
|
|
|
for b in self.position.pieces[color][]:
|
|
|
|
result = result or b
|
2024-04-13 21:23:12 +02:00
|
|
|
|
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
proc getOccupancy(self: ChessBoard): Bitboard =
|
|
|
|
## Get the occupancy bitboard for every piece on
|
|
|
|
## the chessboard
|
|
|
|
result = self.getOccupancyFor(Black) or self.getOccupancyFor(White)
|
2024-04-09 19:55:08 +02:00
|
|
|
|
2023-10-28 02:32:50 +02:00
|
|
|
|
2024-04-17 16:50:55 +02:00
|
|
|
proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
|
|
|
## Returns the attack bitboard for the given square from
|
|
|
|
## the pawns of the given side
|
|
|
|
let
|
|
|
|
sq = square.toBitboard()
|
|
|
|
pawns = self.getBitboard(Pawn, attacker)
|
|
|
|
bottomLeft = sq.backwardLeftRelativeTo(attacker)
|
|
|
|
bottomRight = sq.backwardRightRelativeTo(attacker)
|
|
|
|
return pawns and (bottomLeft or bottomRight)
|
|
|
|
|
|
|
|
|
2024-04-17 20:27:39 +02:00
|
|
|
proc getKingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
|
|
|
## Returns the attack bitboard for the given square from
|
|
|
|
## the king of the given side
|
|
|
|
result = Bitboard(0)
|
|
|
|
let
|
|
|
|
king = self.getBitboard(King, attacker)
|
2024-04-19 14:38:35 +02:00
|
|
|
if (KING_BITBOARDS[square.uint] and king) != 0:
|
2024-04-19 23:28:46 +02:00
|
|
|
result = result or king
|
2024-04-17 20:27:39 +02:00
|
|
|
|
|
|
|
|
|
|
|
proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
|
|
|
## Returns the attack bitboard for the given square from
|
|
|
|
## the knights of the given side
|
|
|
|
let
|
|
|
|
knights = self.getBitboard(Knight, attacker)
|
|
|
|
result = Bitboard(0)
|
|
|
|
for knight in knights:
|
2024-04-19 23:28:46 +02:00
|
|
|
let knightBB = knight.toBitboard()
|
|
|
|
if (KNIGHT_BITBOARDS[square.uint] and knightBB) != 0:
|
|
|
|
result = result or knightBB
|
2024-04-17 20:27:39 +02:00
|
|
|
|
|
|
|
|
2024-04-19 14:38:35 +02:00
|
|
|
proc getSlidingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
|
|
|
## Returns the attack bitboard for the given square from
|
|
|
|
## the sliding pieces of the given side
|
|
|
|
let
|
|
|
|
queens = self.getBitboard(Queen, attacker)
|
2024-04-19 23:28:46 +02:00
|
|
|
rooks = self.getBitboard(Rook, attacker) or queens
|
|
|
|
bishops = self.getBitboard(Bishop, attacker) or queens
|
2024-04-19 14:38:35 +02:00
|
|
|
result = Bitboard(0)
|
|
|
|
for rook in rooks:
|
|
|
|
let blockers = Rook.getRelevantBlockers(square)
|
2024-04-19 23:28:46 +02:00
|
|
|
let rookBB = rook.toBitboard()
|
|
|
|
if (getRookMoves(square, blockers) and rookBB) != 0:
|
|
|
|
result = result or rookBB
|
2024-04-19 14:38:35 +02:00
|
|
|
for bishop in bishops:
|
2024-04-19 23:28:46 +02:00
|
|
|
let
|
|
|
|
blockers = Bishop.getRelevantBlockers(square)
|
|
|
|
bishopBB = bishop.toBitboard()
|
|
|
|
if (getBishopMoves(square, blockers) and bishopBB) != 0:
|
|
|
|
result = result or bishopBB
|
2024-04-19 14:38:35 +02:00
|
|
|
|
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
proc getAttacksTo*(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
2024-04-17 16:50:55 +02:00
|
|
|
## Computes the attack bitboard for the given square from
|
|
|
|
## the given side
|
|
|
|
result = Bitboard(0)
|
|
|
|
result = result or self.getPawnAttacks(square, attacker)
|
2024-04-17 20:27:39 +02:00
|
|
|
result = result or self.getKingAttacks(square, attacker)
|
|
|
|
result = result or self.getKnightAttacks(square, attacker)
|
2024-04-19 14:38:35 +02:00
|
|
|
result = result or self.getSlidingAttacks(square, attacker)
|
|
|
|
|
|
|
|
|
2024-04-20 13:33:42 +02:00
|
|
|
proc updateChecksAndPins(self: ChessBoard) =
|
2024-04-19 23:28:46 +02:00
|
|
|
let
|
|
|
|
side = self.getSideToMove()
|
|
|
|
king = self.getBitboard(King, side).toSquare()
|
|
|
|
self.position.checkers = self.getAttacksTo(king, side.opposite())
|
2024-04-19 21:00:40 +02:00
|
|
|
|
|
|
|
|
2024-04-19 23:28:46 +02:00
|
|
|
proc inCheck(self: ChessBoard): bool =
|
2024-04-19 14:38:35 +02:00
|
|
|
## Returns if the current side to move is in check
|
2024-04-19 23:28:46 +02:00
|
|
|
return self.position.checkers != 0
|
2024-04-19 14:38:35 +02:00
|
|
|
|
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
proc canCastle*(self: ChessBoard, side: PieceColor): tuple[king, queen: bool] =
|
2024-04-19 14:38:35 +02:00
|
|
|
## Returns if the current side to move can castle
|
|
|
|
return (false, false) # TODO
|
2024-04-17 16:50:55 +02:00
|
|
|
|
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
|
|
|
|
## Helper of generatePawnMoves for generating all non-capture
|
2024-04-17 11:54:45 +02:00
|
|
|
## and non-promotion pawn moves
|
2024-04-16 23:45:32 +02:00
|
|
|
let
|
2024-04-17 11:54:45 +02:00
|
|
|
sideToMove = self.getSideToMove()
|
2024-04-16 23:45:32 +02:00
|
|
|
pawns = self.getBitboard(Pawn, sideToMove)
|
2024-04-17 11:54:45 +02:00
|
|
|
# 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
|
|
|
|
# and is handled elsewhere
|
2024-04-17 16:50:55 +02:00
|
|
|
allowedSquares = not (self.getOccupancy() or sideToMove.getLastRank())
|
2024-04-17 11:54:45 +02:00
|
|
|
# Single push
|
2024-04-17 16:50:55 +02:00
|
|
|
for square in pawns.forwardRelativeTo(sideToMove) and allowedSquares:
|
2024-04-17 11:54:45 +02:00
|
|
|
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square))
|
|
|
|
# Double push
|
2024-04-19 17:05:18 +02:00
|
|
|
let rank = if sideToMove == White: getRankMask(6) else: getRankMask(1) # Only pawns on their starting rank can double push
|
2024-04-17 16:50:55 +02:00
|
|
|
for square in (pawns and rank).doubleForwardRelativeTo(sideToMove) and allowedSquares:
|
2024-04-17 11:54:45 +02:00
|
|
|
moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush))
|
2024-04-16 23:45:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
|
|
|
|
## Helper of generatePawnMoves for generating all capture
|
|
|
|
## pawn moves
|
|
|
|
let
|
2024-04-17 11:54:45 +02:00
|
|
|
sideToMove = self.getSideToMove()
|
2024-04-16 23:45:32 +02:00
|
|
|
nonSideToMove = sideToMove.opposite()
|
|
|
|
pawns = self.getBitboard(Pawn, sideToMove)
|
2024-04-17 16:50:55 +02:00
|
|
|
# We can only capture enemy pieces (except the king)
|
2024-04-20 13:33:42 +02:00
|
|
|
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
2024-04-19 21:00:40 +02:00
|
|
|
enemyPawns = self.getBitboard(Pawn, nonSideToMove)
|
2024-04-17 11:54:45 +02:00
|
|
|
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
|
|
|
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
2024-04-16 23:45:32 +02:00
|
|
|
epTarget = self.getEnPassantTarget()
|
2024-04-19 21:00:40 +02:00
|
|
|
var epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
|
|
|
|
epBitboard = epBitboard and enemyPawns
|
2024-04-16 23:45:32 +02:00
|
|
|
# Top right attacks
|
|
|
|
for square in rightMovement and enemyPieces:
|
2024-04-17 16:50:55 +02:00
|
|
|
moves.add(createMove(square.toBitboard().backwardLeftRelativeTo(sideToMove), square, Capture))
|
|
|
|
# Top left attacks
|
2024-04-16 23:45:32 +02:00
|
|
|
for square in leftMovement and enemyPieces:
|
2024-04-17 16:50:55 +02:00
|
|
|
moves.add(createMove(square.toBitboard().backwardRightRelativeTo(sideToMove), square, Capture))
|
2024-04-16 23:45:32 +02:00
|
|
|
# Special case for en passant
|
|
|
|
let
|
|
|
|
epLeft = epBitboard and leftMovement
|
|
|
|
epRight = epBitboard and rightMovement
|
|
|
|
if epLeft != 0:
|
2024-04-17 11:54:45 +02:00
|
|
|
moves.add(createMove(epBitboard.forwardLeftRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
2024-04-16 23:45:32 +02:00
|
|
|
elif epRight != 0:
|
2024-04-17 11:54:45 +02:00
|
|
|
moves.add(createMove(epBitboard.forwardRightRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
2024-04-09 19:55:08 +02:00
|
|
|
|
2023-10-28 02:32:50 +02:00
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) =
|
|
|
|
## Helper of generatePawnMoves for generating all pawn promotion
|
|
|
|
## moves
|
2024-04-17 11:54:45 +02:00
|
|
|
let
|
|
|
|
sideToMove = self.getSideToMove()
|
|
|
|
pawns = self.getBitboard(Pawn, sideToMove)
|
2024-04-17 16:50:55 +02:00
|
|
|
occupancy = self.getOccupancy()
|
|
|
|
for square in pawns.forwardRelativeTo(sideToMove) and not occupancy and sideToMove.getLastRank():
|
2024-04-17 11:54:45 +02:00
|
|
|
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
|
|
|
|
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion))
|
2024-04-16 23:45:32 +02:00
|
|
|
|
2023-10-30 14:46:27 +01:00
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
proc generatePawnMoves(self: ChessBoard, moves: var MoveList) =
|
2024-04-17 16:50:55 +02:00
|
|
|
## Generates all the legal pawn moves for the side to move
|
2024-04-16 23:45:32 +02:00
|
|
|
self.generatePawnMovements(moves)
|
|
|
|
self.generatePawnCaptures(moves)
|
2024-04-17 11:54:45 +02:00
|
|
|
self.generatePawnPromotions(moves)
|
2023-10-17 16:38:43 +02:00
|
|
|
|
2023-10-16 14:55:43 +02:00
|
|
|
|
2024-04-19 21:43:56 +02:00
|
|
|
proc generateRookMoves(self: ChessBoard, moves: var MoveList) =
|
|
|
|
## Helper of generateSlidingMoves to generate rook moves
|
2024-04-19 13:40:31 +02:00
|
|
|
let
|
|
|
|
sideToMove = self.getSideToMove()
|
|
|
|
occupancy = self.getOccupancy()
|
2024-04-20 13:33:42 +02:00
|
|
|
nonSideToMove = sideToMove.opposite()
|
|
|
|
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
2024-04-19 23:28:46 +02:00
|
|
|
rooks = self.getBitboard(Rook, sideToMove) or self.getBitboard(Queen, sideToMove)
|
2024-04-19 13:40:31 +02:00
|
|
|
for square in rooks:
|
2024-04-19 23:28:46 +02:00
|
|
|
let
|
|
|
|
blockers = occupancy and Rook.getRelevantBlockers(square)
|
|
|
|
moveset = getRookMoves(square, blockers)
|
2024-04-19 21:43:56 +02:00
|
|
|
for target in moveset and not occupancy:
|
|
|
|
moves.add(createMove(square, target))
|
|
|
|
# Captures
|
|
|
|
for target in moveset and enemyPieces:
|
2024-04-19 14:38:35 +02:00
|
|
|
moves.add(createMove(square, target, Capture))
|
|
|
|
|
2024-04-19 13:40:31 +02:00
|
|
|
|
2024-04-19 21:43:56 +02:00
|
|
|
proc generateBishopMoves(self: ChessBoard, moves: var MoveList) =
|
|
|
|
## Helper of generateSlidingMoves to generate bishop moves
|
2024-04-19 14:38:35 +02:00
|
|
|
let
|
|
|
|
sideToMove = self.getSideToMove()
|
2024-04-20 13:33:42 +02:00
|
|
|
nonSideToMove = sideToMove.opposite()
|
|
|
|
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
2024-04-19 14:38:35 +02:00
|
|
|
occupancy = self.getOccupancy()
|
2024-04-19 23:28:46 +02:00
|
|
|
bishops = self.getBitboard(Bishop, sideToMove) or self.getBitboard(Queen, sideToMove)
|
2024-04-19 14:38:35 +02:00
|
|
|
for square in bishops:
|
2024-04-19 23:28:46 +02:00
|
|
|
let
|
|
|
|
blockers = occupancy and Bishop.getRelevantBlockers(square)
|
|
|
|
moveset = getBishopMoves(square, blockers)
|
2024-04-19 21:43:56 +02:00
|
|
|
for target in moveset and not occupancy:
|
2024-04-19 14:38:35 +02:00
|
|
|
moves.add(createMove(square, target))
|
2024-04-19 21:43:56 +02:00
|
|
|
for target in moveset and enemyPieces:
|
2024-04-19 14:38:35 +02:00
|
|
|
moves.add(createMove(square, target, Capture))
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-04-17 16:50:55 +02:00
|
|
|
proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) =
|
|
|
|
## Generates all legal sliding moves for the side to move
|
2024-04-19 13:40:31 +02:00
|
|
|
self.generateRookMoves(moves)
|
2024-04-19 14:38:35 +02:00
|
|
|
self.generateBishopMoves(moves)
|
2024-04-19 23:28:46 +02:00
|
|
|
# Queens are just handled rooks + bishops
|
2024-04-19 13:40:31 +02:00
|
|
|
|
2023-10-16 14:55:43 +02:00
|
|
|
|
2024-04-17 16:50:55 +02:00
|
|
|
proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
|
|
|
|
## Generates all legal king moves for the side to move
|
|
|
|
let
|
|
|
|
sideToMove = self.getSideToMove()
|
|
|
|
king = self.getBitboard(King, sideToMove)
|
2024-04-17 20:27:39 +02:00
|
|
|
moveIdx = king.toSquare().uint64
|
2024-04-19 23:28:46 +02:00
|
|
|
occupancy = self.getOccupancy()
|
2024-04-17 16:50:55 +02:00
|
|
|
nonSideToMove = sideToMove.opposite()
|
2024-04-20 13:33:42 +02:00
|
|
|
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
2024-04-17 16:50:55 +02:00
|
|
|
# Regular moves
|
2024-04-19 23:28:46 +02:00
|
|
|
for square in KING_BITBOARDS[moveIdx] and not occupancy:
|
2024-04-17 16:50:55 +02:00
|
|
|
moves.add(createMove(king, square))
|
|
|
|
# Captures
|
2024-04-17 20:27:39 +02:00
|
|
|
for square in KING_BITBOARDS[moveIdx] and enemyPieces:
|
2024-04-17 16:50:55 +02:00
|
|
|
moves.add(createMove(king, square, Capture))
|
2023-10-17 12:42:15 +02:00
|
|
|
|
|
|
|
|
2024-04-17 16:50:55 +02:00
|
|
|
proc generateKnightMoves(self: ChessBoard, moves: var MoveList)=
|
|
|
|
## Generates all the legal knight moves for the side to move
|
2024-04-17 20:27:39 +02:00
|
|
|
let
|
|
|
|
sideToMove = self.getSideToMove()
|
|
|
|
knights = self.getBitboard(Knight, sideToMove)
|
2024-04-19 23:28:46 +02:00
|
|
|
occupancy = self.getOccupancy()
|
2024-04-17 20:27:39 +02:00
|
|
|
nonSideToMove = sideToMove.opposite()
|
2024-04-20 13:33:42 +02:00
|
|
|
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
2024-04-17 20:27:39 +02:00
|
|
|
for square in knights:
|
|
|
|
# Regular moves
|
2024-04-19 23:28:46 +02:00
|
|
|
for target in KNIGHT_BITBOARDS[square.uint64] and not occupancy:
|
2024-04-17 20:27:39 +02:00
|
|
|
moves.add(createMove(square, target))
|
|
|
|
# Captures
|
|
|
|
for target in KNIGHT_BITBOARDS[square.uint64] and enemyPieces:
|
|
|
|
moves.add(createMove(square, target, Capture))
|
2023-10-17 12:42:15 +02:00
|
|
|
|
2023-10-17 12:08:07 +02:00
|
|
|
|
2024-04-16 23:45:32 +02:00
|
|
|
proc generateMoves*(self: ChessBoard, moves: var MoveList) =
|
|
|
|
## Generates the list of all possible legal moves
|
|
|
|
## in the current position
|
2024-04-15 12:04:50 +02:00
|
|
|
if self.position.halfMoveClock >= 100:
|
2024-04-13 19:59:54 +02:00
|
|
|
# Draw by 50-move rule
|
2024-04-16 23:45:32 +02:00
|
|
|
return
|
2024-04-13 19:59:54 +02:00
|
|
|
# TODO: Check for draw by insufficient material
|
2024-04-16 23:45:32 +02:00
|
|
|
# TODO: Check for repetitions (requires zobrist hashing + table)
|
2024-04-17 16:50:55 +02:00
|
|
|
self.generateKingMoves(moves)
|
2024-04-19 21:00:40 +02:00
|
|
|
self.generatePawnMoves(moves)
|
2024-04-17 20:27:39 +02:00
|
|
|
self.generateKnightMoves(moves)
|
2024-04-19 14:38:35 +02:00
|
|
|
self.generateSlidingMoves(moves)
|
2023-10-25 22:41:04 +02:00
|
|
|
|
|
|
|
|
2024-04-15 12:04:50 +02:00
|
|
|
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]
|
2024-04-20 13:28:14 +02:00
|
|
|
var b = self.position.pieces[piece.color][piece.kind]
|
|
|
|
b.clearBit(square)
|
|
|
|
self.position.pieces[piece.color][piece.kind] = b
|
2024-04-15 12:04:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
proc addPieceToBitboard(self: ChessBoard, square: Square, piece: Piece) =
|
|
|
|
## Adds the given piece at the given square in the chessboard to
|
|
|
|
## its respective bitboard
|
2024-04-20 13:28:14 +02:00
|
|
|
var b = self.position.pieces[piece.color][piece.kind]
|
|
|
|
b.setBit(square)
|
|
|
|
self.position.pieces[piece.color][piece.kind] = b
|
2024-04-15 12:04:50 +02:00
|
|
|
|
|
|
|
|
2024-04-19 17:05:18 +02:00
|
|
|
proc removePiece(self: ChessBoard, square: Square) =
|
2024-04-15 12:04:50 +02:00
|
|
|
## Removes a piece from the board, updating necessary
|
|
|
|
## metadata
|
|
|
|
var piece = self.grid[square]
|
2024-04-19 21:00:40 +02:00
|
|
|
when not defined(danger):
|
|
|
|
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
2024-04-15 12:04:50 +02:00
|
|
|
self.removePieceFromBitboard(square)
|
2024-04-19 17:05:18 +02:00
|
|
|
self.grid[square] = nullPiece()
|
2024-04-15 12:04:50 +02:00
|
|
|
|
|
|
|
|
2024-04-19 17:05:18 +02:00
|
|
|
proc movePiece(self: ChessBoard, move: Move) =
|
2024-04-15 12:04:50 +02:00
|
|
|
## 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]
|
2024-04-19 21:00:40 +02:00
|
|
|
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}")
|
2024-04-15 12:04:50 +02:00
|
|
|
# Update positional metadata
|
2024-04-19 17:05:18 +02:00
|
|
|
self.removePiece(move.startSquare)
|
|
|
|
self.spawnPiece(move.targetSquare, piece)
|
2023-11-01 19:07:09 +01:00
|
|
|
|
2023-10-20 02:23:07 +02:00
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
proc doMove*(self: ChessBoard, move: Move) =
|
2023-10-15 16:53:44 +02:00
|
|
|
## Internal function called by makeMove after
|
2023-11-01 19:07:09 +01:00
|
|
|
## performing legality checks. Can be used in
|
|
|
|
## performance-critical paths where a move is
|
|
|
|
## already known to be legal
|
2023-10-31 23:06:27 +01:00
|
|
|
|
|
|
|
# Record final position for future reference
|
|
|
|
self.positions.add(self.position)
|
2023-10-17 15:08:46 +02:00
|
|
|
|
|
|
|
# Final checks
|
2024-04-15 12:04:50 +02:00
|
|
|
let piece = self.grid[move.startSquare]
|
2024-04-19 21:00:40 +02:00
|
|
|
when not defined(danger):
|
|
|
|
doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}"
|
2023-10-17 22:16:01 +02:00
|
|
|
|
2023-10-17 17:27:33 +02:00
|
|
|
var
|
|
|
|
halfMoveClock = self.position.halfMoveClock
|
|
|
|
fullMoveCount = self.position.fullMoveCount
|
2024-04-19 17:05:18 +02:00
|
|
|
castlingRights = self.position.castlingRights
|
2024-04-16 15:24:31 +02:00
|
|
|
enPassantTarget = nullSquare()
|
2023-11-01 19:07:09 +01:00
|
|
|
# Needed to detect draw by the 50 move rule
|
2024-04-13 14:56:08 +02:00
|
|
|
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
|
2023-10-31 23:06:27 +01:00
|
|
|
halfMoveClock = 0
|
2023-10-21 18:19:41 +02:00
|
|
|
else:
|
|
|
|
inc(halfMoveClock)
|
2023-10-28 02:32:50 +02:00
|
|
|
if piece.color == Black:
|
2023-10-21 18:19:41 +02:00
|
|
|
inc(fullMoveCount)
|
2023-11-01 19:07:09 +01:00
|
|
|
|
2023-11-13 09:52:37 +01:00
|
|
|
if move.isDoublePush():
|
2024-04-17 11:54:45 +02:00
|
|
|
enPassantTarget = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
|
2023-11-01 19:07:09 +01:00
|
|
|
|
2023-10-21 18:19:41 +02:00
|
|
|
# Create new position
|
|
|
|
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
2023-10-31 23:06:27 +01:00
|
|
|
halfMoveClock: halfMoveClock,
|
|
|
|
fullMoveCount: fullMoveCount,
|
2024-04-19 21:00:40 +02:00
|
|
|
sideToMove: self.getSideToMove().opposite,
|
2024-04-19 17:05:18 +02:00
|
|
|
castlingRights: castlingRights,
|
2024-04-15 12:04:50 +02:00
|
|
|
enPassantSquare: enPassantTarget,
|
2024-04-16 15:24:31 +02:00
|
|
|
pieces: self.position.pieces
|
2023-10-21 18:19:41 +02:00
|
|
|
)
|
2023-10-31 23:06:27 +01:00
|
|
|
# Update position metadata
|
|
|
|
|
2023-11-13 09:52:37 +01:00
|
|
|
if move.isEnPassant():
|
2023-10-31 23:06:27 +01:00
|
|
|
# Make the en passant pawn disappear
|
2024-04-19 17:05:18 +02:00
|
|
|
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare())
|
2024-04-10 13:45:29 +02:00
|
|
|
|
|
|
|
if move.isCapture():
|
|
|
|
# Get rid of captured pieces
|
2024-04-19 17:05:18 +02:00
|
|
|
self.removePiece(move.targetSquare)
|
2024-04-19 21:00:40 +02:00
|
|
|
|
2024-04-19 17:05:18 +02:00
|
|
|
# Move the piece to its target square
|
|
|
|
self.movePiece(move)
|
2023-10-31 23:06:27 +01:00
|
|
|
if move.isPromotion():
|
2023-10-30 14:46:27 +01:00
|
|
|
# Move is a pawn promotion: get rid of the pawn
|
|
|
|
# and spawn a new piece
|
2024-04-13 14:56:08 +02:00
|
|
|
self.removePiece(move.targetSquare)
|
2023-11-13 09:52:37 +01:00
|
|
|
case move.getPromotionType():
|
2023-10-30 14:46:27 +01:00
|
|
|
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:
|
2024-04-13 14:56:08 +02:00
|
|
|
# Unreachable
|
2023-10-30 14:46:27 +01:00
|
|
|
discard
|
2024-04-20 13:33:42 +02:00
|
|
|
self.updateChecksAndPins()
|
2023-10-17 15:08:46 +02:00
|
|
|
|
|
|
|
|
2024-04-15 12:04:50 +02:00
|
|
|
proc spawnPiece(self: ChessBoard, square: Square, piece: Piece) =
|
2023-10-17 15:08:46 +02:00
|
|
|
## Internal helper to "spawn" a given piece at the given
|
2024-04-19 21:00:40 +02:00
|
|
|
## square
|
|
|
|
when not defined(danger):
|
|
|
|
doAssert self.grid[square].kind == Empty
|
2024-04-15 12:04:50 +02:00
|
|
|
self.addPieceToBitboard(square, piece)
|
|
|
|
self.grid[square] = piece
|
2023-10-17 15:08:46 +02:00
|
|
|
|
|
|
|
|
2024-04-19 21:00:40 +02:00
|
|
|
proc update*(self: ChessBoard) =
|
2024-04-10 13:45:29 +02:00
|
|
|
## Updates the internal grid representation
|
2023-10-23 18:02:31 +02:00
|
|
|
## according to the positional data stored
|
2024-04-08 20:28:31 +02:00
|
|
|
## in the chessboard
|
2023-11-13 09:52:37 +01:00
|
|
|
for i in 0..63:
|
2024-04-16 15:24:31 +02:00
|
|
|
self.grid[i] = nullPiece()
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[White][Pawn]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: White, kind: Pawn)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[Black][Pawn]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: Black, kind: Pawn)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[White][Bishop]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: White, kind: Bishop)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[Black][Bishop]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: Black, kind: Bishop)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[White][Knight]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: White, kind: Knight)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[Black][Knight]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: Black, kind: Knight)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[White][Rook]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: White, kind: Rook)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[Black][Rook]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: Black, kind: Rook)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[White][Queen]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: White, kind: Queen)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[Black][Queen]:
|
2024-04-15 12:04:50 +02:00
|
|
|
self.grid[sq] = Piece(color: Black, kind: Queen)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[White][King]:
|
2024-04-19 17:05:18 +02:00
|
|
|
self.grid[sq] = Piece(color: White, kind: King)
|
2024-04-20 13:28:14 +02:00
|
|
|
for sq in self.position.pieces[Black][King]:
|
2024-04-19 17:05:18 +02:00
|
|
|
self.grid[sq] = Piece(color: Black, kind: King)
|
2024-04-16 15:24:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
proc unmakeMove*(self: ChessBoard) =
|
|
|
|
## Reverts to the previous board position,
|
|
|
|
## if one exists
|
2024-04-19 21:00:40 +02:00
|
|
|
self.position = self.positions.pop()
|
|
|
|
self.update()
|
2023-10-23 18:02:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
proc isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
2023-10-17 16:38:43 +02:00
|
|
|
## Returns whether the given move is legal
|
2024-04-16 23:45:32 +02:00
|
|
|
var moves = MoveList()
|
|
|
|
self.generateMoves(moves)
|
|
|
|
return move in moves
|
2023-10-17 10:31:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
2023-10-28 02:32:50 +02:00
|
|
|
## Makes a move on the board
|
2023-10-17 10:31:38 +02:00
|
|
|
result = move
|
2023-10-23 18:02:31 +02:00
|
|
|
if not self.isLegal(move):
|
2024-04-16 15:24:31 +02:00
|
|
|
return nullMove()
|
2023-10-23 18:02:31 +02:00
|
|
|
self.doMove(move)
|
2023-10-20 02:23:07 +02:00
|
|
|
|
|
|
|
|
2023-11-13 09:52:37 +01:00
|
|
|
proc toChar*(piece: Piece): char =
|
2024-04-20 13:28:14 +02:00
|
|
|
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
|
2023-11-13 09:52:37 +01:00
|
|
|
if piece.color == White:
|
2024-04-20 13:28:14 +02:00
|
|
|
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)
|
2023-11-13 09:52:37 +01:00
|
|
|
|
|
|
|
|
2023-10-15 16:53:44 +02:00
|
|
|
proc `$`*(self: ChessBoard): string =
|
|
|
|
result &= "- - - - - - - -"
|
2024-04-16 23:45:32 +02:00
|
|
|
var file = 8
|
2023-11-13 09:52:37 +01:00
|
|
|
for i in 0..7:
|
2023-10-15 16:53:44 +02:00
|
|
|
result &= "\n"
|
2023-11-13 09:52:37 +01:00
|
|
|
for j in 0..7:
|
2024-04-16 23:45:32 +02:00
|
|
|
let piece = self.grid[makeSquare(i, j)]
|
2023-10-15 16:53:44 +02:00
|
|
|
if piece.kind == Empty:
|
2023-10-15 22:46:22 +02:00
|
|
|
result &= "x "
|
2023-10-15 16:53:44 +02:00
|
|
|
continue
|
2023-11-13 09:52:37 +01:00
|
|
|
result &= &"{piece.toChar()} "
|
2024-04-16 23:45:32 +02:00
|
|
|
result &= &"{file}"
|
|
|
|
dec(file)
|
2023-10-15 16:53:44 +02:00
|
|
|
result &= "\n- - - - - - - -"
|
|
|
|
result &= "\na b c d e f g h"
|
|
|
|
|
|
|
|
|
2023-11-13 09:52:37 +01:00
|
|
|
proc toPretty*(piece: Piece): string =
|
|
|
|
case piece.color:
|
2024-04-09 17:46:30 +02:00
|
|
|
of White:
|
2023-11-13 09:52:37 +01:00
|
|
|
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
|
2024-04-09 17:46:30 +02:00
|
|
|
of Black:
|
2023-11-13 09:52:37 +01:00
|
|
|
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:
|
2024-04-09 17:46:30 +02:00
|
|
|
return "\240\159\168\133"
|
2023-11-13 09:52:37 +01:00
|
|
|
else:
|
|
|
|
discard
|
|
|
|
else:
|
|
|
|
discard
|
2023-10-28 02:32:50 +02:00
|
|
|
|
|
|
|
|
2023-10-15 22:46:22 +02:00
|
|
|
proc pretty*(self: ChessBoard): string =
|
2024-04-09 17:46:30 +02:00
|
|
|
## Returns a colored version of the
|
2023-10-15 22:46:22 +02:00
|
|
|
## board for easier visualization
|
2024-04-16 23:45:32 +02:00
|
|
|
var file = 8
|
2023-11-13 09:52:37 +01:00
|
|
|
for i in 0..7:
|
|
|
|
if i > 0:
|
|
|
|
result &= "\n"
|
|
|
|
for j in 0..7:
|
2024-04-09 17:46:30 +02:00
|
|
|
# Equivalent to (i + j) mod 2
|
|
|
|
# (I'm just evil)
|
|
|
|
if ((i + j) and 1) == 0:
|
2023-11-13 09:52:37 +01:00
|
|
|
result &= "\x1b[39;44;1m"
|
|
|
|
else:
|
|
|
|
result &= "\x1b[39;40;1m"
|
2024-04-16 23:45:32 +02:00
|
|
|
let piece = self.grid[makeSquare(i, j)]
|
2023-10-15 22:46:22 +02:00
|
|
|
if piece.kind == Empty:
|
2023-11-13 09:52:37 +01:00
|
|
|
result &= " \x1b[0m"
|
2023-10-15 22:46:22 +02:00
|
|
|
else:
|
2023-11-13 09:52:37 +01:00
|
|
|
result &= &"{piece.toPretty()} \x1b[0m"
|
2024-04-16 23:45:32 +02:00
|
|
|
result &= &" \x1b[33;1m{file}\x1b[0m"
|
|
|
|
dec(file)
|
2023-10-15 22:46:22 +02:00
|
|
|
|
|
|
|
result &= "\n\x1b[31;1ma b c d e f g h"
|
|
|
|
result &= "\x1b[0m"
|
|
|
|
|
2023-10-15 16:53:44 +02:00
|
|
|
|
2023-10-28 02:32:50 +02:00
|
|
|
proc toFEN*(self: ChessBoard): string =
|
|
|
|
## Returns a FEN string of the current
|
|
|
|
## position in the chessboard
|
|
|
|
var skip: int
|
|
|
|
# Piece placement data
|
2023-11-13 09:52:37 +01:00
|
|
|
for i in 0..7:
|
2023-10-28 02:32:50 +02:00
|
|
|
skip = 0
|
2023-11-13 09:52:37 +01:00
|
|
|
for j in 0..7:
|
2024-04-16 23:45:32 +02:00
|
|
|
let piece = self.grid[makeSquare(i, j)]
|
2023-10-28 02:32:50 +02:00
|
|
|
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
|
2024-04-17 11:54:45 +02:00
|
|
|
result &= (if self.getSideToMove() == White: "w" else: "b")
|
2023-10-28 02:32:50 +02:00
|
|
|
result &= " "
|
|
|
|
# Castling availability
|
2024-04-19 15:50:51 +02:00
|
|
|
result &= "-"
|
2024-04-19 17:05:18 +02:00
|
|
|
# let castleWhite = self.position.castlingRightsAvailable.white
|
|
|
|
# let castleBlack = self.position.castlingRightsAvailable.black
|
2024-04-19 15:50:51 +02:00
|
|
|
# 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"
|
2023-10-28 02:32:50 +02:00
|
|
|
result &= " "
|
|
|
|
# En passant target
|
2024-04-16 15:24:31 +02:00
|
|
|
if self.getEnPassantTarget() == nullSquare():
|
2023-10-28 02:32:50 +02:00
|
|
|
result &= "-"
|
|
|
|
else:
|
2024-04-16 23:45:32 +02:00
|
|
|
result &= self.getEnPassantTarget().toAlgebraic()
|
2023-10-28 02:32:50 +02:00
|
|
|
result &= " "
|
|
|
|
# Halfmove clock
|
|
|
|
result &= $self.getHalfMoveCount()
|
|
|
|
result &= " "
|
|
|
|
# Fullmove number
|
|
|
|
result &= $self.getMoveCount()
|
|
|
|
|
2024-04-19 14:38:35 +02:00
|
|
|
|
2023-10-13 11:54:57 +02:00
|
|
|
when isMainModule:
|
2024-04-19 23:28:46 +02:00
|
|
|
import nimfishpkg/tui
|
|
|
|
import nimfishpkg/misc
|
2024-04-13 21:23:12 +02:00
|
|
|
|
2024-04-19 23:28:46 +02:00
|
|
|
basicTests()
|
2024-04-19 21:00:40 +02:00
|
|
|
|
2024-04-19 14:38:35 +02:00
|
|
|
setControlCHook(proc () {.noconv.} = quit(0))
|
2024-04-19 21:00:40 +02:00
|
|
|
|
2024-04-19 23:28:46 +02:00
|
|
|
quit(commandLoop())
|