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-16 15:24:31 +02:00
import bitboards
import pieces
import 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-10 13:45:29 +02:00
# Did the rooks on either side or the king move?
2023-10-17 22:16:01 +02:00
castlingAvailable : tuple [ white , black : tuple [ queen , king : bool ] ]
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
# every 2 ply
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-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
# (flattened 8x8 matrix)
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-12 16:05:01 +02:00
proc movePiece ( self : ChessBoard , move : Move , attack : bool = true )
2024-04-15 12:04:50 +02:00
proc removePiece ( self : ChessBoard , square : Square , attack : bool = true )
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-16 23:45:32 +02:00
return makeSquare ( 7 , 4 )
2024-04-08 20:28:31 +02:00
of Black :
2024-04-16 23:45:32 +02:00
return makeSquare ( 0 , 4 )
2024-04-08 20:28:31 +02:00
else :
discard
2024-04-16 23:45:32 +02:00
func kingSideRook ( color : PieceColor ) : Square {. inline . } = ( if color = = White : makeSquare ( 7 , 7 ) else : makeSquare ( 0 , 7 ) )
func queenSideRook ( color : PieceColor ) : Square {. inline . } = ( if color = = White : makeSquare ( 7 , 0 ) else : makeSquare ( 0 , 0 ) )
func longCastleKing ( color : PieceColor ) : Square {. inline . } = ( if color = = White : makeSquare ( 7 , 2 ) else : makeSquare ( 0 , 5 ) )
func shortCastleKing ( color : PieceColor ) : Square {. inline . } = ( if color = = White : makeSquare ( 7 , 6 ) else : makeSquare ( 0 , 1 ) )
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 ) )
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 ( )
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-16 15:24:31 +02:00
if result . position . pieces . black . king ! = Bitboard ( 0 'u64 ) :
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 :
of ' - ' :
# Neither side can castle anywhere: do nothing,
# as the castling metadata is set to this state
# by default
discard
of ' K ' :
2023-10-17 22:16:01 +02:00
result . position . castlingAvailable . white . king = true
2023-03-18 18:14:30 +01:00
of ' Q ' :
2023-10-17 22:16:01 +02:00
result . position . castlingAvailable . white . queen = true
2023-03-18 18:14:30 +01:00
of ' k ' :
2023-10-17 22:16:01 +02:00
result . position . castlingAvailable . black . king = true
2023-03-18 18:14:30 +01:00
of ' q ' :
2023-10-17 22:16:01 +02:00
result . position . castlingAvailable . black . queen = true
2023-03-18 18:14:30 +01:00
else :
2023-11-01 19:07:09 +01:00
raise newException ( ValueError , & " invalid FEN: unknown symbol ' {c} ' found in castling 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-16 08:50:42 +02:00
# result.updateAttackedSquares()
2024-04-17 11:54:45 +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-16 08:50:42 +02:00
raise newException ( ValueError , " invalid position: opponent king can be captured " ) ] #
2024-04-16 15:24:31 +02:00
if result . position . pieces . white . king = = Bitboard ( 0 ) or result . position . pieces . black . king = = Bitboard ( 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 . } =
## Returns the castling type of the given move.
## 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-15 12:04:50 +02:00
func getKing ( self : ChessBoard , color : PieceColor = None ) : Square {. inline . } =
## Returns the square of the king for the given
2024-04-10 13:45:29 +02:00
## color (if it is None, the active color 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-16 23:45:32 +02:00
return Square ( self . position . pieces . white . king . uint64 . countTrailingZeroBits ( ) )
2024-04-08 20:28:31 +02:00
of Black :
2024-04-16 23:45:32 +02:00
return Square ( self . position . pieces . black . king . uint64 . countTrailingZeroBits ( ) )
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-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
occupancy = not ( self . getOccupancy ( ) or sideToMove . getLastRank ( ) )
# Single push
2024-04-16 23:45:32 +02:00
for square in pawns . forwardRelativeTo ( sideToMove ) and occupancy :
2024-04-17 11:54:45 +02:00
moves . add ( createMove ( square . toBitboard ( ) . backwardRelativeTo ( sideToMove ) , square ) )
# Double push
let rank = if sideToMove = = White : getRankMask ( 6 ) else : getRankMask ( 1 ) # Only pawns on their starting rank can double push
for square in ( pawns and rank ) . doubleForwardRelativeTo ( sideToMove ) and occupancy :
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 )
# We can only capture enemy pieces
enemyPieces = self . getOccupancyFor ( 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 11:54:45 +02:00
moves . add ( createMove ( square . toBitboard ( ) . bottomLeftRelativeTo ( sideToMove ) , square , Capture ) )
2024-04-16 23:45:32 +02:00
for square in leftMovement and enemyPieces :
2024-04-17 11:54:45 +02:00
moves . add ( createMove ( square . toBitboard ( ) . bottomRightRelativeTo ( 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 )
occupancy = not self . getOccupancy ( )
for square in pawns . forwardRelativeTo ( sideToMove ) and occupancy and sideToMove . getLastRank ( ) :
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 ) =
2023-10-16 15:25:48 +02:00
## Generates the possible moves for the pawn in the given
2024-04-15 12:04:50 +02:00
## square
2024-04-16 08:50:42 +02:00
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-15 12:04:50 +02:00
proc generateSlidingMoves ( self : ChessBoard , square : Square ) : seq [ Move ] =
## Generates moves for the sliding piece in the given square
2023-10-16 14:55:43 +02:00
2024-04-15 12:04:50 +02:00
proc generateKingMoves ( self : ChessBoard , square : Square ) : seq [ Move ] =
## Generates moves for the king in the given square
2023-10-17 12:42:15 +02:00
2024-04-15 12:04:50 +02:00
proc generateKnightMoves ( self : ChessBoard , square : Square ) : seq [ Move ] =
## Generates moves for the knight in the given square
2024-04-16 23:45:32 +02:00
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 )
# TODO: all pieces
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 )
2023-10-15 16:53:44 +02:00
else :
2024-04-15 12:04:50 +02:00
discard
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-16 23:45:32 +02:00
self . position . pieces . black . king . uint64 . clearBit ( square . int8 )
2023-10-15 16:53:44 +02:00
else :
discard
else :
discard
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
proc removePiece ( self : ChessBoard , square : Square , attack : bool = true ) =
## Removes a piece from the board, updating necessary
## metadata
var piece = self . grid [ square ]
2024-04-16 15:24:31 +02:00
self . grid [ square ] = nullPiece ( )
2024-04-15 12:04:50 +02:00
self . removePieceFromBitboard ( square )
2024-04-16 08:50:42 +02:00
#[if attack:
self . updateAttackedSquares ( ) ] #
2023-10-15 16:53:44 +02:00
2024-04-16 15:24:31 +02:00
proc updateMovepieces ( self : ChessBoard , move : Move ) =
2024-04-15 12:04:50 +02:00
## Updates our bitboard representation after a move: note that this
## does *not* handle captures, en passant, promotions etc. as those
## are already called by helpers such as removePiece() and spawnPiece()
var bitboard : uint64
let piece = self . grid [ move . startSquare ]
# TODO: Should we use our helpers or is it faster to branch only once?
2023-10-28 02:32:50 +02:00
case piece . color :
2023-10-15 16:53:44 +02:00
of White :
2023-10-28 02:32:50 +02:00
case piece . kind :
2023-10-15 16:53:44 +02:00
of Pawn :
2024-04-16 23:45:32 +02:00
self . position . pieces . white . pawns . uint64 . setBit ( move . targetSquare . int8 )
self . position . pieces . white . pawns . uint64 . clearBit ( move . startSquare . 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 . setBit ( move . targetSquare . int8 )
self . position . pieces . white . bishops . uint64 . clearBit ( move . startSquare . 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 . setBit ( move . targetSquare . int8 )
self . position . pieces . white . knights . uint64 . clearBit ( move . startSquare . 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 . setBit ( move . targetSquare . int8 )
self . position . pieces . white . rooks . uint64 . clearBit ( move . startSquare . 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 . setBit ( move . targetSquare . int8 )
self . position . pieces . white . queens . uint64 . clearBit ( move . startSquare . 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 . setBit ( move . targetSquare . int8 )
self . position . pieces . white . king . uint64 . clearBit ( move . startSquare . int8 )
2023-10-15 16:53:44 +02:00
else :
2023-10-16 14:55:43 +02:00
discard
2023-10-15 16:53:44 +02:00
of Black :
2023-10-28 02:32:50 +02:00
case piece . kind :
2023-10-15 16:53:44 +02:00
of Pawn :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . pawns . uint64 . setBit ( move . targetSquare . int8 )
self . position . pieces . black . pawns . uint64 . clearBit ( move . startSquare . 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 . setBit ( move . targetSquare . int8 )
self . position . pieces . black . bishops . uint64 . clearBit ( move . startSquare . 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 . setBit ( move . targetSquare . int8 )
self . position . pieces . black . knights . uint64 . clearBit ( move . startSquare . 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 . setBit ( move . targetSquare . int8 )
self . position . pieces . black . rooks . uint64 . clearBit ( move . startSquare . 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 . setBit ( move . targetSquare . int8 )
self . position . pieces . black . queens . uint64 . clearBit ( move . startSquare . int8 )
2023-10-15 16:53:44 +02:00
of King :
2024-04-16 23:45:32 +02:00
self . position . pieces . black . king . uint64 . setBit ( move . targetSquare . int8 )
self . position . pieces . black . king . uint64 . clearBit ( move . startSquare . int8 )
2023-10-15 16:53:44 +02:00
else :
discard
else :
2023-10-17 15:08:46 +02:00
discard
2024-04-15 12:04:50 +02:00
proc movePiece ( self : ChessBoard , move : Move , attack : bool = true ) =
## 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 :
raise newException ( AccessViolationDefect , & " attempted to overwrite a piece! {move} " )
# Update positional metadata
2024-04-16 23:45:32 +02:00
self . updateMovePieces ( move )
2023-10-21 18:19:41 +02:00
# Empty out the starting square
2024-04-16 15:24:31 +02:00
self . grid [ move . startSquare ] = nullPiece ( )
2024-04-10 13:45:29 +02:00
# Actually move the piece on the board
2024-04-15 12:04:50 +02:00
self . grid [ move . targetSquare ] = piece
2024-04-16 08:50:42 +02:00
#[if attack:
self . updateAttackedSquares ( ) ] #
2023-11-01 19:07:09 +01:00
2023-10-20 02:23:07 +02:00
2024-04-15 12:04:50 +02:00
proc movePiece ( self : ChessBoard , startSquare , targetSquare : Square , attack : bool = true ) =
## Like the other movePiece(), but with two squares
2023-10-28 02:32:50 +02:00
self . movePiece ( Move ( startSquare : startSquare , targetSquare : targetSquare ) , attack )
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 )
2023-10-17 15:08:46 +02:00
# Final checks
2024-04-15 12:04:50 +02:00
let piece = self . grid [ move . startSquare ]
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
2023-10-17 22:16:01 +02:00
castlingAvailable = self . position . castlingAvailable
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
# Castling check: have the rooks moved?
2023-10-28 02:32:50 +02:00
if piece . kind = = Rook :
case piece . color :
2023-10-21 18:19:41 +02:00
of White :
2024-04-16 23:45:32 +02:00
if rowFromSquare ( move . startSquare ) = = piece . getStartRank ( ) :
if colFromSquare ( move . startSquare ) = = 0 :
2023-10-21 18:19:41 +02:00
# Queen side
castlingAvailable . white . queen = false
2024-04-16 23:45:32 +02:00
elif colfromSquare ( move . startSquare ) = = 7 :
2023-10-21 18:19:41 +02:00
# King side
castlingAvailable . white . king = false
of Black :
2024-04-16 23:45:32 +02:00
if rowFromSquare ( move . startSquare ) = = piece . getStartRank ( ) :
if colFromSquare ( move . startSquare ) = = 0 :
2023-10-21 18:19:41 +02:00
# Queen side
castlingAvailable . black . queen = false
2024-04-16 23:45:32 +02:00
elif colFromSquare ( move . startSquare ) = = 7 :
2023-10-21 18:19:41 +02:00
# King side
castlingAvailable . black . king = false
else :
discard
# Has a rook been captured?
2023-11-13 09:52:37 +01:00
if move . isCapture ( ) :
2024-04-15 12:04:50 +02:00
let captured = self . grid [ move . targetSquare ]
2023-10-31 23:06:27 +01:00
if captured . kind = = Rook :
2024-04-09 17:46:30 +02:00
case captured . color :
2023-10-17 23:56:26 +02:00
of White :
2023-10-31 23:06:27 +01:00
if move . targetSquare = = captured . color . queenSideRook ( ) :
2023-10-21 18:19:41 +02:00
# Queen side
castlingAvailable . white . queen = false
2023-10-31 23:06:27 +01:00
elif move . targetSquare = = captured . color . kingSideRook ( ) :
2023-10-21 18:19:41 +02:00
# King side
castlingAvailable . white . king = false
2023-10-17 23:56:26 +02:00
of Black :
2023-10-31 23:06:27 +01:00
if move . targetSquare = = captured . color . queenSideRook ( ) :
2023-10-21 18:19:41 +02:00
# Queen side
castlingAvailable . black . queen = false
2023-10-31 23:06:27 +01:00
elif move . targetSquare = = captured . color . kingSideRook ( ) :
2023-10-21 18:19:41 +02:00
# King side
castlingAvailable . black . king = false
2023-10-17 23:56:26 +02:00
else :
2023-10-21 18:19:41 +02:00
# Unreachable
2023-10-17 23:56:26 +02:00
discard
2023-10-21 18:19:41 +02:00
# Has the king moved?
2023-11-13 09:52:37 +01:00
if piece . kind = = King or move . isCastling ( ) :
2023-10-28 02:32:50 +02:00
# Revoke all castling rights for the moving king
case piece . color :
2023-10-21 18:19:41 +02:00
of White :
castlingAvailable . white . king = false
castlingAvailable . white . queen = false
of Black :
castlingAvailable . black . king = false
castlingAvailable . black . queen = false
2023-10-17 22:16:01 +02:00
else :
2023-10-21 18:19:41 +02:00
discard
2024-04-08 20:28:31 +02: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 ,
2023-10-31 23:06:27 +01:00
castlingAvailable : castlingAvailable ,
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 . isCastling ( ) :
2023-10-21 18:19:41 +02:00
# Move the rook onto the
2023-10-31 23:06:27 +01:00
# correct file when castling
2023-10-21 18:19:41 +02:00
var
2024-04-15 12:04:50 +02:00
square : Square
target : Square
2024-04-16 23:45:32 +02:00
flag : MoveFlag
2023-11-13 09:52:37 +01:00
if move . getCastlingType ( ) = = CastleShort :
2024-04-15 12:04:50 +02:00
square = piece . color . kingSideRook ( )
2024-04-16 23:45:32 +02:00
target = shortCastleRook ( piece . color )
flag = CastleShort
2023-10-21 18:19:41 +02:00
else :
2024-04-15 12:04:50 +02:00
square = piece . color . queenSideRook ( )
2024-04-16 23:45:32 +02:00
target = longCastleRook ( piece . color )
flag = CastleLong
let rook = self . grid [ square ]
self . movePiece ( createMove ( square , target , flag ) , attack = false )
2023-10-17 22:16:01 +02:00
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-17 11:54:45 +02:00
self . removePiece ( move . targetSquare . toBitboard ( ) . backwardRelativeTo ( piece . color ) . toSquare ( ) , attack = false )
2024-04-10 13:45:29 +02:00
if move . isCapture ( ) :
# Get rid of captured pieces
self . removePiece ( move . targetSquare , attack = false )
# Move the piece to its target square and update attack metadata
self . movePiece ( move , attack = false )
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-16 08:50:42 +02:00
#self.updateAttackedSquares()
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-17 11:54:45 +02:00
self . grid [ self . position . pieces . white . king . toSquare ( ) ] = Piece ( color : White , kind : King )
self . grid [ self . position . pieces . black . king . toSquare ( ) ] = 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
if self . currPos > 0 :
dec ( self . currPos )
self . position = self . positions [ self . currPos ]
self . updateBoard ( )
2023-10-23 18:02:31 +02:00
2024-04-16 15:24:31 +02:00
proc redoMove * ( self : ChessBoard ) =
## Reverts to the next board position, if one
## exists. Only makes sense after a call to
## unmakeMove
if self . positions . high ( ) > self . currPos :
inc ( self . currPos )
self . position = self . positions [ 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
let castleWhite = self . position . castlingAvailable . white
let castleBlack = self . position . castlingAvailable . black
if not ( castleBlack . king or castleBlack . queen or castleWhite . king or castleWhite . queen ) :
result & = " - "
else :
if castleWhite . king :
result & = " K "
if castleWhite . queen :
result & = " Q "
if castleBlack . king :
result & = " k "
if castleBlack . queen :
result & = " q "
result & = " "
# En passant target
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-16 23:45:32 +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 :
if len ( moves ) = = 0 and self . inCheck ( ) :
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-16 23:45:32 +02:00
echo & " Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}, from ({move.startSquare.rank}, {move.startSquare.file}) to ({move.targetSquare.rank}, {move.targetSquare.file}) "
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()} "
2023-10-30 14:46:27 +01:00
echo & " In check: {(if self.inCheck(): \" 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 )
2023-10-30 14:46:27 +01:00
if self . inCheck ( ) :
# 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 "
echo & " Opponent in check: {(if self.inCheck(): \" yes \" else: \" no \" )} "
echo & " Opponent can castle: \n - King side: {(if canCastle.king: \" yes \" else: \" no \" )} \n - Queen side: {(if canCastle.queen: \" yes \" else: \" no \" )} "
echo & " 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-16 23:45:32 +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-16 23:45:32 +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-16 23:45:32 +02:00
] #
2023-11-13 09:52:37 +01:00
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
# push, a capture, a promotion, castling, etc.)
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-16 23:45:32 +02:00
elif board . grid [ startSquare ] . kind = = Pawn and abs ( rowFromSquare ( startSquare ) - rowFromSquare ( targetSquare ) ) = = 2 :
2024-04-16 15:24:31 +02:00
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
- castle : Print castling rights for each side
- 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-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-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-09 17:46:30 +02:00
continue
2024-04-16 23:45:32 +02:00
#[of "castle":
2024-04-08 20:28:31 +02:00
let canCastle = board . canCastle ( )
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-17 11:54:45 +02:00
echo & " {board.getSideToMove()} king in check: {(if board.inCheck(): \" yes \" else: \" no \" )} "
2024-04-16 23:45:32 +02:00
] #
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-16 23:45:32 +02:00
2024-04-17 11:54:45 +02:00
b = newChessboardFromFEN ( " B7/P1P1P1P1/1P1P1P1P/7k/8/8/8/3K4 w - - 0 1 " )
2024-04-16 23:45:32 +02:00
var m = MoveList ( )
b . generatePawnMoves ( m )
2024-04-17 11:54:45 +02:00
echo & " Pawn moves for {b.getSideToMove()} at {b.toFEN()}: "
2024-04-16 23:45:32 +02:00
for move in m :
2024-04-17 11:54:45 +02:00
echo " - " , move . startSquare , move . targetSquare , " " , move . getFlags ( )
#[b.doMove(createMove("d1", "c1"))
2024-04-16 23:45:32 +02:00
m . clear ( )
b . generatePawnMoves ( m )
2024-04-17 11:54:45 +02:00
echo & " Pawn moves for {b.getSideToMove()} at {b.toFEN()}: "
2024-04-16 23:45:32 +02:00
for move in m :
2024-04-17 11:54:45 +02:00
echo " - " , move . startSquare , move . targetSquare , " " , move . getFlags ( )
echo b . pretty ( ) ] #
2024-04-16 15:24:31 +02:00
# setControlCHook(proc () {.noconv.} = quit(0))
2024-04-16 23:45:32 +02:00
# quit(main())