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
2024-04-08 20:28:31 +02:00
import std / times
import std / math
2024-04-15 12:04:50 +02:00
import std / bitops
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-16 15:24:31 +02:00
CountData = tuple [ nodes : uint64 , captures : uint64 , castles : uint64 , checks : uint64 , promotions : uint64 , enPassant : uint64 , checkmates : uint64 ]
2023-11-13 09:52:37 +01: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
2023-10-17 15:08:46 +02:00
# Active color
turn : PieceColor
2024-04-16 15:24:31 +02:00
# Positional bitboards for all pieces
pieces : tuple [ white , black : tuple [ king , queens , rooks , bishops , knights , pawns : Bitboard ] ]
2024-04-19 15:50:51 +02:00
# Pinned pieces for each side
pins : tuple [ white , black : Bitboard ]
# Checking pieces
checkers : tuple [ white , black : 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 ]
2024-04-16 15:24:31 +02:00
# Index of the current position
currPos : int
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 . }
2023-10-18 10:45:54 +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
proc extend [ T ] ( self : var seq [ T ] , other : openarray [ T ] ) {. inline . } =
for x in other :
self . add ( x )
2024-04-10 13:45:29 +02:00
proc updateBoard * ( self : ChessBoard )
2023-10-16 22:14:58 +02:00
2024-04-15 12:04:50 +02:00
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
2023-10-17 15:08:46 +02:00
return self . position . turn
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
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-15 12:04:50 +02:00
func getStartRank ( piece : Piece ) : int {. inline . } =
2023-10-16 14:55:43 +02:00
## Retrieves the starting row of
## the given piece inside our 8x8
## grid
case piece . color :
of None :
return - 1
of White :
case piece . kind :
of Pawn :
return 6
else :
2023-10-17 22:16:01 +02:00
return 7
2023-10-16 14:55:43 +02:00
of Black :
case piece . kind :
of Pawn :
return 1
else :
return 0
2023-10-12 10:14:37 +02:00
2024-04-15 12:45:47 +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 15:50:51 +02:00
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 ( ) )
2024-04-16 23:45:32 +02:00
func longCastleRook ( color : PieceColor ) : Square {. inline . } = ( if color = = White : makeSquare ( 7 , 3 ) else : makeSquare ( 7 , 5 ) )
func shortCastleRook ( color : PieceColor ) : Square {. inline . } = ( if color = = White : makeSquare ( 0 , 0 ) else : makeSquare ( 0 , 2 ) )
2024-04-19 14:38:35 +02:00
proc inCheck ( self : ChessBoard , side : PieceColor ) : bool
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-19 17:05:18 +02:00
result . currPos = - 1
2024-04-16 15:24:31 +02:00
result . position = Position ( enPassantSquare : nullSquare ( ) , turn : White )
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
func getDirectionMask ( self : ChessBoard , square : Square , direction : Direction ) : Bitboard =
## Like getDirectionMask(), but used within the board context
## with a piece square and direction only
return getDirectionMask ( square , self . grid [ square ] . color , direction )
func getBitboard ( self : ChessBoard , kind : PieceKind , color : PieceColor ) : Bitboard =
## Returns the positional bitboard for the given piece kind and color
case color :
of White :
case kind :
of Pawn :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . pawns
2024-04-15 12:04:50 +02:00
of Knight :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . knights
2024-04-15 12:04:50 +02:00
of Bishop :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . bishops
2024-04-15 12:04:50 +02:00
of Rook :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . rooks
2024-04-15 12:04:50 +02:00
of Queen :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . queens
2024-04-15 12:04:50 +02:00
of King :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . king
2024-04-15 12:04:50 +02:00
else :
discard
of Black :
case kind :
of Pawn :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . pawns
2024-04-15 12:04:50 +02:00
of Knight :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . knights
2024-04-15 12:04:50 +02:00
of Bishop :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . bishops
2024-04-15 12:04:50 +02:00
of Rook :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . rooks
2024-04-15 12:04:50 +02:00
of Queen :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . queens
2024-04-15 12:04:50 +02:00
of King :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . king
2024-04-15 12:04:50 +02:00
else :
discard
else :
discard
func getBitboard ( self : ChessBoard , piece : Piece ) : Bitboard =
## 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
2024-04-15 12:04:50 +02:00
pieces : int
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-15 12:04:50 +02:00
let
2024-04-16 23:45:32 +02:00
square : Square = makeSquare ( row , column )
bitIndex = square . int8
2023-10-12 11:55:12 +02:00
# We know for a fact these values are in our
# enumeration, so all is good
{. warning [ HoleEnumConv ] : off . }
piece = Piece ( kind : PieceKind ( c . toLowerAscii ( ) ) , color : if c . isUpperAscii ( ) : White else : Black )
case piece . color :
of Black :
case piece . kind :
of Pawn :
2024-04-16 15:24:31 +02:00
result . position . pieces . black . pawns . uint64 . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Bishop :
2024-04-16 15:24:31 +02:00
result . position . pieces . black . bishops . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Knight :
2024-04-16 15:24:31 +02:00
result . position . pieces . black . knights . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Rook :
2024-04-16 15:24:31 +02:00
result . position . pieces . black . rooks . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Queen :
2024-04-16 15:24:31 +02:00
result . position . pieces . black . queens . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of King :
2024-04-17 16:50:55 +02:00
if result . position . pieces . black . king ! = 0 :
2023-11-01 19:07:09 +01:00
raise newException ( ValueError , " invalid position: exactly one king of each color must be present " )
2024-04-16 15:24:31 +02:00
result . position . pieces . black . king . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
else :
discard
of White :
case piece . kind :
of Pawn :
2024-04-16 15:24:31 +02:00
result . position . pieces . white . pawns . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Bishop :
2024-04-16 15:24:31 +02:00
result . position . pieces . white . bishops . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Knight :
2024-04-16 15:24:31 +02:00
result . position . pieces . white . knights . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Rook :
2024-04-16 15:24:31 +02:00
result . position . pieces . white . rooks . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Queen :
2024-04-16 15:24:31 +02:00
result . position . pieces . white . queens . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of King :
2024-04-16 15:24:31 +02:00
if result . position . pieces . white . king ! = 0 :
2023-11-01 19:07:09 +01:00
raise newException ( ValueError , " invalid position: exactly one king of each color must be present " )
2024-04-16 15:24:31 +02:00
result . position . pieces . white . king . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
else :
discard
else :
discard
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 ' :
2023-10-17 15:08:46 +02:00
result . position . turn = White
2023-03-18 18:14:30 +01:00
of ' b ' :
2023-10-17 15:08:46 +02:00
result . position . turn = 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 )
2024-04-19 14:38:35 +02:00
if result . inCheck ( result . getSideToMove ( ) . opposite ) :
2023-11-01 19:07:09 +01:00
# Opponent king cannot be captured on the next move
2024-04-19 14:38:35 +02:00
raise newException ( ValueError , " invalid position: opponent king can be captured " )
2024-04-17 16:50:55 +02:00
if result . position . pieces . white . king = = 0 or result . position . pieces . black . king = = 0 :
2023-11-01 19:07:09 +01:00
# Both kings must be on the board
raise newException ( ValueError , " invalid position: exactly one king of each color must be present " )
2023-03-18 18:14:30 +01:00
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
2023-10-15 16:53:44 +02:00
case color :
of White :
case kind :
of Pawn :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . pawns . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . bishops . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . knights . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . rooks . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . queens . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of King :
2024-04-16 15:24:31 +02:00
return self . position . pieces . white . king . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
else :
2024-04-15 12:04:50 +02:00
raise newException ( ValueError , " invalid piece type " )
2023-10-15 16:53:44 +02:00
of Black :
case kind :
of Pawn :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . pawns . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . bishops . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . knights . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . rooks . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . queens . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of King :
2024-04-16 15:24:31 +02:00
return self . position . pieces . black . king . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
else :
2023-10-28 02:32:50 +02:00
raise newException ( ValueError , " invalid piece type " )
2023-10-15 16:53:44 +02:00
of None :
2023-10-28 02:32:50 +02:00
raise newException ( ValueError , " invalid piece color " )
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-17 16:50:55 +02:00
func getKingSquare ( self : ChessBoard , color : PieceColor = None ) : Square {. inline . } =
2024-04-15 12:04:50 +02:00
## Returns the square of the king for the given
2024-04-17 16:50:55 +02:00
## side (if it is None, the side to move is used)
2024-04-08 20:28:31 +02:00
var color = color
if color = = None :
2024-04-17 11:54:45 +02:00
color = self . getSideToMove ( )
2024-04-08 20:28:31 +02:00
case color :
of White :
2024-04-17 16:50:55 +02:00
return self . position . pieces . white . king . toSquare ( )
2024-04-08 20:28:31 +02:00
of Black :
2024-04-17 16:50:55 +02:00
return self . position . pieces . black . king . toSquare ( )
2024-04-08 20:28:31 +02:00
else :
discard
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-13 21:23:12 +02:00
case color :
of White :
2024-04-16 23:45:32 +02:00
let b = self . position . pieces . white
return b . pawns or b . knights or b . bishops or b . rooks or b . queens or b . king
2024-04-13 21:23:12 +02:00
of Black :
2024-04-16 23:45:32 +02:00
let b = self . position . pieces . black
return b . pawns or b . knights or b . bishops or b . rooks or b . queens or b . king
2024-04-13 21:23:12 +02:00
else :
2024-04-16 23:45:32 +02:00
# huh?
2024-04-13 21:23:12 +02:00
discard
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
sq = square . toBitboard ( )
king = self . getBitboard ( King , attacker )
2024-04-19 14:38:35 +02:00
if ( KING_BITBOARDS [ square . uint ] and king ) ! = 0 :
2024-04-17 20:27:39 +02:00
result = result or sq
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
sq = square . toBitboard ( )
knights = self . getBitboard ( Knight , attacker )
result = Bitboard ( 0 )
for knight in knights :
2024-04-19 14:38:35 +02:00
if ( KNIGHT_BITBOARDS [ square . uint ] and knight . toBitboard ( ) ) ! = 0 :
2024-04-17 20:27:39 +02:00
result = result or knight . toBitboard ( )
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
sq = square . toBitboard ( )
queens = self . getBitboard ( Queen , attacker )
rooks = self . getBitboard ( Rook , attacker )
bishops = self . getBitboard ( Bishop , attacker )
occupancy = self . getOccupancy ( )
result = Bitboard ( 0 )
for rook in rooks :
let blockers = Rook . getRelevantBlockers ( square )
if ( getRookMoves ( square , blockers ) and rook . toBitboard ( ) ) ! = 0 :
result = result or rook . toBitboard ( )
for bishop in bishops :
let blockers = Bishop . getRelevantBlockers ( square )
if ( getBishopMoves ( square , blockers ) and bishop . toBitboard ( ) ) ! = 0 :
result = result or bishop . toBitboard ( )
for queen in queens :
let rookBlockers = Rook . getRelevantBlockers ( square )
if ( getRookMoves ( square , rookBlockers ) and queen . toBitboard ( ) ) ! = 0 :
result = result or queen . toBitboard ( )
let bishopBlockers = Bishop . getRelevantBlockers ( square )
if ( getBishopMoves ( square , bishopBlockers ) and queen . toBitboard ( ) ) ! = 0 :
result = result or queen . toBitboard ( )
2024-04-17 16:50:55 +02:00
proc getAttacksTo ( self : ChessBoard , square : Square , attacker : PieceColor ) : Bitboard =
## Computes the attack bitboard for the given square from
## the given side
result = Bitboard ( 0 )
let
squareBitboard = square . toBitboard ( )
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 )
proc inCheck ( self : ChessBoard , side : PieceColor ) : bool =
## Returns if the current side to move is in check
return self . getAttacksTo ( self . getKingSquare ( side ) , side . opposite ( ) ) ! = 0
proc canCastle ( self : ChessBoard , side : PieceColor ) : tuple [ king , queen : bool ] =
## Returns if the current side to move can castle
return ( false , false ) # TODO
2024-04-17 16:50:55 +02:00
2024-04-17 20:27:39 +02:00
proc getCapturablePieces ( self : ChessBoard , side : PieceColor ) : Bitboard {. inline . } =
## Returns the set of pieces of the given color that can
## be captured
# Just a handy helper to filter out the king and avoid code duplication
return self . getOccupancyFor ( side ) and not self . getBitboard ( King , side )
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-17 20:27:39 +02:00
enemyPieces = self . getCapturablePieces ( 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-17 11:54:45 +02:00
let epBitboard = if ( epTarget ! = nullSquare ( ) ) : epTarget . toBitboard ( ) else : Bitboard ( 0 )
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 13:40:31 +02:00
proc generateRookMovements ( self : ChessBoard , moves : var MoveList ) =
## Helper of generateRookMoves to generate all non-capture
## rook moves
let
sideToMove = self . getSideToMove ( )
occupancy = self . getOccupancy ( )
friendlyPieces = self . getOccupancyFor ( sideToMove )
rooks = self . getBitboard ( Rook , sideToMove )
for square in rooks :
let blockers = occupancy and Rook . getRelevantBlockers ( square )
var moveset = getRookMoves ( square , blockers )
2024-04-19 14:38:35 +02:00
# Can't move over our own pieces
2024-04-19 13:40:31 +02:00
moveset = moveset and not friendlyPieces
for target in moveset :
moves . add ( createMove ( square , target ) )
proc generateRookCaptures ( self : ChessBoard , moves : var MoveList ) =
## Helper of generateRookMoves to generate all capture
## rook moves
let
sideToMove = self . getSideToMove ( )
occupancy = self . getOccupancy ( )
enemyPieces = self . getCapturablePieces ( sideToMove . opposite ( ) )
rooks = self . getBitboard ( Rook , sideToMove )
for square in rooks :
let blockers = occupancy and Rook . getRelevantBlockers ( square )
var moveset = getRookMoves ( square , blockers )
# Can only cature enemy pieces
2024-04-19 14:38:35 +02:00
moveset = moveset and enemyPieces
2024-04-19 13:40:31 +02:00
for target in moveset :
2024-04-19 14:38:35 +02:00
moves . add ( createMove ( square , target , Capture ) )
2024-04-19 13:40:31 +02:00
proc generateRookMoves ( self : ChessBoard , moves : var MoveList ) =
## Helper of generateSlidingMoves to generate rook moves
self . generateRookMovements ( moves )
self . generateRookCaptures ( moves )
2024-04-19 14:38:35 +02:00
proc generateBishopMovements ( self : ChessBoard , moves : var MoveList ) =
## Helper of generateBishopMoves to generate all non-capture
## bishop moves
let
sideToMove = self . getSideToMove ( )
occupancy = self . getOccupancy ( )
friendlyPieces = self . getOccupancyFor ( sideToMove )
bishops = self . getBitboard ( Bishop , sideToMove )
for square in bishops :
let blockers = occupancy and Bishop . getRelevantBlockers ( square )
var moveset = getBishopMoves ( square , blockers )
# Can't move over our own pieces
moveset = moveset and not friendlyPieces
for target in moveset :
moves . add ( createMove ( square , target ) )
proc generateBishopCaptures ( self : ChessBoard , moves : var MoveList ) =
## Helper of generateBishopMoves to generate all capture
## bishop moves
let
sideToMove = self . getSideToMove ( )
occupancy = self . getOccupancy ( )
enemyPieces = self . getCapturablePieces ( sideToMove . opposite ( ) )
bishops = self . getBitboard ( Bishop , sideToMove )
for square in bishops :
let blockers = occupancy and Bishop . getRelevantBlockers ( square )
var moveset = getRookMoves ( square , blockers )
# Can only cature enemy pieces
moveset = moveset and enemyPieces
for target in moveset :
moves . add ( createMove ( square , target , Capture ) )
proc generateBishopMoves ( self : ChessBoard , moves : var MoveList ) =
## Helper of generateSlidingMoves to generate bishop moves
self . generateBishopMovements ( moves )
self . generateBishopCaptures ( moves )
proc generateQueenMovements ( self : ChessBoard , moves : var MoveList ) =
## Helper of generateQueenMoves to generate all non-capture
## bishop moves
let
sideToMove = self . getSideToMove ( )
occupancy = self . getOccupancy ( )
friendlyPieces = self . getOccupancyFor ( sideToMove )
queens = self . getBitboard ( Queen , sideToMove )
for square in queens :
# A queen is just a rook plus a bishop in terms of move
# generation
let
rookBlockers = Rook . getRelevantBlockers ( square ) and occupancy
bishopBlockers = Bishop . getRelevantBlockers ( square ) and occupancy
rookMoves = getRookMoves ( square , rookBlockers )
bishopMoves = getBishopMoves ( square , bishopBlockers )
var moveset = rookMoves or bishopMoves
# Can't move over our own pieces
moveset = moveset and not friendlyPieces
for target in moveset :
moves . add ( createMove ( square , target ) )
proc generateQueenCaptures ( self : ChessBoard , moves : var MoveList ) =
## Helper of generateQueenMoves to generate all capture
## queen moves
let
sideToMove = self . getSideToMove ( )
occupancy = self . getOccupancy ( )
enemyPieces = self . getCapturablePieces ( sideToMove . opposite ( ) )
queens = self . getBitboard ( Queen , sideToMove )
for square in queens :
# A queen is just a rook plus a bishop in terms of move
# generation
let
rookBlockers = Rook . getRelevantBlockers ( square ) and occupancy
bishopBlockers = Bishop . getRelevantBlockers ( square ) and occupancy
rookMoves = getRookMoves ( square , rookBlockers )
bishopMoves = getBishopMoves ( square , bishopBlockers )
var moveset = rookMoves or bishopMoves
# Can only capture the enemy pieces
moveset = moveset and enemyPieces
for target in moveset :
moves . add ( createMove ( square , target , Capture ) )
proc generateQueenMoves ( self : ChessBoard , moves : var MoveList ) =
## Helper of generateSlidingMoves to generate queen moves
self . generateQueenMovements ( moves )
self . generateQueenCaptures ( moves )
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 )
self . generateQueenMoves ( moves )
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-17 16:50:55 +02:00
allowedSquares = not self . getOccupancy ( )
nonSideToMove = sideToMove . opposite ( )
2024-04-17 20:27:39 +02:00
enemyPieces = self . getCapturablePieces ( nonSideToMove )
2024-04-17 16:50:55 +02:00
# Regular moves
2024-04-17 20:27:39 +02:00
for square in KING_BITBOARDS [ moveIdx ] and allowedSquares :
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 )
allowedSquares = not self . getOccupancy ( )
nonSideToMove = sideToMove . opposite ( )
enemyPieces = self . getCapturablePieces ( nonSideToMove )
for square in knights :
# Regular moves
for target in KNIGHT_BITBOARDS [ square . uint64 ] and allowedSquares :
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-13 19:59:54 +02:00
proc checkInsufficientMaterialPieceCount ( self : ChessBoard , color : PieceColor ) : bool =
## Helper function for checkInsufficientMaterial
let
friendlyPawns = self . countPieces ( Piece ( kind : Pawn , color : color ) )
friendlyRooks = self . countPieces ( Piece ( kind : Rook , color : color ) )
friendlyQueens = self . countPieces ( Piece ( kind : Queen , color : color ) )
friendlyKnights = self . countPieces ( Piece ( kind : Knight , color : color ) )
friendlyBishops = self . countPieces ( Piece ( kind : Bishop , color : color ) )
enemyPawns = self . countPieces ( Piece ( kind : Pawn , color : color . opposite ( ) ) )
enemyRooks = self . countPieces ( Piece ( kind : Rook , color : color . opposite ( ) ) )
enemyQueens = self . countPieces ( Piece ( kind : Queen , color : color . opposite ( ) ) )
enemyKnights = self . countPieces ( Piece ( kind : Knight , color : color . opposite ( ) ) )
enemyBishops = self . countPieces ( Piece ( kind : Bishop , color : color . opposite ( ) ) )
if friendlyPawns > 0 or friendlyRooks > 0 or friendlyQueens > 0 :
return false
if friendlyKnights > = 2 :
return false
if friendlyKnights + friendlyBishops > = 2 :
return false
if friendlyKnights > = 1 and ( enemyPawns > 0 or enemyRooks > 0 or enemyBishops > 0 or enemyKnights > 0 or enemyQueens > 0 ) :
return false
if friendlyBishops > = 1 and ( enemyKnights > 0 or enemyPawns > 0 ) :
return false
return true
proc checkInsufficientMaterial ( self : ChessBoard ) : bool =
## Checks if the given position has not enough material for either side to
## checkmate the enemy king. Note that the criteria as implemented here are
## not fully compliant with FIDE rules (they just define a draw by insufficient
## material as "[...] the position is such that the opponent cannot checkmate
## the player’ s king by any possible series of legal moves.", which is really
## tricky to implement efficiently). For more info see https://www.reddit.com/r/chess/comments/se89db/a_writeup_on_definitions_of_insufficient_material/
if not ( self . checkInsufficientMaterialPieceCount ( White ) and self . checkInsufficientMaterialPieceCount ( Black ) ) :
return false
let
whiteBishops = self . countPieces ( Piece ( kind : Bishop , color : White ) )
blackBishops = self . countPieces ( Piece ( kind : Bishop , color : Black ) )
if blackBishops + whiteBishops > = 2 :
var
darkSquare = 0
lightSquare = 0
2024-04-16 15:24:31 +02:00
for bishop in self . position . pieces . black . bishops :
2024-04-13 19:59:54 +02:00
if bishop . isLightSquare ( ) :
lightSquare + = 1
else :
darkSquare + = 1
2024-04-16 15:24:31 +02:00
for bishop in self . position . pieces . white . bishops :
2024-04-13 19:59:54 +02:00
if bishop . isLightSquare ( ) :
lightSquare + = 1
else :
darkSquare + = 1
if darkSquare > = 1 and lightSquare > = 1 :
return false
return true
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
#[
if self . checkInsufficientMaterial ( ) :
return @ [ ]
] #
2024-04-16 23:45:32 +02:00
# TODO: Check for repetitions (requires zobrist hashing + table)
self . generatePawnMoves ( moves )
2024-04-17 16:50:55 +02:00
self . generateKingMoves ( 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 ]
2023-10-15 16:53:44 +02:00
case piece . color :
of White :
case piece . kind :
of Pawn :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . pawns . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . bishops . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . knights . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . rooks . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . queens . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of King :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . king . uint64 . clearBit ( square . int8 )
2024-04-19 17:05:18 +02:00
of Empty :
doAssert false , & " cannot remove empty white piece from {square} "
2023-10-15 16:53:44 +02:00
of Black :
case piece . kind :
of Pawn :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . pawns . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . bishops . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . knights . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . rooks . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . queens . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
of King :
2024-04-19 17:05:18 +02:00
self . position . pieces . black . king . uint64 . clearBit ( square . int8 )
of Empty :
doAssert false , & " cannot remove empty black piece from {square} "
2023-10-15 16:53:44 +02:00
else :
2024-04-19 17:05:18 +02:00
doAssert false , & " cannot remove empty piece from colorless square {square} "
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
case piece . color :
of White :
case piece . kind :
of Pawn :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . pawns . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of Bishop :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . bishops . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of Knight :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . knights . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of Rook :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . rooks . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of Queen :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . queens . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of King :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . king . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
else :
discard
of Black :
case piece . kind :
of Pawn :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . pawns . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of Bishop :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . bishops . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of Knight :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . knights . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of Rook :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . rooks . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of Queen :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . queens . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
of King :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . king . uint64 . setBit ( square . int8 )
2024-04-15 12:04:50 +02:00
else :
discard
else :
discard
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 17:05:18 +02:00
doAssert piece . kind ! = Empty and piece . color ! = None
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 ]
let targetSquare = self . getPiece ( move . targetSquare )
if targetSquare . color ! = None :
2024-04-19 17:05:18 +02:00
raise newException ( AccessViolationDefect , & " {piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare} " )
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 17:05:18 +02:00
proc movePiece ( self : ChessBoard , startSquare , targetSquare : Square ) =
2024-04-15 12:04:50 +02:00
## Like the other movePiece(), but with two squares
2024-04-19 17:05:18 +02:00
self . movePiece ( Move ( startSquare : startSquare , targetSquare : targetSquare ) )
2023-10-17 15:08:46 +02:00
2024-04-08 20:28:31 +02:00
2023-10-15 16:53:44 +02:00
proc doMove ( self : ChessBoard , move : Move ) =
## 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 )
2024-04-19 17:05:18 +02:00
inc ( self . currPos )
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 17:05:18 +02:00
doAssert piece . kind ! = Empty and piece . color ! = None
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-17 11:54:45 +02:00
turn : 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 )
# 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-19 17:05:18 +02:00
self . updateBoard ( )
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-15 12:04:50 +02:00
## square. Note that this will overwrite whatever piece
2023-10-17 15:08:46 +02:00
## was previously located there: use with caution. Does
## not automatically update the attacked square metadata
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-10 13:45:29 +02:00
proc updateBoard * ( self : ChessBoard ) =
## 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 ( )
for sq in self . position . pieces . white . pawns :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : White , kind : Pawn )
2024-04-16 15:24:31 +02:00
for sq in self . position . pieces . black . pawns :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : Black , kind : Pawn )
2024-04-16 15:24:31 +02:00
for sq in self . position . pieces . white . bishops :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : White , kind : Bishop )
2024-04-16 15:24:31 +02:00
for sq in self . position . pieces . black . bishops :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : Black , kind : Bishop )
2024-04-16 15:24:31 +02:00
for sq in self . position . pieces . white . knights :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : White , kind : Knight )
2024-04-16 15:24:31 +02:00
for sq in self . position . pieces . black . knights :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : Black , kind : Knight )
2024-04-16 15:24:31 +02:00
for sq in self . position . pieces . white . rooks :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : White , kind : Rook )
2024-04-16 15:24:31 +02:00
for sq in self . position . pieces . black . rooks :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : Black , kind : Rook )
2024-04-16 15:24:31 +02:00
for sq in self . position . pieces . white . queens :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : White , kind : Queen )
2024-04-16 15:24:31 +02:00
for sq in self . position . pieces . black . queens :
2024-04-15 12:04:50 +02:00
self . grid [ sq ] = Piece ( color : Black , kind : Queen )
2024-04-19 17:05:18 +02:00
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 )
2024-04-16 15:24:31 +02:00
proc unmakeMove * ( self : ChessBoard ) =
## Reverts to the previous board position,
## if one exists
2024-04-19 17:05:18 +02:00
if self . currPos > = 0 :
2024-04-16 15:24:31 +02:00
self . position = self . positions [ self . currPos ]
2024-04-19 17:05:18 +02:00
dec ( self . currPos )
2024-04-10 13:45:29 +02:00
self . updateBoard ( )
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 =
if piece . color = = White :
return char ( piece . kind ) . toUpperAscii ( )
return char ( piece . kind )
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 & = " \n a 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-30 14:46:27 +01:00
proc perft * ( self : ChessBoard , ply : int , verbose : bool = false , divide : bool = false , bulk : bool = false ) : CountData =
## Counts (and debugs) the number of legal positions reached after
## the given number of ply
2024-04-16 23:45:32 +02:00
var moves = MoveList ( )
self . generateMoves ( moves )
2024-04-08 20:28:31 +02:00
if not bulk :
2024-04-19 14:38:35 +02:00
if len ( moves ) = = 0 and self . inCheck ( self . getSideToMove ( ) ) :
2024-04-08 20:28:31 +02:00
result . checkmates = 1
2024-04-15 12:04:50 +02:00
# TODO: Should we count stalemates/draws?
2024-04-08 20:28:31 +02:00
if ply = = 0 :
result . nodes = 1
return
elif ply = = 1 and bulk :
2023-10-31 23:06:27 +01:00
if divide :
var postfix = " "
for move in moves :
2023-11-13 09:52:37 +01:00
case move . getPromotionType ( ) :
2023-10-31 23:06:27 +01:00
of PromoteToBishop :
postfix = " b "
of PromoteToKnight :
postfix = " n "
of PromoteToRook :
postfix = " r "
of PromoteToQueen :
postfix = " q "
else :
postfix = " "
2024-04-16 23:45:32 +02:00
echo & " {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}{postfix}: 1 "
2023-10-31 23:06:27 +01:00
if verbose :
echo " "
2023-10-30 14:46:27 +01:00
return ( uint64 ( len ( moves ) ) , 0 , 0 , 0 , 0 , 0 , 0 )
2023-10-28 02:32:50 +02:00
for move in moves :
if verbose :
2024-04-17 11:54:45 +02:00
let canCastle = self . canCastle ( self . getSideToMove ( ) )
2023-10-31 23:06:27 +01:00
echo & " Ply (from root): {self.position.plyFromRoot} "
2024-04-19 14:38:35 +02:00
echo & " Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()} "
2024-04-17 11:54:45 +02:00
echo & " Turn: {self.getSideToMove()} "
2024-04-15 12:04:50 +02:00
echo & " Piece: {self.grid[move.startSquare].kind} "
2024-04-08 20:28:31 +02:00
echo & " Flags: {move.getFlags()} "
2024-04-19 14:38:35 +02:00
echo & " In check: {(if self.inCheck(self.getSideToMove()): \" yes \" else: \" no \" )} "
2023-10-28 02:32:50 +02:00
echo & " Can castle: \n - King side: {(if canCastle.king: \" yes \" else: \" no \" )} \n - Queen side: {(if canCastle.queen: \" yes \" else: \" no \" )} "
2023-10-30 14:46:27 +01:00
echo & " Position before move: {self.toFEN()} "
stdout . write ( " En Passant target: " )
2024-04-16 15:24:31 +02:00
if self . getEnPassantTarget ( ) ! = nullSquare ( ) :
2024-04-16 23:45:32 +02:00
echo self . getEnPassantTarget ( ) . toAlgebraic ( )
2023-10-30 14:46:27 +01:00
else :
echo " None "
echo " \n " , self . pretty ( )
2023-10-28 02:32:50 +02:00
self . doMove ( move )
2024-04-08 20:28:31 +02:00
if ply = = 1 :
if move . isCapture ( ) :
inc ( result . captures )
if move . isCastling ( ) :
inc ( result . castles )
if move . isPromotion ( ) :
inc ( result . promotions )
if move . isEnPassant ( ) :
inc ( result . enPassant )
2024-04-19 14:38:35 +02:00
if self . inCheck ( self . getSideToMove ( ) ) :
2023-10-30 14:46:27 +01:00
# Opponent king is in check
inc ( result . checks )
2023-10-28 02:32:50 +02:00
if verbose :
2024-04-17 11:54:45 +02:00
let canCastle = self . canCastle ( self . getSideToMove ( ) )
2023-10-30 14:46:27 +01:00
echo " \n "
2024-04-19 14:38:35 +02:00
echo & " Opponent in check: {(if self.inCheck(self.getSideToMove()): \" yes \" else: \" no \" )} "
2023-10-30 14:46:27 +01:00
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 " \n " , self . pretty ( )
2023-10-31 23:06:27 +01:00
stdout . write ( " nextpos>> " )
2023-10-28 02:32:50 +02:00
try :
discard readLine ( stdin )
except IOError :
discard
except EOFError :
discard
2023-10-30 14:46:27 +01:00
let next = self . perft ( ply - 1 , verbose , bulk = bulk )
2024-04-16 15:24:31 +02:00
self . unmakeMove ( )
2023-10-31 23:06:27 +01:00
if divide and ( not bulk or ply > 1 ) :
2023-10-30 14:46:27 +01:00
var postfix = " "
2023-11-13 09:52:37 +01:00
if move . isPromotion ( ) :
case move . getPromotionType ( ) :
of PromoteToBishop :
postfix = " b "
of PromoteToKnight :
postfix = " n "
of PromoteToRook :
postfix = " r "
of PromoteToQueen :
postfix = " q "
else :
discard
2024-04-16 23:45:32 +02:00
echo & " {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}{postfix}: {next.nodes} "
2023-10-28 02:32:50 +02:00
if verbose :
echo " "
2023-10-30 14:46:27 +01:00
result . nodes + = next . nodes
result . captures + = next . captures
result . checks + = next . checks
result . promotions + = next . promotions
result . castles + = next . castles
result . enPassant + = next . enPassant
result . checkmates + = next . checkmates
2024-04-19 14:38:35 +02:00
2023-10-28 02:32:50 +02:00
2023-11-13 09:52:37 +01:00
proc handleGoCommand ( board : ChessBoard , command : seq [ string ] ) =
if len ( command ) < 2 :
echo & " Error: go: invalid number of arguments "
return
case command [ 1 ] :
of " perft " :
if len ( command ) = = 2 :
echo & " Error: go: perft: invalid number of arguments "
return
var
args = command [ 2 ] . splitWhitespace ( )
bulk = false
verbose = false
if args . len ( ) > 1 :
var ok = true
for arg in args [ 1 .. ^ 1 ] :
case arg :
2024-04-08 20:28:31 +02:00
of " bulk " :
2023-11-13 09:52:37 +01:00
bulk = true
of " verbose " :
verbose = true
else :
echo & " Error: go: perft: invalid argument ' {args[1]} ' "
ok = false
break
if not ok :
return
2024-04-19 14:38:35 +02:00
try :
2023-11-13 09:52:37 +01:00
let ply = parseInt ( args [ 0 ] )
if bulk :
2024-04-08 20:28:31 +02:00
let t = cpuTime ( )
let nodes = board . perft ( ply , divide = true , bulk = true , verbose = verbose ) . nodes
echo & " \n Nodes searched (bulk-counting: on): {nodes} "
echo & " Time taken: {round(cpuTime() - t, 3)} seconds \n "
2023-11-13 09:52:37 +01:00
else :
2024-04-08 20:28:31 +02:00
let t = cpuTime ( )
2023-11-13 09:52:37 +01:00
let data = board . perft ( ply , divide = true , verbose = verbose )
echo & " \n Nodes searched (bulk-counting: off): {data.nodes} "
echo & " - Captures: {data.captures} "
echo & " - Checks: {data.checks} "
echo & " - E.P: {data.enPassant} "
echo & " - Checkmates: {data.checkmates} "
echo & " - Castles: {data.castles} "
echo & " - Promotions: {data.promotions} "
echo " "
2024-04-08 20:28:31 +02:00
echo & " Time taken: {round(cpuTime() - t, 3)} seconds "
2023-11-13 09:52:37 +01:00
except ValueError :
echo " Error: go: perft: invalid depth "
else :
echo & " Error: go: unknown subcommand ' {command[1]} ' "
2024-04-08 20:28:31 +02:00
proc handleMoveCommand ( board : ChessBoard , command : seq [ string ] ) : Move {. discardable . } =
2023-11-13 09:52:37 +01:00
if len ( command ) ! = 2 :
echo & " Error: move: invalid number of arguments "
return
let moveString = command [ 1 ]
if len ( moveString ) notin 4 .. 5 :
echo & " Error: move: invalid move syntax "
return
var
2024-04-15 12:04:50 +02:00
startSquare : Square
targetSquare : Square
2024-04-16 15:24:31 +02:00
flags : seq [ MoveFlag ]
2023-11-13 09:52:37 +01:00
try :
2024-04-16 23:45:32 +02:00
startSquare = moveString [ 0 .. 1 ] . toSquare ( )
2023-11-13 09:52:37 +01:00
except ValueError :
2024-04-08 20:28:31 +02:00
echo & " Error: move: invalid start square ({moveString[0..1]}) "
2023-11-13 09:52:37 +01:00
return
try :
2024-04-16 23:45:32 +02:00
targetSquare = moveString [ 2 .. 3 ] . toSquare ( )
2023-11-13 09:52:37 +01:00
except ValueError :
2024-04-08 20:28:31 +02:00
echo & " Error: move: invalid target square ({moveString[2..3]}) "
2023-11-13 09:52:37 +01:00
return
2024-04-08 20:28:31 +02:00
# Since the user tells us just the source and target square of the move,
# we have to figure out all the flags by ourselves (whether it's a double
2024-04-19 17:05:18 +02:00
# push, a capture, a promotion, etc.)
2024-04-08 20:28:31 +02:00
2024-04-15 12:04:50 +02:00
if board . grid [ targetSquare ] . kind ! = Empty :
2024-04-16 15:24:31 +02:00
flags . add ( Capture )
2023-11-13 09:52:37 +01:00
2024-04-19 17:05:18 +02:00
elif board . grid [ startSquare ] . kind = = Pawn and abs ( rankFromSquare ( startSquare ) - rankFromSquare ( targetSquare ) ) = = 2 :
flags . add ( DoublePush )
2023-11-13 09:52:37 +01:00
if len ( moveString ) = = 5 :
# Promotion
case moveString [ 4 ] :
of ' b ' :
2024-04-16 15:24:31 +02:00
flags . add ( PromoteToBishop )
2023-11-13 09:52:37 +01:00
of ' n ' :
2024-04-16 15:24:31 +02:00
flags . add ( PromoteToKnight )
2023-11-13 09:52:37 +01:00
of ' q ' :
2024-04-16 15:24:31 +02:00
flags . add ( PromoteToQueen )
2023-11-13 09:52:37 +01:00
of ' r ' :
2024-04-16 15:24:31 +02:00
flags . add ( PromoteToRook )
2023-11-13 09:52:37 +01:00
else :
echo & " Error: move: invalid promotion type "
return
2024-04-16 15:24:31 +02:00
var move = createMove ( startSquare , targetSquare , flags )
2024-04-16 23:45:32 +02:00
let piece = board . getPiece ( move . startSquare )
2024-04-17 11:54:45 +02:00
if piece . kind = = King and move . startSquare = = board . getSideToMove ( ) . getKingStartingSquare ( ) :
2024-04-16 23:45:32 +02:00
if move . targetSquare = = longCastleKing ( piece . color ) :
2024-04-08 20:28:31 +02:00
move . flags = move . flags or CastleLong . uint16
2024-04-16 23:45:32 +02:00
elif move . targetSquare = = shortCastleKing ( piece . color ) :
2024-04-08 20:28:31 +02:00
move . flags = move . flags or CastleShort . uint16
2024-04-12 16:05:01 +02:00
if move . targetSquare = = board . getEnPassantTarget ( ) :
move . flags = move . flags or EnPassant . uint16
2024-04-08 20:28:31 +02:00
result = board . makeMove ( move )
2024-04-16 15:24:31 +02:00
if result = = nullMove ( ) :
2024-04-08 20:28:31 +02:00
echo & " Error: move: {moveString} is illegal "
proc handlePositionCommand ( board : var ChessBoard , command : seq [ string ] ) =
if len ( command ) < 2 :
echo " Error: position: invalid number of arguments "
return
# Makes sure we don't leave the board in an invalid state if
# some error occurs
2024-04-10 13:45:29 +02:00
var tempBoard : ChessBoard
2024-04-08 20:28:31 +02:00
case command [ 1 ] :
of " startpos " :
tempBoard = newDefaultChessboard ( )
if command . len ( ) > 2 :
let args = command [ 2 ] . splitWhitespace ( )
if args . len ( ) > 0 :
var i = 0
while i < args . len ( ) :
case args [ i ] :
of " moves " :
var j = i + 1
while j < args . len ( ) :
2024-04-16 15:24:31 +02:00
if handleMoveCommand ( tempBoard , @ [ " move " , args [ j ] ] ) = = nullMove ( ) :
2024-04-08 20:28:31 +02:00
return
inc ( j )
inc ( i )
board = tempBoard
of " fen " :
if len ( command ) = = 2 :
echo & " Current position: {board.toFEN()} "
return
var
args = command [ 2 ] . splitWhitespace ( )
fenString = " "
stop = 0
for i , arg in args :
if arg in [ " moves " , ] :
break
if i > 0 :
fenString & = " "
fenString & = arg
inc ( stop )
args = args [ stop .. ^ 1 ]
try :
tempBoard = newChessboardFromFEN ( fenString )
except ValueError :
2024-04-12 16:05:01 +02:00
echo & " error: position: {getCurrentExceptionMsg()} "
2024-04-08 20:28:31 +02:00
return
if args . len ( ) > 0 :
var i = 0
while i < args . len ( ) :
case args [ i ] :
of " moves " :
var j = i + 1
while j < args . len ( ) :
2024-04-16 15:24:31 +02:00
if handleMoveCommand ( tempBoard , @ [ " move " , args [ j ] ] ) = = nullMove ( ) :
2024-04-08 20:28:31 +02:00
return
inc ( j )
inc ( i )
board = tempBoard
of " print " :
echo board
of " pretty " :
echo board . pretty ( )
2024-04-12 16:05:01 +02:00
else :
echo & " error: position: unknown subcommand ' {command[1]} ' "
return
2024-04-08 20:28:31 +02:00
2024-04-09 19:55:08 +02:00
proc handleUCICommand ( board : var ChessBoard , command : seq [ string ] ) =
2024-04-09 17:55:12 +02:00
echo " id name Nimfish 0.1 "
echo " id author Nocturn9x & Contributors (see LICENSE) "
# TODO
echo " uciok "
2024-04-08 20:28:31 +02:00
const HELP_TEXT = """ Nimfish help menu:
- go : Begin a search
Subcommands :
- perft < depth > [ options ] : Run the performance test at the given depth ( in ply ) and
print the results
Options :
- bulk : Enable bulk - counting ( significantly faster , gives less statistics )
- verbose : Enable move debugging ( for each and every move , not recommended on large searches )
Example : go perft 5 bulk
- position : Get / set board position
Subcommands :
- fen [ string ] : Set the board to the given fen string if one is provided , or print
the current position as a FEN string if no arguments are given
- startpos : Set the board to the starting position
- pretty : Pretty - print the current position
- print : Print the current position using ASCII characters only
Options :
- moves { moveList } : Perform the given moves ( space - separated , all - lowercase )
in algebraic notation after the position is loaded . This option only applies
to the " startpos " and " fen " subcommands : it is ignored otherwise
Examples :
- position startpos
- position fen " ... " moves a2a3 a7a6
- clear : Clear the screen
- move < move > : Perform the given move in algebraic notation
2024-04-19 17:05:18 +02:00
- castle : Print castlingRights rights for each side
2024-04-08 20:28:31 +02:00
- check : Print if the current side to move is in check
2024-04-10 13:45:29 +02:00
- undo , u : Undoes the last move . Can be used in succession
2024-04-08 20:28:31 +02:00
- turn : Print which side is to move
- ep : Print the current en passant target
- pretty : Shorthand for " position pretty "
- print : Shorthand for " position print "
2024-04-12 16:05:01 +02:00
- fen : Shorthand for " position fen "
- pos < args > : Shorthand for " position <args> "
2024-04-09 17:46:30 +02:00
- get < square > : Get the piece on the given square
2024-04-19 14:38:35 +02:00
- atk : Print the attack bitboard of the given square for the side to move
2024-04-09 17:55:12 +02:00
- uci : enter UCI mode ( WIP )
2024-04-08 20:28:31 +02:00
"""
2023-11-13 09:52:37 +01:00
2023-10-30 14:46:27 +01:00
proc main : int =
2023-10-31 23:06:27 +01:00
## Nimfish's control interface
2023-10-30 14:46:27 +01:00
echo " Nimfish by nocturn9x (see LICENSE) "
2024-04-09 17:55:12 +02:00
var
board = newDefaultChessboard ( )
uciMode = false
2023-10-30 14:46:27 +01:00
while true :
2023-10-31 23:06:27 +01:00
var
cmd : seq [ string ]
cmdStr : string
2023-10-30 14:46:27 +01:00
try :
2024-04-09 17:55:12 +02:00
if not uciMode :
stdout . write ( " >>> " )
stdout . flushFile ( )
2023-10-31 23:06:27 +01:00
cmdStr = readLine ( stdin ) . strip ( leading = true , trailing = true , chars = { ' \t ' , ' ' } )
if cmdStr . len ( ) = = 0 :
continue
cmd = cmdStr . splitWhitespace ( maxsplit = 2 )
2023-10-30 14:46:27 +01:00
case cmd [ 0 ] :
2024-04-09 17:55:12 +02:00
of " uci " :
2024-04-09 19:55:08 +02:00
handleUCICommand ( board , cmd )
uciMode = true
2023-10-30 14:46:27 +01:00
of " clear " :
echo " \x1B c "
of " help " :
2024-04-08 20:28:31 +02:00
echo HELP_TEXT
2023-10-30 14:46:27 +01:00
of " go " :
2023-11-13 09:52:37 +01:00
handleGoCommand ( board , cmd )
2024-04-12 16:05:01 +02:00
of " position " , " pos " :
2023-11-13 09:52:37 +01:00
handlePositionCommand ( board , cmd )
of " move " :
handleMoveCommand ( board , cmd )
2024-04-09 19:55:08 +02:00
of " pretty " , " print " , " fen " :
2024-04-08 20:28:31 +02:00
handlePositionCommand ( board , @ [ " position " , cmd [ 0 ] ] )
2024-04-09 19:55:08 +02:00
of " undo " , " u " :
2024-04-16 15:24:31 +02:00
board . unmakeMove ( )
2024-04-08 20:28:31 +02:00
of " turn " :
2024-04-17 11:54:45 +02:00
echo & " Active color: {board.getSideToMove()} "
2024-04-19 14:38:35 +02:00
of " atk " :
if len ( cmd ) ! = 2 :
echo " error: atk: invalid number of arguments "
continue
try :
echo board . getAttacksTo ( cmd [ 1 ] . toSquare ( ) , board . getSideToMove ( ) )
except ValueError :
echo " error: atk: invalid square "
continue
2024-04-08 20:28:31 +02:00
of " ep " :
let target = board . getEnPassantTarget ( )
2024-04-16 15:24:31 +02:00
if target ! = nullSquare ( ) :
2024-04-16 23:45:32 +02:00
echo & " En passant target: {target.toAlgebraic()} "
2024-04-08 20:28:31 +02:00
else :
echo " En passant target: None "
2024-04-09 17:46:30 +02:00
of " get " :
if len ( cmd ) ! = 2 :
2024-04-09 17:55:12 +02:00
echo " error: get: invalid number of arguments "
2024-04-09 17:46:30 +02:00
continue
try :
echo board . getPiece ( cmd [ 1 ] )
except ValueError :
2024-04-09 17:55:12 +02:00
echo " error: get: invalid square "
2024-04-19 17:05:18 +02:00
continue
2024-04-19 14:38:35 +02:00
of " castle " :
let canCastle = board . canCastle ( board . getSideToMove ( ) )
2024-04-17 11:54:45 +02:00
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 \" )} "
2024-04-08 20:28:31 +02:00
of " check " :
2024-04-19 14:38:35 +02:00
echo & " {board.getSideToMove()} king in check: {(if board.inCheck(board.getSideToMove()): \" yes \" else: \" no \" )} "
2023-10-23 18:02:31 +02:00
else :
2023-10-30 14:46:27 +01:00
echo & " Unknown command ' {cmd[0]} ' . Type ' help ' for more information. "
except IOError :
echo " "
2024-04-13 16:28:48 +02:00
return 0
2023-10-30 14:46:27 +01:00
except EOFError :
echo " "
return 0
2023-10-21 18:19:41 +02:00
2023-10-13 11:54:57 +02:00
when isMainModule :
2024-04-13 21:23:12 +02:00
2023-10-13 11:54:57 +02:00
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 "
2023-10-13 12:26:14 +02:00
proc testPieceCount ( board : ChessBoard , kind : PieceKind , color : PieceColor , count : int ) =
let pieces = board . countPieces ( kind , color )
doAssert pieces = = count , & " expected {count} pieces of kind {kind} and color {color}, got {pieces} instead "
2024-04-16 15:24:31 +02:00
proc testPieceBitboard ( bitboard : Bitboard , squares : seq [ Square ] ) =
var i = 0
for square in bitboard :
doAssert squares [ i ] = = square , & " squares[{i}] != bitboard[i]: {squares[i]} != {square} "
inc ( i )
if i ! = squares . len ( ) :
doAssert false , & " bitboard.len() ({i}) != squares.len() ({squares.len()}) "
2023-10-13 12:26:14 +02:00
2023-10-13 11:54:57 +02:00
var b = newDefaultChessboard ( )
2023-10-13 12:26:14 +02:00
# Ensure correct number of pieces
testPieceCount ( b , Pawn , White , 8 )
testPieceCount ( b , Pawn , Black , 8 )
testPieceCount ( b , Knight , White , 2 )
testPieceCount ( b , Knight , Black , 2 )
testPieceCount ( b , Bishop , White , 2 )
testPieceCount ( b , Bishop , Black , 2 )
testPieceCount ( b , Rook , White , 2 )
testPieceCount ( b , Rook , Black , 2 )
testPieceCount ( b , Queen , White , 1 )
testPieceCount ( b , Queen , Black , 1 )
testPieceCount ( b , King , White , 1 )
testPieceCount ( b , King , Black , 1 )
2024-04-16 15:24:31 +02:00
# Ensure pieces are in the correct squares. This is testing the FEN
# parser
2023-10-13 12:26:14 +02:00
# Pawns
2023-10-13 11:54:57 +02:00
for loc in [ " a2 " , " b2 " , " c2 " , " d2 " , " e2 " , " f2 " , " g2 " , " h2 " ] :
testPiece ( b . getPiece ( loc ) , Pawn , White )
for loc in [ " a7 " , " b7 " , " c7 " , " d7 " , " e7 " , " f7 " , " g7 " , " h7 " ] :
testPiece ( b . getPiece ( loc ) , Pawn , Black )
# Rooks
testPiece ( b . getPiece ( " a1 " ) , Rook , White )
testPiece ( b . getPiece ( " h1 " ) , Rook , White )
testPiece ( b . getPiece ( " a8 " ) , Rook , Black )
testPiece ( b . getPiece ( " h8 " ) , Rook , Black )
# Knights
testPiece ( b . getPiece ( " b1 " ) , Knight , White )
testPiece ( b . getPiece ( " g1 " ) , Knight , White )
testPiece ( b . getPiece ( " b8 " ) , Knight , Black )
testPiece ( b . getPiece ( " g8 " ) , Knight , Black )
# Bishops
testPiece ( b . getPiece ( " c1 " ) , Bishop , White )
testPiece ( b . getPiece ( " f1 " ) , Bishop , White )
testPiece ( b . getPiece ( " c8 " ) , Bishop , Black )
testPiece ( b . getPiece ( " f8 " ) , Bishop , Black )
# Kings
testPiece ( b . getPiece ( " e1 " ) , King , White )
testPiece ( b . getPiece ( " e8 " ) , King , Black )
# Queens
testPiece ( b . getPiece ( " d1 " ) , Queen , White )
testPiece ( b . getPiece ( " d8 " ) , Queen , Black )
2024-04-15 12:45:47 +02:00
2024-04-16 15:24:31 +02:00
# Ensure our bitboards match with the board
let
whitePawns = b . getBitboard ( Pawn , White )
whiteKnights = b . getBitboard ( Knight , White )
whiteBishops = b . getBitboard ( Bishop , White )
whiteRooks = b . getBitboard ( Rook , White )
whiteQueens = b . getBitboard ( Queen , White )
whiteKing = b . getBitboard ( King , White )
blackPawns = b . getBitboard ( Pawn , Black )
blackKnights = b . getBitboard ( Knight , Black )
blackBishops = b . getBitboard ( Bishop , Black )
blackRooks = b . getBitboard ( Rook , Black )
blackQueens = b . getBitboard ( Queen , Black )
blackKing = b . getBitboard ( King , Black )
2024-04-16 23:45:32 +02:00
whitePawnSquares = @ [ makeSquare ( 6 'i8 , 0 'i8 ) , makeSquare ( 6 , 1 ) , makeSquare ( 6 , 2 ) , makeSquare ( 6 , 3 ) , makeSquare ( 6 , 4 ) , makeSquare ( 6 , 5 ) , makeSquare ( 6 , 6 ) , makeSquare ( 6 , 7 ) ]
whiteKnightSquares = @ [ makeSquare ( 7 'i8 , 1 'i8 ) , makeSquare ( 7 , 6 ) ]
whiteBishopSquares = @ [ makeSquare ( 7 'i8 , 2 'i8 ) , makeSquare ( 7 , 5 ) ]
whiteRookSquares = @ [ makeSquare ( 7 'i8 , 0 'i8 ) , makeSquare ( 7 , 7 ) ]
whiteQueenSquares = @ [ makeSquare ( 7 'i8 , 3 'i8 ) ]
whiteKingSquares = @ [ makeSquare ( 7 'i8 , 4 'i8 ) ]
blackPawnSquares = @ [ makeSquare ( 1 'i8 , 0 'i8 ) , makeSquare ( 1 , 1 ) , makeSquare ( 1 , 2 ) , makeSquare ( 1 , 3 ) , makeSquare ( 1 , 4 ) , makeSquare ( 1 , 5 ) , makeSquare ( 1 , 6 ) , makeSquare ( 1 , 7 ) ]
blackKnightSquares = @ [ makeSquare ( 0 'i8 , 1 'i8 ) , makeSquare ( 0 , 6 ) ]
blackBishopSquares = @ [ makeSquare ( 0 'i8 , 2 'i8 ) , makeSquare ( 0 , 5 ) ]
blackRookSquares = @ [ makeSquare ( 0 'i8 , 0 'i8 ) , makeSquare ( 0 , 7 ) ]
blackQueenSquares = @ [ makeSquare ( 0 'i8 , 3 'i8 ) ]
blackKingSquares = @ [ makeSquare ( 0 'i8 , 4 'i8 ) ]
2024-04-16 15:24:31 +02:00
testPieceBitboard ( whitePawns , whitePawnSquares )
testPieceBitboard ( whiteKnights , whiteKnightSquares )
testPieceBitboard ( whiteBishops , whiteBishopSquares )
testPieceBitboard ( whiteRooks , whiteRookSquares )
testPieceBitboard ( whiteQueens , whiteQueenSquares )
testPieceBitboard ( whiteKing , whiteKingSquares )
testPieceBitboard ( blackPawns , blackPawnSquares )
testPieceBitboard ( blackKnights , blackKnightSquares )
testPieceBitboard ( blackBishops , blackBishopSquares )
testPieceBitboard ( blackRooks , blackRookSquares )
testPieceBitboard ( blackQueens , blackQueenSquares )
testPieceBitboard ( blackKing , blackKingSquares )
2024-04-19 13:40:31 +02:00
2024-04-19 14:38:35 +02:00
setControlCHook ( proc ( ) {. noconv . } = quit ( 0 ) )
quit ( main ( ) )