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
2023-10-30 14:46:27 +01:00
2023-03-18 18:14:30 +01:00
type
2023-10-15 16:53:44 +02:00
2023-03-18 18:14:30 +01:00
PieceColor * = enum
2023-10-15 16:53:44 +02:00
## A piece color enumeration
2023-10-18 10:45:54 +02:00
None = 0 'i8 ,
2023-03-18 18:14:30 +01:00
White ,
Black
2023-10-15 16:53:44 +02:00
2023-03-18 18:14:30 +01:00
PieceKind * = enum
2023-10-15 16:53:44 +02:00
## A chess piece enumeration
2023-10-25 22:41:04 +02:00
Empty = 0 'i8 , # No piece
2023-03-18 18:14:30 +01:00
Bishop = ' b ' ,
King = ' k '
Knight = ' n ' ,
Pawn = ' p ' ,
Queen = ' q ' ,
Rook = ' r ' ,
2023-10-15 16:53:44 +02:00
2024-04-15 12:04:50 +02:00
Direction * = enum
## A move direction enumeration
Forward ,
Backward ,
Left ,
Right
ForwardLeft ,
ForwardRight ,
BackwardLeft ,
BackwardRight
2023-03-18 18:14:30 +01:00
Piece * = object
2023-10-15 16:53:44 +02:00
## A chess piece
2023-03-18 18:14:30 +01:00
color * : PieceColor
kind * : PieceKind
2023-10-15 16:53:44 +02:00
2023-10-16 14:55:43 +02:00
MoveFlag * = enum
## An enumeration of move flags
2023-11-13 09:52:37 +01:00
Default = 0 'u 16 , # No flag
EnPassant = 1 , # Move is a capture with en passant
Capture = 2 , # Move is a capture
DoublePush = 4 , # Move is a double pawn push
2023-10-20 02:23:07 +02:00
# Castling metadata
2023-11-13 09:52:37 +01:00
CastleLong = 8 ,
CastleShort = 16 ,
2023-10-20 02:23:07 +02:00
# Pawn promotion metadata
2023-11-13 09:52:37 +01:00
PromoteToQueen = 32 ,
PromoteToRook = 64 ,
PromoteToBishop = 128 ,
PromoteToKnight = 256
2023-10-16 14:55:43 +02:00
2024-04-15 12:04:50 +02:00
Bitboard * = distinct uint64
Square * = tuple [ rank , file : int8 ]
2023-10-25 22:41:04 +02:00
2024-04-15 12:04:50 +02:00
Attacked = seq [ tuple [ source , target , direction : Square ] ]
2023-10-25 22:41:04 +02:00
2024-04-15 12:04:50 +02:00
Pieces = tuple [ king : Square , queens : seq [ Square ] , rooks : seq [ Square ] ,
bishops : seq [ Square ] , knights : seq [ Square ] ,
pawns : seq [ Square ] ]
2023-10-25 22:41:04 +02:00
CountData = tuple [ nodes : uint64 , captures : uint64 , castles : uint64 , checks : uint64 , promotions : uint64 , enPassant : uint64 , checkmates : uint64 ]
2023-10-16 14:55:43 +02:00
Move * = object
2023-10-15 16:53:44 +02:00
## A chess move
2024-04-15 12:04:50 +02:00
startSquare * : Square
targetSquare * : Square
2023-11-13 11:03:54 +01:00
flags * : uint16
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
# Squares of all pieces
pieces : tuple [ white , black : Pieces ]
2023-10-31 23:06:27 +01:00
# Squares attacked by both sides
2024-04-15 12:04:50 +02:00
attacked : tuple [ white , black : Attacked ]
2024-04-10 13:45:29 +02:00
# Pieces pinned by both sides (only absolute pins)
2024-04-15 12:04:50 +02:00
pinned : tuple [ white , black : Attacked ]
2023-10-17 15:08:46 +02:00
# Active color
turn : PieceColor
2024-04-15 12:04:50 +02:00
# Bitboards for all pieces
bitboards : tuple [ white , black : tuple [ king , queens , rooks , bishops , knights , pawns : Bitboard ] ]
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 ]
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-11-01 19:07:09 +01:00
2024-04-15 12:45:47 +02:00
2023-10-15 16:53:44 +02:00
func emptyPiece * : Piece {. inline . } = Piece ( kind : Empty , color : None )
2024-04-15 12:04:50 +02:00
func emptySquare * : Square {. inline . } = ( - 1 , - 1 )
2023-10-15 16:53:44 +02:00
func opposite * ( c : PieceColor ) : PieceColor {. inline . } = ( if c = = White : Black else : White )
2024-04-15 12:45:47 +02:00
func algebraicToSquare * ( s : string ) : Square {. inline . }
2023-10-21 18:19:41 +02:00
proc makeMove * ( self : ChessBoard , move : Move ) : Move {. discardable . }
2024-04-15 12:04:50 +02:00
func emptyMove * : Move {. inline . } = Move ( startSquare : emptySquare ( ) , targetSquare : emptySquare ( ) )
2024-04-15 12:45:47 +02:00
func ` + ` * ( a , b : Square ) : Square {. inline . } = ( a . rank + b . rank , a . file + b . file )
func ` - ` * ( a : Square ) : Square {. inline . } = ( - a . rank , - a . file )
func ` - ` * ( a , b : Square ) : Square {. inline . } = ( a . rank - b . rank , a . file - b . file )
2024-04-15 12:04:50 +02:00
func isValid * ( a : Square ) : bool {. inline . } = a . rank in 0 .. 7 and a . file in 0 .. 7
func isLightSquare ( a : Square ) : bool {. inline . } = ( a . rank + a . file and 2 ) = = 0
proc generateMoves ( self : ChessBoard , square : Square ) : seq [ Move ]
proc getAttackers * ( self : ChessBoard , square : Square , color : PieceColor ) : seq [ Square ]
proc getAttackFor * ( self : ChessBoard , source , target : Square ) : tuple [ source , target , direction : Square ]
proc isAttacked * ( self : ChessBoard , square : Square , color : PieceColor = None ) : bool
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 )
2023-10-21 18:19:41 +02:00
proc updateAttackedSquares ( self : ChessBoard )
2024-04-12 16:05:01 +02:00
proc updateSlidingAttacks ( self : ChessBoard )
2024-04-15 12:04:50 +02:00
proc getPinnedDirections ( self : ChessBoard , square : Square ) : seq [ Square ]
proc getAttacks * ( self : ChessBoard , square : Square ) : Attacked
proc getSlidingAttacks ( self : ChessBoard , square : Square ) : tuple [ attacks : Attacked , pins : Attacked ]
2023-10-30 17:46:06 +01:00
proc inCheck * ( self : ChessBoard , color : PieceColor = None ) : bool
2024-04-08 20:28:31 +02:00
proc toFEN * ( self : ChessBoard ) : string
proc undoLastMove * ( 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
2024-04-15 12:45:47 +02:00
func createMove ( startSquare , targetSquare : string , flags : seq [ MoveFlag ] = @ [ ] ) : Move =
result = Move ( startSquare : startSquare . algebraicToSquare ( ) ,
targetSquare : targetSquare . algebraicToSquare ( ) , flags : Default . uint16 )
for flag in flags :
result . flags = result . flags or flag . uint16
func makeSquare ( rank , file : SomeInteger ) : Square = ( rank : rank . int8 , file : file . int8 )
2023-11-01 19:07:09 +01:00
# Due to our board layout, directions of movement are reversed for white and black, so
# we need these helpers to avoid going mad with integer tuples and minus signs everywhere
2024-04-15 12:04:50 +02:00
func topLeftDiagonal ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( - 1 , - 1 ) else : ( 1 , 1 ) )
func topRightDiagonal ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( - 1 , 1 ) else : ( 1 , - 1 ) )
func bottomLeftDiagonal ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( 1 , - 1 ) else : ( - 1 , 1 ) )
func bottomRightDiagonal ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( 1 , 1 ) else : ( - 1 , - 1 ) )
func leftSide ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( 0 , - 1 ) else : ( 0 , 1 ) )
func rightSide ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( 0 , 1 ) else : ( 0 , - 1 ) )
func topSide ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( - 1 , 0 ) else : ( 1 , 0 ) )
func bottomSide ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( 1 , 0 ) else : ( - 1 , 0 ) )
func doublePush ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( - 2 , 0 ) else : ( 2 , 0 ) )
func longCastleKing : Square {. inline . } = ( 0 , - 2 )
func shortCastleKing : Square {. inline . } = ( 0 , 2 )
func longCastleRook : Square {. inline . } = ( 0 , 3 )
func shortCastleRook : Square {. inline . } = ( 0 , - 2 )
func bottomLeftKnightMove ( color : PieceColor , long : bool = true ) : Square {. inline . } =
2023-10-17 22:16:01 +02:00
if color = = White :
2023-10-17 12:42:15 +02:00
if long :
2023-10-18 10:45:54 +02:00
return ( 2 , - 1 )
2023-10-17 12:42:15 +02:00
else :
2023-10-20 02:23:07 +02:00
return ( 1 , - 2 )
2023-10-17 22:16:01 +02:00
elif color = = Black :
2023-10-17 12:42:15 +02:00
if long :
2023-10-21 18:19:41 +02:00
return ( - 2 , 1 )
2023-10-17 12:42:15 +02:00
else :
2023-10-21 18:19:41 +02:00
return ( 1 , - 2 )
2023-10-17 12:42:15 +02:00
2024-04-15 12:04:50 +02:00
func bottomRightKnightMove ( color : PieceColor , long : bool = true ) : Square {. inline . } =
2023-10-17 22:16:01 +02:00
if color = = White :
2023-10-17 12:42:15 +02:00
if long :
2023-10-20 02:23:07 +02:00
return ( 2 , 1 )
2023-10-17 12:42:15 +02:00
else :
return ( 1 , 2 )
2023-10-17 22:16:01 +02:00
elif color = = Black :
2023-10-17 12:42:15 +02:00
if long :
2023-10-20 02:23:07 +02:00
return ( - 2 , - 1 )
2023-10-17 12:42:15 +02:00
else :
2023-10-20 02:23:07 +02:00
return ( - 1 , - 2 )
2023-10-17 12:42:15 +02:00
2024-04-15 12:04:50 +02:00
func topLeftKnightMove ( color : PieceColor , long : bool = true ) : Square {. inline . } =
2023-10-17 22:16:01 +02:00
if color = = White :
2023-10-17 12:42:15 +02:00
if long :
return ( - 2 , - 1 )
else :
return ( - 1 , - 2 )
2023-10-17 22:16:01 +02:00
elif color = = Black :
2023-10-17 12:42:15 +02:00
if long :
return ( 2 , 1 )
else :
return ( 1 , 2 )
2024-04-15 12:04:50 +02:00
func topRightKnightMove ( color : PieceColor , long : bool = true ) : Square {. inline . } =
2023-10-17 22:16:01 +02:00
if color = = White :
2023-10-17 12:42:15 +02:00
if long :
return ( - 2 , 1 )
else :
return ( - 1 , 2 )
2023-10-17 22:16:01 +02:00
elif color = = Black :
2023-10-17 12:42:15 +02:00
if long :
return ( 2 , - 1 )
else :
2023-10-21 18:19:41 +02:00
return ( - 1 , 2 )
2023-10-16 22:14:58 +02:00
2024-04-15 12:04:50 +02:00
# These return absolute squares rather than relative direction offsets
func kingSideRook ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( 7 , 7 ) else : ( 0 , 7 ) )
func queenSideRook ( color : PieceColor ) : Square {. inline . } = ( if color = = White : ( 7 , 0 ) else : ( 0 , 0 ) )
2023-11-01 19:07:09 +01:00
2023-10-16 22:14:58 +02:00
2023-11-01 19:07:09 +01:00
# A bunch of getters
2023-10-28 02:32:50 +02:00
func getActiveColor * ( self : ChessBoard ) : PieceColor {. inline . } =
2023-10-16 22:14:58 +02:00
## Returns the currently active color
## (turn of who has 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 :
return ( 7 , 4 )
of Black :
return ( 0 , 4 )
else :
discard
2024-04-15 12:04:50 +02:00
func getLastRank ( color : PieceColor ) : int {. inline . } =
## Retrieves the square of the last
## rank relative to the given color
2023-10-17 10:31:38 +02:00
case color :
of White :
return 0
of Black :
return 7
else :
return - 1
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 :
result . grid [ i ] = emptyPiece ( )
2023-10-17 15:08:46 +02:00
result . position = Position ( attacked : ( @ [ ] , @ [ ] ) ,
2024-04-15 12:04:50 +02:00
enPassantSquare : emptySquare ( ) ,
2023-10-20 02:23:07 +02:00
turn : White ,
2023-10-25 22:41:04 +02:00
fullMoveCount : 1 ,
2024-04-15 12:04:50 +02:00
pieces : ( white : ( king : emptySquare ( ) ,
2023-10-25 22:41:04 +02:00
queens : @ [ ] ,
rooks : @ [ ] ,
bishops : @ [ ] ,
knights : @ [ ] ,
pawns : @ [ ] ) ,
2024-04-15 12:04:50 +02:00
black : ( king : emptySquare ( ) ,
2023-10-25 22:41:04 +02:00
queens : @ [ ] ,
rooks : @ [ ] ,
bishops : @ [ ] ,
knights : @ [ ] ,
pawns : @ [ ] ) ) )
2023-10-17 15:08:46 +02:00
2024-04-15 12:04:50 +02:00
# Handy wrappers and utilities for handling low-level stuff
func coordToIndex ( row , col : SomeInteger ) : int {. inline . } = ( row * 8 ) + col
func coordToIndex ( square : Square ) : int {. inline . } = coordToIndex ( square . rank , square . file )
2024-04-15 12:45:47 +02:00
func indexToCoord ( index : SomeInteger ) : Square {. inline . } = ( ( index div 8 ) . int8 , ( index mod 8 ) . int8 )
2023-10-12 11:55:12 +02:00
2024-04-15 12:04:50 +02:00
# Indexing operations
2024-04-10 13:45:29 +02:00
func ` [ ] ` ( self : array [ 64 , Piece ] , row , column : Natural ) : Piece {. inline . } = self [ coordToIndex ( row , column ) ]
proc `[]=` ( self : var array [ 64 , Piece ] , row , column : Natural , piece : Piece ) {. inline . } = self [ coordToIndex ( row , column ) ] = piece
2024-04-15 12:04:50 +02:00
func ` [ ] ` ( self : array [ 64 , Piece ] , square : Square ) : Piece {. inline . } = self [ square . rank , square . file ]
func ` [ ] = ` ( self : var array [ 64 , Piece ] , square : Square , piece : Piece ) {. inline . } = self [ square . rank , square . file ] = piece
# Overloaded operators and functions for our bitboard type
func ` shl ` ( a : Bitboard , x : Positive ) : Bitboard = Bitboard ( a . uint64 shl x )
func ` shr ` ( a : Bitboard , x : Positive ) : Bitboard = Bitboard ( a . uint64 shr x )
func ` and ` ( a , b : Bitboard ) : Bitboard = Bitboard ( a . uint64 and b . uint64 )
func ` shr ` ( a , b : Bitboard ) : Bitboard = Bitboard ( a . uint64 and b . uint64 )
func ` = = ` ( a , b : Bitboard ) : bool {. inline . } = a . uint64 = = b . uint64
func ` = = ` ( a : Bitboard , b : SomeInteger ) : bool {. inline . } = a . uint64 = = b . uint64
func getFileMask ( file : Positive ) : Bitboard = Bitboard ( 0x101010101010101 'u64 ) shl file
func getRankMask ( rank : Positive ) : Bitboard = Bitboard ( uint64 . high ( ) ) shl Positive ( 8 * ( rank + 1 ) )
func squareToBitboard ( square : SomeInteger ) : Bitboard = Bitboard ( 1 'u64 ) shl square . uint64
func squareToBitboard ( square : Square ) : Bitboard = squareToBitboard ( coordToIndex ( square ) )
func toBin ( x : Bitboard , b : Positive = 64 ) : string = toBin ( BiggestInt ( x ) , b )
func toBin ( x : uint64 , b : Positive = 64 ) : string = toBin ( Bitboard ( x ) , b )
2024-04-15 12:45:47 +02:00
iterator items ( self : Bitboard ) : Square =
## Iterates ove the given bitboard
## and returns all the squares that
## are set
var bits = self . uint64
while bits ! = 0 :
yield Square ( bits . countTrailingZeroBits ( ) . indexToCoord ( ) )
bits = bits and bits - 1
func pretty ( self : Bitboard ) : string =
iterator items ( self : Bitboard ) : uint8 =
## Iterates over all the bits in the
## given bitboard
for i in 0 .. 63 :
yield self . uint64 . bitsliced ( i .. i ) . uint8
iterator pairs ( self : Bitboard ) : ( int , uint8 ) =
var i = 0
for bit in self :
yield ( i , bit )
inc ( i )
## Returns a prettyfied version of
## the given bitboard
for i , bit in self :
if i > 0 and i mod 8 = = 0 :
result & = " \n "
result & = $ bit
2024-04-15 12:04:50 +02:00
func computeDiagonalBitboards : array [ 14 , Bitboard ] {. compileTime . } =
## Precomputes all the bitboards for diagonals
## at compile time
result [ 0 ] = Bitboard ( 0x8040201008040201 'u64 )
var
col = 1
i = 0
# Left to right
while col < 8 :
result [ col ] = Bitboard ( 0x8040201008040201 'u64 ) shl ( 8 * col )
inc ( col )
inc ( i )
result [ i ] = Bitboard ( 0x102040810204080 'u64 )
inc ( i )
col = 1
# Right to left
while col < 7 :
result [ i ] = Bitboard ( 0x102040810204080 'u64 ) shr ( 8 * col )
inc ( i )
inc ( col )
const diagonalBitboards = computeDiagonalBitboards ( )
func getDirectionMask ( square : Square , color : PieceColor , direction : Direction ) : Bitboard =
## Get a bitmask for the given direction for a piece
## of the given color
case color :
of White :
case direction :
of Forward :
return squareToBitboard ( square ) shl 8
of Backward :
return squareToBitboard ( square ) shr 8
of ForwardRight :
return squareToBitboard ( square ) shl 9
of ForwardLeft :
return squareToBitboard ( square ) shr 9
of BackwardRight :
return squareToBitboard ( square ) shl 17
of BackwardLeft :
return squareToBitboard ( square ) shr 17
else :
discard
of Black :
# The directions for black are just the opposite of those for white,
# so we avoid duplicating any code
case direction :
of Forward :
return getDirectionMask ( square , White , Backward )
of Backward :
return getDirectionMask ( square , White , Forward )
of ForwardRight :
return getDirectionMask ( square , White , ForwardLeft )
of ForwardLeft :
return getDirectionMask ( square , White , ForwardRight )
of BackwardRight :
return getDirectionMask ( square , White , BackwardLeft )
of BackwardLeft :
return getDirectionMask ( square , White , BackwardRight )
else :
discard
else :
discard
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 :
return self . position . bitboards . white . pawns
of Knight :
return self . position . bitboards . white . knights
of Bishop :
return self . position . bitboards . white . bishops
of Rook :
return self . position . bitboards . white . rooks
of Queen :
return self . position . bitboards . white . queens
of King :
return self . position . bitboards . white . king
else :
discard
of Black :
case kind :
of Pawn :
return self . position . bitboards . black . pawns
of Knight :
return self . position . bitboards . black . knights
of Bishop :
return self . position . bitboards . black . bishops
of Rook :
return self . position . bitboards . black . rooks
of Queen :
return self . position . bitboards . black . queens
of King :
return self . position . bitboards . black . king
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
square : Square = ( row , column )
bitIndex = square . coordToIndex ( )
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-15 12:04:50 +02:00
result . position . bitboards . black . pawns . uint64 . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Bishop :
2024-04-15 12:04:50 +02:00
result . position . bitboards . black . bishops . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Knight :
2024-04-15 12:04:50 +02:00
result . position . bitboards . black . knights . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Rook :
2024-04-15 12:04:50 +02:00
result . position . bitboards . black . rooks . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Queen :
2024-04-15 12:04:50 +02:00
result . position . bitboards . black . queens . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of King :
2024-04-15 12:04:50 +02:00
if result . position . bitboards . 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-15 12:04:50 +02:00
result . position . bitboards . black . king . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
else :
discard
of White :
case piece . kind :
of Pawn :
2024-04-15 12:04:50 +02:00
result . position . bitboards . white . pawns . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Bishop :
2024-04-15 12:04:50 +02:00
result . position . bitboards . white . bishops . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Knight :
2024-04-15 12:04:50 +02:00
result . position . bitboards . white . knights . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Rook :
2024-04-15 12:04:50 +02:00
result . position . bitboards . white . rooks . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of Queen :
2024-04-15 12:04:50 +02:00
result . position . bitboards . white . queens . uint64 . setBit ( bitIndex )
2023-10-12 11:55:12 +02:00
of King :
2024-04-15 12:04:50 +02:00
if result . position . bitboards . 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-15 12:04:50 +02:00
result . position . bitboards . 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-15 12:04:50 +02:00
result . position . enPassantSquare = fen [ index .. index + 1 ] . algebraicToSquare ( )
2023-03-18 18:14:30 +01:00
# Square metadata is 2 bytes long
inc ( index )
of 4 :
# Halfmove clock
var s = " "
2023-11-01 19:07:09 +01:00
while not fen [ index ] . isSpaceAscii ( ) :
s . add ( fen [ index ] )
2023-03-18 18:14:30 +01:00
inc ( index )
# Backtrack so the space is seen by the
# next iteration of the loop
dec ( index )
2023-10-18 10:45:54 +02:00
result . position . halfMoveClock = parseInt ( s ) . int8
2023-03-18 18:14:30 +01:00
of 5 :
# Fullmove number
var s = " "
2023-11-01 19:07:09 +01:00
while index < = fen . high ( ) :
s . add ( fen [ index ] )
2023-03-18 18:14:30 +01:00
inc ( index )
2023-10-18 10:45:54 +02:00
result . position . fullMoveCount = parseInt ( s ) . int8
2023-03-18 18:14:30 +01:00
else :
2023-11-01 19:07:09 +01:00
raise newException ( ValueError , " invalid FEN: too many fields in FEN string " )
2023-03-18 18:14:30 +01:00
inc ( index )
2023-10-21 18:19:41 +02:00
result . updateAttackedSquares ( )
2023-10-30 17:46:06 +01:00
if result . inCheck ( result . getActiveColor ( ) . opposite ) :
2023-11-01 19:07:09 +01:00
# Opponent king cannot be captured on the next move
2023-10-30 17:46:06 +01:00
raise newException ( ValueError , " invalid position: opponent king can be captured " )
2024-04-15 12:04:50 +02:00
if result . position . bitboards . white . king = = Bitboard ( 0 ) or result . position . bitboards . 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-15 12:04:50 +02:00
return self . position . bitboards . white . pawns . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . white . bishops . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . white . knights . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . white . rooks . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . white . queens . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of King :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . 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-15 12:04:50 +02:00
return self . position . bitboards . black . pawns . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . black . bishops . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . black . knights . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . black . rooks . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . black . queens . uint64 . countSetBits ( )
2023-10-15 16:53:44 +02:00
of King :
2024-04-15 12:04:50 +02:00
return self . position . bitboards . 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 )
2023-10-28 02:32:50 +02:00
func rankToColumn ( rank : int ) : int8 {. inline . } =
2023-10-15 16:53:44 +02:00
## Converts a chess rank (1-indexed)
## into a 0-indexed column value for our
## board. This converter is necessary because
## chess positions are indexed differently with
## respect to our internal representation
2023-10-18 10:45:54 +02:00
const indeces : array [ 8 , int8 ] = [ 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 ]
2023-10-15 16:53:44 +02:00
return indeces [ rank - 1 ]
2024-04-10 13:45:29 +02:00
func rowToFile ( row : int ) : int8 {. inline . } =
2023-10-28 02:32:50 +02:00
## Converts a row into our grid into
2024-04-08 20:28:31 +02:00
## a chess file
2024-04-10 13:45:29 +02:00
const indeces : array [ 8 , int8 ] = [ 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 ]
2023-10-17 17:27:33 +02:00
return indeces [ row ]
2024-04-15 12:45:47 +02:00
func algebraicToSquare * ( s : string ) : Square =
2024-04-15 12:04:50 +02:00
## Converts a square square from algebraic
2023-10-15 16:53:44 +02:00
## notation to its corresponding row and column
## in the chess grid (0 indexed)
if len ( s ) ! = 2 :
raise newException ( ValueError , " algebraic position must be of length 2 " )
var s = s . toLowerAscii ( )
if s [ 0 ] notin ' a ' .. ' h ' :
raise newException ( ValueError , & " algebraic position has invalid first character ( ' {s[0]} ' ) " )
if s [ 1 ] notin ' 1 ' .. ' 8 ' :
raise newException ( ValueError , & " algebraic position has invalid second character ( ' {s[1]} ' ) " )
2023-10-18 10:45:54 +02:00
let rank = int8 ( uint8 ( s [ 0 ] ) - uint8 ( ' a ' ) )
2023-10-17 17:27:33 +02:00
# Convert the file character to a number
2023-10-18 10:45:54 +02:00
let file = rankToColumn ( int8 ( uint8 ( s [ 1 ] ) - uint8 ( ' 0 ' ) ) )
2023-10-17 17:27:33 +02:00
return ( file , rank )
2023-10-15 16:53:44 +02:00
2024-04-15 12:04:50 +02:00
func squareToAlgebraic * ( square : Square ) : string {. inline . } =
2024-04-15 12:45:47 +02:00
## Converts a square from our internal rank/file
2023-10-17 16:38:43 +02:00
## notation to a square in algebraic notation
2024-04-15 12:04:50 +02:00
return & " {char(uint8(square.file) + uint8( ' a ' ))}{rowToFile(square.rank)} "
2023-10-17 16:38:43 +02:00
2024-04-15 12:04:50 +02:00
func getPiece * ( self : ChessBoard , square : Square ) : Piece {. inline . } =
## Gets the piece at the given square
return self . grid [ square . rank , square . file ]
2023-10-21 18:19:41 +02:00
2023-10-28 02:32:50 +02:00
func 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-15 12:04:50 +02:00
return self . getPiece ( square . algebraicToSquare ( ) )
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 :
color = self . getActiveColor ( )
case color :
of White :
return self . position . pieces . white . king
of Black :
return self . position . pieces . black . king
else :
discard
2023-10-17 22:16:01 +02:00
proc inCheck * ( self : ChessBoard , color : PieceColor = None ) : bool =
## Returns whether the given color's
## king is in check. If the color is
## set to None, checks are checked
## for the active color's king
2023-10-20 02:23:07 +02:00
var color = color
if color = = None :
color = self . getActiveColor ( )
2023-10-17 22:16:01 +02:00
case color :
of White :
2023-10-28 02:32:50 +02:00
result = self . isAttacked ( self . position . pieces . white . king , Black )
of Black :
result = self . isAttacked ( self . position . pieces . black . king , White )
else :
# Unreachable
discard
2023-10-17 22:16:01 +02:00
2024-04-13 21:23:12 +02:00
proc inDoubleCheck * ( self : ChessBoard , color : PieceColor = None ) : bool =
## Returns whether the given color's
## king is in double check. If the color is
## set to None, checks are checked
## for the active color's king
var color = color
if color = = None :
color = self . getActiveColor ( )
case color :
of White :
result = self . getAttackers ( self . position . pieces . white . king , Black ) . len ( ) > 1
of Black :
result = self . getAttackers ( self . position . pieces . black . king , White ) . len ( ) > 1
else :
# Unreachable
discard
2023-10-17 22:16:01 +02:00
proc canCastle * ( self : ChessBoard , color : PieceColor = None ) : tuple [ queen , king : bool ] {. inline . } =
## Returns the sides on which castling is allowed
2023-10-28 02:32:50 +02:00
## for the given color. If the color is None, the
2023-10-17 22:16:01 +02:00
## currently active color is used
var color = color
if color = = None :
color = self . getActiveColor ( )
2023-11-01 19:07:09 +01:00
# Check if castling rights are still available for moving side
2023-10-17 22:16:01 +02:00
case color :
of White :
result . king = self . position . castlingAvailable . white . king
result . queen = self . position . castlingAvailable . white . queen
of Black :
result . king = self . position . castlingAvailable . black . king
result . queen = self . position . castlingAvailable . black . queen
of None :
# Unreachable
discard
2024-04-12 17:03:45 +02:00
2024-04-08 20:28:31 +02:00
# Some of these checks may seem redundant, but we
# perform them because they're less expensive
# King is not on its starting square
2024-04-15 12:45:47 +02:00
if self . getKing ( color ) ! = getKingStartingSquare ( color ) :
2024-04-08 20:28:31 +02:00
return ( false , false )
2023-10-31 23:06:27 +01:00
if self . inCheck ( color ) :
# King can not castle out of check
return ( false , false )
2023-10-23 18:02:31 +02:00
if result . king or result . queen :
var
2024-04-15 12:04:50 +02:00
square : Square
queenSide : Square
kingSide : Square
2023-11-01 19:07:09 +01:00
# If the path between the king and rook on a given side is blocked, or any of the
# squares where the king would move to are attacked by the opponent, then castling
# is temporarily prohibited on that side
2023-10-23 18:02:31 +02:00
case color :
of White :
2024-04-15 12:04:50 +02:00
square = self . position . pieces . white . king
2023-10-23 18:02:31 +02:00
queenSide = color . leftSide ( )
kingSide = color . rightSide ( )
of Black :
2024-04-15 12:04:50 +02:00
square = self . position . pieces . black . king
2023-10-23 18:02:31 +02:00
queenSide = color . rightSide ( )
kingSide = color . leftSide ( )
of None :
# Unreachable
discard
2024-04-09 19:55:08 +02:00
# We only need to check for attacked squares up until
# the point where the king arrives on the target castling
# square, but we _also_ need to make sure the path is free
# of obstacles for the rook to move past the king. This variable
# becomes false once the king has arrived on its target square so
# that we don't prevent castling when it would otherwise be allowed
# (for an example see r3k2r/p1pNqpb1/bn2pnp1/3P4/1p2P3/2N2Q1p/PPPBBPPP/R3K2R b KQkq - 0 1)
var checkAttacks = true
2023-10-17 22:16:01 +02:00
2023-10-23 18:02:31 +02:00
if result . king :
# Short castle
2023-10-25 22:41:04 +02:00
var
2024-04-15 12:04:50 +02:00
currentSquare = square
2023-10-25 22:41:04 +02:00
otherPiece : Piece
2023-11-01 19:07:09 +01:00
while true :
2024-04-15 12:04:50 +02:00
currentSquare = currentSquare + kingSide
2024-04-09 17:46:30 +02:00
2024-04-15 12:04:50 +02:00
if currentSquare = = color . kingSideRook ( ) :
2023-11-01 19:07:09 +01:00
break
2024-04-09 17:46:30 +02:00
2024-04-15 12:04:50 +02:00
otherPiece = self . grid [ currentSquare . rank , currentSquare . file ]
2023-10-25 22:41:04 +02:00
2023-11-01 19:07:09 +01:00
if otherPiece . color ! = None :
result . king = false
break
2024-04-09 19:55:08 +02:00
2024-04-15 12:04:50 +02:00
if checkAttacks and self . isAttacked ( currentSquare , color . opposite ( ) ) :
2023-10-23 18:02:31 +02:00
result . king = false
break
2024-04-09 19:55:08 +02:00
# King has arrived at the target square: we no longer
# need to check whether subsequent squares are free from
# attacks
2024-04-15 12:04:50 +02:00
if currentSquare = = shortCastleKing ( ) + square :
2024-04-09 19:55:08 +02:00
checkAttacks = false
2024-04-09 17:46:30 +02:00
2023-10-23 18:02:31 +02:00
if result . queen :
2024-04-09 19:55:08 +02:00
checkAttacks = true
2023-10-23 18:02:31 +02:00
# Long castle
2023-10-25 22:41:04 +02:00
var
2024-04-15 12:04:50 +02:00
currentSquare = square
2023-10-25 22:41:04 +02:00
otherPiece : Piece
2023-11-01 19:07:09 +01:00
while true :
2024-04-15 12:04:50 +02:00
currentSquare = currentSquare + queenSide
2024-04-09 17:46:30 +02:00
2024-04-15 12:04:50 +02:00
if currentSquare = = color . queenSideRook ( ) :
2023-11-01 19:07:09 +01:00
break
2024-04-09 17:46:30 +02:00
2024-04-15 12:04:50 +02:00
otherPiece = self . grid [ currentSquare . rank , currentSquare . file ]
2023-10-25 22:41:04 +02:00
2023-11-01 19:07:09 +01:00
if otherPiece . color ! = None :
result . queen = false
break
2024-04-09 19:55:08 +02:00
2024-04-15 12:04:50 +02:00
if checkAttacks and self . isAttacked ( currentSquare , color . opposite ( ) ) :
2023-10-28 02:32:50 +02:00
result . queen = false
2023-10-23 18:02:31 +02:00
break
2023-10-28 02:32:50 +02:00
2024-04-15 12:04:50 +02:00
if currentSquare = = longCastleKing ( ) + square :
2024-04-09 19:55:08 +02:00
checkAttacks = false
2023-10-28 02:32:50 +02:00
2024-04-15 12:04:50 +02:00
proc getCheckResolutions ( self : ChessBoard , color : PieceColor ) : seq [ Square ] =
2023-10-28 02:32:50 +02:00
## Returns the squares that need to be covered to
2023-11-01 19:07:09 +01:00
## resolve the current check (including capturing
2023-10-28 02:32:50 +02:00
## the checking piece). In case of double check, an
2024-04-12 17:03:45 +02:00
## empty list is returned (as the king must move).
## Note that this function does not handle the special
## case of a friendly pawn being able to capture an enemy
## pawn that is checking our friendly king via en passant:
## that is handled internally by generatePawnMoves
2024-04-15 12:04:50 +02:00
var king : Square
2023-10-28 02:32:50 +02:00
case color :
of White :
king = self . position . pieces . white . king
of Black :
king = self . position . pieces . black . king
else :
2023-11-01 19:07:09 +01:00
return
2023-10-28 02:32:50 +02:00
2024-04-15 12:04:50 +02:00
let attackers : seq [ Square ] = self . getAttackers ( king , color . opposite ( ) )
2023-10-28 02:32:50 +02:00
if attackers . len ( ) > 1 :
# Double checks require to move the king
return @ [ ]
let
attacker = attackers [ 0 ]
2024-04-15 12:04:50 +02:00
attackerPiece = self . grid [ attacker ]
2024-04-10 13:45:29 +02:00
var attack = self . getAttackFor ( attacker , king )
2023-10-28 02:32:50 +02:00
# Capturing the piece resolves the check
result . add ( attacker )
2024-04-12 17:03:45 +02:00
2023-10-28 02:32:50 +02:00
# Blocking the attack is also a viable strategy
# (unless the check is from a knight or a pawn,
# in which case either the king has to move or
2023-11-01 19:07:09 +01:00
# that piece has to be captured, but this is
# already implicitly handled by the loop below)
2024-04-15 12:04:50 +02:00
var square = attacker
while square ! = king :
square = square + attack . direction
if not square . isValid ( ) :
2023-11-01 19:07:09 +01:00
break
2024-04-15 12:04:50 +02:00
result . add ( square )
2023-10-30 14:46:27 +01:00
2024-04-15 12:04:50 +02:00
proc generatePawnMoves ( self : ChessBoard , square : Square ) : seq [ Move ] =
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
2023-10-16 14:55:43 +02:00
var
2024-04-15 12:04:50 +02:00
piece = self . grid [ square . rank , square . file ]
directions : seq [ Square ] = @ [ ]
2024-04-12 17:03:45 +02:00
assert piece . kind = = Pawn , & " generatePawnMoves called on a {piece.kind} "
2023-10-16 22:14:58 +02:00
# Pawns can move forward one square
2024-04-15 12:04:50 +02:00
let forward = square + piece . color . topSide ( )
2023-10-20 02:23:07 +02:00
# Only if the square is empty though
2024-04-09 19:55:08 +02:00
if forward . isValid ( ) and self . grid [ forward ] . color = = None :
directions . add ( piece . color . topSide ( ) )
2023-10-16 22:14:58 +02:00
# If the pawn is on its first rank, it can push two squares
2024-04-15 12:04:50 +02:00
if square . rank = = piece . getStartRank ( ) :
let double = square + piece . color . doublePush ( )
2023-10-23 18:02:31 +02:00
# Check that both squares are empty
2024-04-09 19:55:08 +02:00
if double . isValid ( ) and self . grid [ forward ] . color = = None and self . grid [ double ] . color = = None :
directions . add ( piece . color . doublePush ( ) )
2024-04-12 17:03:45 +02:00
let
enPassantTarget = self . getEnPassantTarget ( )
enPassantPawn = enPassantTarget + piece . color . opposite ( ) . topSide ( )
topLeft = piece . color . topLeftDiagonal ( )
topRight = piece . color . topRightDiagonal ( )
var enPassantLegal = false
2024-04-09 17:46:30 +02:00
# They can also move one square on either of their
# forward diagonals, but only for captures and en passant
2024-04-12 17:03:45 +02:00
for diagonal in [ topRight , topLeft ] :
2024-04-15 12:04:50 +02:00
let target = square + diagonal
2024-04-09 19:55:08 +02:00
if target . isValid ( ) :
let otherPiece = self . grid [ target ]
2024-04-12 17:03:45 +02:00
if target = = enPassantTarget and self . grid [ enPassantPawn ] . color = = piece . color . opposite ( ) :
2024-04-12 16:05:01 +02:00
# En passant may be possible
let targetPawn = self . grid [ enPassantPawn ]
2024-04-12 17:03:45 +02:00
# Simulate the move and see if the king ends up in check
2024-04-12 16:05:01 +02:00
self . removePiece ( enPassantPawn , attack = false )
2024-04-15 12:04:50 +02:00
self . removePiece ( square , attack = false )
2024-04-12 17:03:45 +02:00
self . spawnPiece ( target , piece )
2024-04-12 16:05:01 +02:00
self . updateAttackedSquares ( )
if not self . inCheck ( piece . color ) :
# King is not in check after en passant: move is legal
2024-04-09 19:55:08 +02:00
directions . add ( diagonal )
2024-04-12 17:03:45 +02:00
enPassantLegal = true
2024-04-12 16:05:01 +02:00
# Reset what we just did and reupdate the attack metadata
2024-04-12 17:03:45 +02:00
self . removePiece ( target , attack = false )
2024-04-15 12:04:50 +02:00
self . spawnPiece ( square , piece )
2024-04-12 16:05:01 +02:00
self . spawnPiece ( enPassantPawn , targetPawn )
self . updateAttackedSquares ( )
2024-04-09 19:55:08 +02:00
elif otherPiece . color = = piece . color . opposite ( ) and otherPiece . kind ! = King : # Can't capture the king!
2024-04-12 16:05:01 +02:00
# A capture may be possible
2024-04-09 19:55:08 +02:00
directions . add ( diagonal )
2023-10-28 02:32:50 +02:00
# Check for pins
2024-04-15 12:04:50 +02:00
let pinned = self . getPinnedDirections ( square )
2024-04-08 20:28:31 +02:00
if pinned . len ( ) > 0 :
2024-04-15 12:04:50 +02:00
var newDirections : seq [ Square ] = @ [ ]
2024-04-09 19:55:08 +02:00
for direction in directions :
if direction in pinned :
newDirections . add ( direction )
directions = newDirections
2023-10-28 02:32:50 +02:00
let checked = self . inCheck ( )
2024-04-12 17:03:45 +02:00
var resolutions = if not checked : @ [ ] else : self . getCheckResolutions ( piece . color )
# If the check comes from a pawn and en passant is legal and would capture it,
# we add that to the list of possible check resolutions
if checked and enPassantLegal :
let attackingPawn = self . getAttackFor ( enPassantPawn , self . getKing ( piece . color ) )
if attackingPawn . source = = enPassantPawn :
resolutions . add ( enPassantTarget )
2023-10-23 18:02:31 +02:00
var targetPiece : Piece
2024-04-09 19:55:08 +02:00
for direction in directions :
2024-04-15 12:04:50 +02:00
let target = square + direction
2023-10-28 02:32:50 +02:00
if checked and target notin resolutions :
continue
2024-04-09 19:55:08 +02:00
targetPiece = self . grid [ target ]
2024-04-08 20:28:31 +02:00
var flags : uint16 = Default . uint16
if targetPiece . color ! = None :
flags = flags or Capture . uint16
2024-04-15 12:04:50 +02:00
elif abs ( square . rank - target . rank ) = = 2 :
2024-04-08 20:28:31 +02:00
flags = flags or DoublePush . uint16
elif target = = self . getEnPassantTarget ( ) :
flags = flags or EnPassant . uint16
2024-04-15 12:04:50 +02:00
if target . rank = = piece . color . getLastRank ( ) :
2023-10-20 02:23:07 +02:00
# Pawn reached the other side of the board: generate all potential piece promotions
2023-10-16 15:25:48 +02:00
for promotionType in [ PromoteToKnight , PromoteToBishop , PromoteToRook , PromoteToQueen ] :
2024-04-15 12:04:50 +02:00
result . add ( Move ( startSquare : square , targetSquare : target , flags : promotionType . uint16 or flags ) )
2023-10-16 22:14:58 +02:00
continue
2024-04-15 12:04:50 +02:00
result . add ( Move ( startSquare : square , targetSquare : target , flags : flags ) )
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
let piece = self . grid [ square . rank , square . file ]
2024-04-12 17:03:45 +02:00
assert piece . kind in [ Bishop , Rook , Queen ] , & " generateSlidingMoves called on a {piece.kind} "
2024-04-15 12:04:50 +02:00
var directions : seq [ Square ] = @ [ ]
2023-10-25 22:41:04 +02:00
2023-10-16 23:02:58 +02:00
# Only check in the right directions for the chosen piece
if piece . kind in [ Bishop , Queen ] :
2023-10-17 22:16:01 +02:00
directions . add ( piece . color . topLeftDiagonal ( ) )
directions . add ( piece . color . topRightDiagonal ( ) )
directions . add ( piece . color . bottomLeftDiagonal ( ) )
directions . add ( piece . color . bottomRightDiagonal ( ) )
2023-10-16 23:02:58 +02:00
if piece . kind in [ Queen , Rook ] :
2023-10-17 22:16:01 +02:00
directions . add ( piece . color . topSide ( ) )
directions . add ( piece . color . bottomSide ( ) )
directions . add ( piece . color . rightSide ( ) )
directions . add ( piece . color . leftSide ( ) )
2024-04-15 12:04:50 +02:00
let pinned = self . getPinnedDirections ( square )
2023-10-25 22:41:04 +02:00
if pinned . len ( ) > 0 :
2024-04-15 12:04:50 +02:00
var newDirections : seq [ Square ] = @ [ ]
2024-04-08 20:28:31 +02:00
for direction in directions :
if direction in pinned :
newDirections . add ( direction )
directions = newDirections
2024-04-12 16:05:01 +02:00
let
checked = self . inCheck ( )
resolutions = if not checked : @ [ ] else : self . getCheckResolutions ( piece . color )
2023-10-16 22:14:58 +02:00
for direction in directions :
# Slide in this direction as long as it's possible
var
2024-04-15 12:04:50 +02:00
square : Square = square
2023-10-16 22:14:58 +02:00
otherPiece : Piece
while true :
square = square + direction
# End of board reached
if not square . isValid ( ) :
break
2024-04-15 12:04:50 +02:00
otherPiece = self . grid [ square ]
2023-10-16 22:14:58 +02:00
# A friendly piece is in the way
if otherPiece . color = = piece . color :
break
2023-10-28 02:32:50 +02:00
if checked and square notin resolutions :
2024-04-10 13:45:29 +02:00
# We don't always break out of the loop because
2024-04-08 20:28:31 +02:00
# we might resolve the check later
2024-04-10 13:45:29 +02:00
if otherPiece . color = = None :
# We can still move in this direction, so maybe
# the check can be resolved later
continue
else :
# Our movement is blocked, switch to next direction
break
2023-10-17 10:31:38 +02:00
if otherPiece . color = = piece . color . opposite :
2023-10-17 12:08:07 +02:00
# Target square contains an enemy piece: capture
# it and stop going any further
2023-10-23 18:02:31 +02:00
if otherPiece . kind ! = King :
# Can't capture the king
2024-04-15 12:04:50 +02:00
result . add ( Move ( startSquare : square , targetSquare : square , flags : Capture . uint16 ) )
2023-10-17 10:31:38 +02:00
break
2024-04-10 13:45:29 +02:00
# Target square is empty, keep going
2024-04-15 12:04:50 +02:00
result . add ( Move ( startSquare : square , targetSquare : 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:08:07 +02:00
var
2024-04-15 12:04:50 +02:00
piece = self . grid [ square . rank , square . file ]
2024-04-12 17:03:45 +02:00
assert piece . kind = = King , & " generateKingMoves called on a {piece.kind} "
2024-04-15 12:04:50 +02:00
var directions : seq [ Square ] = @ [ piece . color . topLeftDiagonal ( ) ,
2023-10-17 22:16:01 +02:00
piece . color . topRightDiagonal ( ) ,
piece . color . bottomRightDiagonal ( ) ,
piece . color . bottomLeftDiagonal ( ) ,
piece . color . topSide ( ) ,
piece . color . bottomSide ( ) ,
piece . color . leftSide ( ) ,
piece . color . rightSide ( ) ]
# Castling
2023-10-23 18:02:31 +02:00
let canCastle = self . canCastle ( piece . color )
2023-10-17 22:16:01 +02:00
if canCastle . queen :
2023-10-20 02:23:07 +02:00
directions . add ( longCastleKing ( ) )
2023-10-17 22:16:01 +02:00
if canCastle . king :
2023-10-20 02:23:07 +02:00
directions . add ( shortCastleKing ( ) )
2023-10-17 22:16:01 +02:00
var flag = Default
2023-10-17 12:08:07 +02:00
for direction in directions :
2023-10-23 18:02:31 +02:00
# Step in this direction once
2024-04-15 12:04:50 +02:00
let square : Square = square + direction
2023-10-23 18:02:31 +02:00
# End of board reached
if not square . isValid ( ) :
continue
2023-10-28 02:32:50 +02:00
if self . isAttacked ( square , piece . color . opposite ( ) ) :
continue
2023-10-20 02:23:07 +02:00
if direction = = longCastleKing ( ) :
2023-10-17 22:16:01 +02:00
flag = CastleLong
2023-10-20 02:23:07 +02:00
elif direction = = shortCastleKing ( ) :
2023-10-17 22:16:01 +02:00
flag = CastleShort
2023-10-20 02:23:07 +02:00
else :
flag = Default
2024-04-15 12:04:50 +02:00
let otherPiece = self . grid [ square ]
2024-04-09 17:46:30 +02:00
if otherPiece . color = = piece . color . opposite ( ) :
2023-10-20 02:23:07 +02:00
flag = Capture
2023-10-28 02:32:50 +02:00
# A friendly piece is in the way, move onto the next direction
if otherPiece . color = = piece . color :
2023-10-17 12:08:07 +02:00
continue
2023-10-17 22:16:01 +02:00
# Target square is empty or contains an enemy piece:
# All good for us!
2024-04-15 12:04:50 +02:00
result . add ( Move ( startSquare : square , targetSquare : square , flags : flag . uint16 ) )
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
2023-10-17 12:42:15 +02:00
var
2024-04-15 12:04:50 +02:00
piece = self . grid [ square . rank , square . file ]
2024-04-12 17:03:45 +02:00
assert piece . kind = = Knight , & " generateKnightMoves called on a {piece.kind} "
2024-04-15 12:04:50 +02:00
var directions : seq [ Square ] = @ [ piece . color . bottomLeftKnightMove ( ) ,
2023-10-17 22:16:01 +02:00
piece . color . bottomRightKnightMove ( ) ,
piece . color . topLeftKnightMove ( ) ,
piece . color . topRightKnightMove ( ) ,
piece . color . bottomLeftKnightMove ( long = false ) ,
piece . color . bottomRightKnightMove ( long = false ) ,
piece . color . topLeftKnightMove ( long = false ) ,
piece . color . topRightKnightMove ( long = false ) ]
2024-04-15 12:04:50 +02:00
let pinned = self . getPinnedDirections ( square )
2023-10-25 22:41:04 +02:00
if pinned . len ( ) > 0 :
# Knight is pinned: can't move!
return @ [ ]
2023-10-28 02:32:50 +02:00
let checked = self . inCheck ( )
let resolutions = if not checked : @ [ ] else : self . getCheckResolutions ( piece . color )
2023-10-17 12:42:15 +02:00
for direction in directions :
# Jump to this square
2024-04-15 12:04:50 +02:00
let square : Square = square + direction
2023-10-17 12:42:15 +02:00
# End of board reached
if not square . isValid ( ) :
continue
2024-04-15 12:04:50 +02:00
let otherPiece = self . grid [ square ]
2023-10-23 18:02:31 +02:00
# A friendly piece or the opponent king is is in the way
if otherPiece . color = = piece . color or otherPiece . kind = = King :
2023-10-17 12:42:15 +02:00
continue
2023-10-28 02:32:50 +02:00
if checked and square notin resolutions :
continue
2023-10-23 18:02:31 +02:00
if otherPiece . color ! = None :
2023-10-17 12:42:15 +02:00
# Target square contains an enemy piece: capture
# it
2024-04-15 12:04:50 +02:00
result . add ( Move ( startSquare : square , targetSquare : square , flags : Capture . uint16 ) )
2023-10-23 18:02:31 +02:00
else :
# Target square is empty
2024-04-15 12:04:50 +02:00
result . add ( Move ( startSquare : square , targetSquare : square ) )
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
for bishop in self . position . pieces . black . bishops :
if bishop . isLightSquare ( ) :
lightSquare + = 1
else :
darkSquare + = 1
for bishop in self . position . pieces . white . bishops :
if bishop . isLightSquare ( ) :
lightSquare + = 1
else :
darkSquare + = 1
if darkSquare > = 1 and lightSquare > = 1 :
return false
return true
2024-04-15 12:04:50 +02:00
proc generateMoves ( self : ChessBoard , square : Square ) : seq [ Move ] =
2023-10-23 18:02:31 +02:00
## Returns the list of possible legal chess moves for the
2024-04-15 12:04:50 +02:00
## piece in the given square
if self . position . halfMoveClock > = 100 :
2024-04-13 19:59:54 +02:00
# Draw by 50-move rule
return @ [ ]
# TODO: Check for draw by insufficient material
#[
if self . checkInsufficientMaterial ( ) :
return @ [ ]
] #
2024-04-15 12:04:50 +02:00
let piece = self . grid [ square . rank , square . file ]
2023-10-16 15:25:48 +02:00
case piece . kind :
of Queen , Bishop , Rook :
2024-04-15 12:04:50 +02:00
return self . generateSlidingMoves ( square )
2023-10-16 15:25:48 +02:00
of Pawn :
2024-04-15 12:04:50 +02:00
return self . generatePawnMoves ( square )
2023-10-16 22:14:58 +02:00
of King :
2024-04-15 12:04:50 +02:00
return self . generateKingMoves ( square )
2023-10-17 12:42:15 +02:00
of Knight :
2024-04-15 12:04:50 +02:00
return self . generateKnightMoves ( square )
2023-10-15 16:53:44 +02:00
else :
2023-10-17 12:08:07 +02:00
return @ [ ]
2023-10-15 16:53:44 +02:00
2024-04-15 12:04:50 +02:00
2023-10-18 10:45:54 +02:00
proc generateAllMoves * ( self : ChessBoard ) : seq [ Move ] =
2023-10-23 18:02:31 +02:00
## Returns the list of all possible legal moves
2023-10-18 10:45:54 +02:00
## in the current position
2023-11-13 09:52:37 +01:00
for i in 0 .. 7 :
for j in 0 .. 7 :
2023-10-21 18:19:41 +02:00
if self . grid [ i , j ] . color = = self . getActiveColor ( ) :
for move in self . generateMoves ( ( int8 ( i ) , int8 ( j ) ) ) :
result . add ( move )
2023-10-28 02:32:50 +02:00
2023-10-18 10:45:54 +02:00
2024-04-15 12:04:50 +02:00
proc isAttacked * ( self : ChessBoard , square : Square , color : PieceColor = None ) : bool =
## Returns whether the given square is attacked
2023-10-25 22:41:04 +02:00
## by the given color
2023-10-23 18:02:31 +02:00
var color = color
if color = = None :
2023-10-25 22:41:04 +02:00
color = self . getActiveColor ( ) . opposite ( )
2023-10-23 18:02:31 +02:00
case color :
2023-10-17 10:31:38 +02:00
of Black :
2023-10-21 18:19:41 +02:00
for attack in self . position . attacked . black :
2024-04-15 12:04:50 +02:00
if attack . target = = square :
2023-10-25 22:41:04 +02:00
return true
of White :
for attack in self . position . attacked . white :
2024-04-15 12:04:50 +02:00
if attack . target = = square :
2023-10-17 10:31:38 +02:00
return true
of None :
2023-10-23 18:02:31 +02:00
discard
2023-10-17 10:31:38 +02:00
2024-04-15 12:04:50 +02:00
proc getAttackers * ( self : ChessBoard , square : Square , color : PieceColor ) : seq [ Square ] =
2023-10-28 02:32:50 +02:00
## Returns all the attackers of the given color
## for the given square
case color :
of Black :
for attack in self . position . attacked . black :
2024-04-15 12:04:50 +02:00
if attack . target = = square :
2023-10-28 02:32:50 +02:00
result . add ( attack . source )
of White :
for attack in self . position . attacked . white :
2024-04-15 12:04:50 +02:00
if attack . target = = square :
2023-10-28 02:32:50 +02:00
result . add ( attack . source )
of None :
discard
2024-04-15 12:04:50 +02:00
proc getAttacks * ( self : ChessBoard , square : Square ) : Attacked =
2023-10-28 02:32:50 +02:00
## Returns all the squares attacked by the piece in the given
2024-04-15 12:04:50 +02:00
## square
let piece = self . grid [ square . rank , square . file ]
2023-10-28 02:32:50 +02:00
case piece . color :
of Black :
for attack in self . position . attacked . black :
2024-04-15 12:04:50 +02:00
if attack . source = = square :
2023-10-28 02:32:50 +02:00
result . add ( attack )
of White :
for attack in self . position . attacked . white :
2024-04-15 12:04:50 +02:00
if attack . source = = square :
2023-10-28 02:32:50 +02:00
result . add ( attack )
of None :
discard
2024-04-15 12:04:50 +02:00
proc getAttackFor * ( self : ChessBoard , source , target : Square ) : tuple [ source , target , direction : Square ] =
2024-04-12 17:03:45 +02:00
## Returns the first attack from the given source to the
## given target square
2024-04-15 12:04:50 +02:00
result = ( emptySquare ( ) , emptySquare ( ) , emptySquare ( ) )
let piece = self . grid [ source ]
2023-10-28 02:32:50 +02:00
case piece . color :
of Black :
for attack in self . position . attacked . black :
if attack . target = = target and attack . source = = source :
return attack
of White :
for attack in self . position . attacked . white :
if attack . target = = target and attack . source = = source :
return attack
of None :
discard
2023-10-17 10:31:38 +02:00
proc isAttacked * ( self : ChessBoard , square : string ) : bool =
## Returns whether the given square is attacked
2023-10-25 22:41:04 +02:00
## by the current
2024-04-15 12:04:50 +02:00
return self . isAttacked ( square . algebraicToSquare ( ) )
2023-10-17 10:31:38 +02:00
2024-04-15 12:04:50 +02:00
func addAttack ( self : ChessBoard , attack : tuple [ source , target , direction : Square ] , color : PieceColor ) {. inline . } =
2023-10-25 22:41:04 +02:00
if attack . source . isValid ( ) and attack . target . isValid ( ) :
2023-10-23 18:02:31 +02:00
case color :
of White :
self . position . attacked . white . add ( attack )
of Black :
self . position . attacked . black . add ( attack )
else :
discard
2024-04-15 12:04:50 +02:00
proc getPinnedDirections ( self : ChessBoard , square : Square ) : seq [ Square ] =
2024-04-12 17:03:45 +02:00
## Returns all the directions along which the piece in the given
2024-04-15 12:04:50 +02:00
## square is pinned. If the result is non-empty, the piece at
## the given square is only allowed to move along the directions
2024-04-12 17:03:45 +02:00
## returned by this function
2024-04-15 12:04:50 +02:00
let piece = self . grid [ square . rank , square . file ]
2023-10-25 22:41:04 +02:00
case piece . color :
of None :
discard
of White :
for pin in self . position . pinned . black :
2024-04-15 12:04:50 +02:00
if pin . target = = square :
2023-10-25 22:41:04 +02:00
result . add ( pin . direction )
of Black :
for pin in self . position . pinned . white :
2024-04-15 12:04:50 +02:00
if pin . target = = square :
2023-10-25 22:41:04 +02:00
result . add ( pin . direction )
2023-10-28 02:32:50 +02:00
proc updatePawnAttacks ( self : ChessBoard ) =
2023-10-21 18:19:41 +02:00
## Internal helper of updateAttackedSquares
2024-04-15 12:04:50 +02:00
for sq in self . position . pieces . white . pawns :
2023-10-21 18:19:41 +02:00
# Pawns are special in how they capture (i.e. the
2024-04-12 17:03:45 +02:00
# squares they can regularly move to do not match
# the squares they can capture on. Sneaky fucks)
2024-04-15 12:04:50 +02:00
self . addAttack ( ( sq , sq + White . topRightDiagonal ( ) , White . topRightDiagonal ( ) ) , White )
self . addAttack ( ( sq , sq + White . topLeftDiagonal ( ) , White . topLeftDiagonal ( ) ) , White )
2023-10-21 18:19:41 +02:00
# We do the same thing for black
2024-04-15 12:04:50 +02:00
for sq in self . position . pieces . black . pawns :
self . addAttack ( ( sq , sq + Black . topRightDiagonal ( ) , Black . topRightDiagonal ( ) ) , Black )
self . addAttack ( ( sq , sq + Black . topLeftDiagonal ( ) , Black . topLeftDiagonal ( ) ) , Black )
2023-10-23 18:02:31 +02:00
proc updateKingAttacks ( self : ChessBoard ) =
## Internal helper of updateAttackedSquares
var king = self . position . pieces . white . king
2023-10-28 02:32:50 +02:00
self . addAttack ( ( king , king + White . topRightDiagonal ( ) , White . topRightDiagonal ( ) ) , White )
self . addAttack ( ( king , king + White . topLeftDiagonal ( ) , White . topLeftDiagonal ( ) ) , White )
self . addAttack ( ( king , king + White . bottomLeftDiagonal ( ) , White . bottomLeftDiagonal ( ) ) , White )
self . addAttack ( ( king , king + White . bottomRightDiagonal ( ) , White . bottomRightDiagonal ( ) ) , White )
2024-04-12 17:03:45 +02:00
self . addAttack ( ( king , king + White . leftSide ( ) , White . leftSide ( ) ) , White )
self . addAttack ( ( king , king + White . rightSide ( ) , White . rightSide ( ) ) , White )
self . addAttack ( ( king , king + White . bottomSide ( ) , White . bottomSide ( ) ) , White )
self . addAttack ( ( king , king + White . topSide ( ) , White . topSide ( ) ) , White )
2023-10-23 18:02:31 +02:00
king = self . position . pieces . black . king
2023-10-28 02:32:50 +02:00
self . addAttack ( ( king , king + Black . topRightDiagonal ( ) , Black . topRightDiagonal ( ) ) , Black )
self . addAttack ( ( king , king + Black . topLeftDiagonal ( ) , Black . topLeftDiagonal ( ) ) , Black )
self . addAttack ( ( king , king + Black . bottomLeftDiagonal ( ) , Black . bottomLeftDiagonal ( ) ) , Black )
self . addAttack ( ( king , king + Black . bottomRightDiagonal ( ) , Black . bottomRightDiagonal ( ) ) , Black )
2024-04-12 17:03:45 +02:00
self . addAttack ( ( king , king + Black . leftSide ( ) , Black . leftSide ( ) ) , Black )
self . addAttack ( ( king , king + Black . rightSide ( ) , Black . rightSide ( ) ) , Black )
self . addAttack ( ( king , king + Black . bottomSide ( ) , Black . bottomSide ( ) ) , Black )
self . addAttack ( ( king , king + Black . topSide ( ) , Black . topSide ( ) ) , Black )
2023-10-23 18:02:31 +02:00
proc updateKnightAttacks ( self : ChessBoard ) =
## Internal helper of updateAttackedSquares
for loc in self . position . pieces . white . knights :
2023-10-28 02:32:50 +02:00
self . addAttack ( ( loc , loc + White . topLeftKnightMove ( ) , White . topLeftKnightMove ( ) ) , White )
self . addAttack ( ( loc , loc + White . topRightKnightMove ( ) , White . topRightKnightMove ( ) ) , White )
self . addAttack ( ( loc , loc + White . bottomLeftKnightMove ( ) , White . bottomLeftKnightMove ( ) ) , White )
self . addAttack ( ( loc , loc + White . bottomRightKnightMove ( ) , White . bottomRightKnightMove ( ) ) , White )
self . addAttack ( ( loc , loc + White . topLeftKnightMove ( long = false ) , White . topLeftKnightMove ( long = false ) ) , White )
self . addAttack ( ( loc , loc + White . topRightKnightMove ( long = false ) , White . topRightKnightMove ( long = false ) ) , White )
self . addAttack ( ( loc , loc + White . bottomLeftKnightMove ( long = false ) , White . bottomLeftKnightMove ( long = false ) ) , White )
self . addAttack ( ( loc , loc + White . bottomRightKnightMove ( long = false ) , White . bottomRightKnightMove ( long = false ) ) , White )
2023-10-23 18:02:31 +02:00
for loc in self . position . pieces . black . knights :
2023-10-28 02:32:50 +02:00
self . addAttack ( ( loc , loc + Black . topLeftKnightMove ( ) , Black . topLeftKnightMove ( ) ) , Black )
self . addAttack ( ( loc , loc + Black . topRightKnightMove ( ) , Black . topRightKnightMove ( ) ) , Black )
self . addAttack ( ( loc , loc + Black . bottomLeftKnightMove ( ) , Black . bottomLeftKnightMove ( ) ) , Black )
self . addAttack ( ( loc , loc + Black . bottomRightKnightMove ( ) , Black . bottomRightKnightMove ( ) ) , Black )
self . addAttack ( ( loc , loc + Black . topLeftKnightMove ( long = false ) , Black . topLeftKnightMove ( long = false ) ) , Black )
self . addAttack ( ( loc , loc + Black . topRightKnightMove ( long = false ) , Black . topRightKnightMove ( long = false ) ) , Black )
self . addAttack ( ( loc , loc + Black . bottomLeftKnightMove ( long = false ) , Black . bottomLeftKnightMove ( long = false ) ) , Black )
self . addAttack ( ( loc , loc + Black . bottomRightKnightMove ( long = false ) , Black . bottomRightKnightMove ( long = false ) ) , Black )
2023-10-20 02:23:07 +02:00
2023-10-16 14:55:43 +02:00
2024-04-15 12:04:50 +02:00
proc getSlidingAttacks ( self : ChessBoard , square : Square ) : tuple [ attacks : Attacked , pins : Attacked ] =
2023-10-21 18:19:41 +02:00
## Internal helper of updateSlidingAttacks
var
2024-04-15 12:04:50 +02:00
directions : seq [ Square ] = @ [ ]
let piece = self . grid [ square . rank , square . file ]
2023-10-21 18:19:41 +02:00
if piece . kind in [ Bishop , Queen ] :
directions . add ( piece . color . topLeftDiagonal ( ) )
directions . add ( piece . color . topRightDiagonal ( ) )
directions . add ( piece . color . bottomLeftDiagonal ( ) )
directions . add ( piece . color . bottomRightDiagonal ( ) )
2023-10-25 22:41:04 +02:00
2023-10-21 18:19:41 +02:00
if piece . kind in [ Queen , Rook ] :
directions . add ( piece . color . topSide ( ) )
directions . add ( piece . color . bottomSide ( ) )
directions . add ( piece . color . rightSide ( ) )
directions . add ( piece . color . leftSide ( ) )
2023-10-25 22:41:04 +02:00
2023-10-21 18:19:41 +02:00
for direction in directions :
2023-10-25 22:41:04 +02:00
var
2024-04-15 12:04:50 +02:00
currentSquare = square
2023-10-25 22:41:04 +02:00
otherPiece : Piece
2023-10-21 18:19:41 +02:00
# Slide in this direction as long as it's possible
while true :
2024-04-15 12:04:50 +02:00
currentSquare = currentSquare + direction
2023-10-21 18:19:41 +02:00
# End of board reached
if not square . isValid ( ) :
break
2024-04-15 12:04:50 +02:00
otherPiece = self . grid [ square ]
2023-10-25 22:41:04 +02:00
# Target square is attacked (even if a friendly piece
# is present, because in this case we're defending
# it)
2024-04-15 12:04:50 +02:00
result . attacks . add ( ( square , currentSquare , direction ) )
2023-10-25 22:41:04 +02:00
# Empty square, keep going
if otherPiece . color = = None :
continue
2023-10-30 17:46:06 +01:00
if otherPiece . color = = piece . color . opposite ( ) :
if otherPiece . kind ! = King :
# We found an enemy piece that is not
# the enemy king. We don't break out
# immediately because we first want
2024-04-10 13:45:29 +02:00
# to check if we've pinned it to the king
2023-10-30 17:46:06 +01:00
var
2024-04-15 12:04:50 +02:00
otherSquare : Square = square
2023-10-30 17:46:06 +01:00
behindPiece : Piece
while true :
otherSquare = otherSquare + direction
if not otherSquare . isValid ( ) :
break
2024-04-15 12:04:50 +02:00
behindPiece = self . grid [ otherSquare ]
2023-10-30 17:46:06 +01:00
if behindPiece . color = = None :
continue
if behindPiece . color = = piece . color . opposite and behindPiece . kind = = King :
2023-10-31 23:06:27 +01:00
# The enemy king is behind this enemy piece: pin it along
# this axis in both directions
2024-04-15 12:04:50 +02:00
result . pins . add ( ( square , currentSquare , direction ) )
result . pins . add ( ( square , currentSquare , - direction ) )
if otherPiece . kind = = Pawn and square . rank = = otherPiece . getStartRank ( ) :
2024-04-10 13:45:29 +02:00
# The pinned piece is a pawn which hasn't moved yet:
# we allow it to move two squares as well
2024-04-15 12:04:50 +02:00
if square . file = = square . file :
2024-04-10 13:45:29 +02:00
# The pawn can only push two squares if it's being pinned from the
2024-04-12 17:03:45 +02:00
# top side (relative to the pawn itself)
2024-04-15 12:04:50 +02:00
result . pins . add ( ( square , currentSquare , otherPiece . color . doublePush ( ) ) )
2023-10-30 17:46:06 +01:00
else :
break
else :
# Enemy king is here: ensure it cannot move backwards by
# attacking the square behind it (if one exists and is
# valid)
let target = square + direction
if target . isValid ( ) :
2024-04-15 12:04:50 +02:00
result . attacks . add ( ( square , target , direction ) )
2023-10-25 22:41:04 +02:00
break
2023-10-21 18:19:41 +02:00
proc updateSlidingAttacks ( self : ChessBoard ) =
## Internal helper of updateAttackedSquares
2023-10-28 02:32:50 +02:00
var data : tuple [ attacks : Attacked , pins : Attacked ]
2023-10-17 15:08:46 +02:00
for loc in self . position . pieces . white . bishops :
2023-10-25 22:41:04 +02:00
data = self . getSlidingAttacks ( loc )
self . position . attacked . white . extend ( data . attacks )
self . position . pinned . white . extend ( data . pins )
2023-10-17 15:08:46 +02:00
for loc in self . position . pieces . white . rooks :
2023-10-25 22:41:04 +02:00
data = self . getSlidingAttacks ( loc )
self . position . attacked . white . extend ( data . attacks )
self . position . pinned . white . extend ( data . pins )
2023-10-17 15:08:46 +02:00
for loc in self . position . pieces . white . queens :
2023-10-25 22:41:04 +02:00
data = self . getSlidingAttacks ( loc )
self . position . attacked . white . extend ( data . attacks )
self . position . pinned . white . extend ( data . pins )
for loc in self . position . pieces . black . bishops :
data = self . getSlidingAttacks ( loc )
self . position . attacked . black . extend ( data . attacks )
self . position . pinned . black . extend ( data . pins )
for loc in self . position . pieces . black . rooks :
data = self . getSlidingAttacks ( loc )
self . position . attacked . black . extend ( data . attacks )
self . position . pinned . black . extend ( data . pins )
2023-10-21 18:19:41 +02:00
for loc in self . position . pieces . black . queens :
2023-10-25 22:41:04 +02:00
data = self . getSlidingAttacks ( loc )
self . position . attacked . black . extend ( data . attacks )
self . position . pinned . black . extend ( data . pins )
2023-10-21 18:19:41 +02:00
proc updateAttackedSquares ( self : ChessBoard ) =
## Updates internal metadata about which squares
2023-10-25 22:41:04 +02:00
## are attacked
2023-10-21 18:19:41 +02:00
self . position . attacked . white . setLen ( 0 )
self . position . attacked . black . setLen ( 0 )
2024-04-12 16:05:01 +02:00
self . position . pinned . white . setLen ( 0 )
self . position . pinned . black . setLen ( 0 )
2023-10-21 18:19:41 +02:00
# Pawns
self . updatePawnAttacks ( )
# Sliding pieces
self . updateSlidingAttacks ( )
2023-10-25 22:41:04 +02:00
# Knights
2023-10-23 18:02:31 +02:00
self . updateKnightAttacks ( )
# Kings
self . updateKingAttacks ( )
2023-10-16 14:55:43 +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-15 12:04:50 +02:00
self . position . bitboards . white . pawns . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . bishops . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . knights . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . rooks . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . queens . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of King :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . king . uint64 . clearBit ( square . coordToIndex ( ) )
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-15 12:04:50 +02:00
self . position . bitboards . black . pawns . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . bishops . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . knights . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . rooks . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . queens . uint64 . clearBit ( square . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of King :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . king . uint64 . clearBit ( square . coordToIndex ( ) )
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 :
self . position . bitboards . white . pawns . uint64 . setBit ( square . coordToIndex ( ) )
of Bishop :
self . position . bitboards . white . bishops . uint64 . setBit ( square . coordToIndex ( ) )
of Knight :
self . position . bitboards . white . knights . uint64 . setBit ( square . coordToIndex ( ) )
of Rook :
self . position . bitboards . white . rooks . uint64 . setBit ( square . coordToIndex ( ) )
of Queen :
self . position . bitboards . white . queens . uint64 . setBit ( square . coordToIndex ( ) )
of King :
self . position . bitboards . white . king . uint64 . setBit ( square . coordToIndex ( ) )
else :
discard
of Black :
case piece . kind :
of Pawn :
self . position . bitboards . black . pawns . uint64 . setBit ( square . coordToIndex ( ) )
of Bishop :
self . position . bitboards . black . bishops . uint64 . setBit ( square . coordToIndex ( ) )
of Knight :
self . position . bitboards . black . knights . uint64 . setBit ( square . coordToIndex ( ) )
of Rook :
self . position . bitboards . black . rooks . uint64 . setBit ( square . coordToIndex ( ) )
of Queen :
self . position . bitboards . black . queens . uint64 . setBit ( square . coordToIndex ( ) )
of King :
self . position . bitboards . black . king . uint64 . setBit ( square . coordToIndex ( ) )
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 ]
self . grid [ square ] = emptyPiece ( )
self . removePieceFromBitboard ( square )
2023-10-23 18:02:31 +02:00
if attack :
self . updateAttackedSquares ( )
2023-10-15 16:53:44 +02:00
2024-04-15 12:04:50 +02:00
proc updateMoveBitboards ( self : ChessBoard , move : Move ) =
## 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-15 12:04:50 +02:00
self . position . bitboards . white . pawns . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . white . pawns . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . bishops . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . white . bishops . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . knights . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . white . knights . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . rooks . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . white . rooks . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . queens . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . white . queens . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of King :
2024-04-15 12:04:50 +02:00
self . position . bitboards . white . king . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . white . king . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
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-15 12:04:50 +02:00
self . position . bitboards . black . pawns . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . black . pawns . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Bishop :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . bishops . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . black . bishops . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Knight :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . knights . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . black . knights . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Rook :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . rooks . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . black . rooks . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of Queen :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . queens . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . black . queens . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
2023-10-15 16:53:44 +02:00
of King :
2024-04-15 12:04:50 +02:00
self . position . bitboards . black . king . uint64 . setBit ( move . targetSquare . coordToIndex ( ) )
self . position . bitboards . black . king . uint64 . clearBit ( move . startSquare . coordToIndex ( ) )
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
self . updateMoveBitboards ( move )
2023-10-21 18:19:41 +02:00
# Empty out the starting square
2024-04-15 12:04:50 +02:00
self . grid [ move . startSquare ] = emptyPiece ( )
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
2023-10-20 02:23:07 +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-15 12:04:50 +02:00
enPassantTarget = emptySquare ( )
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 ( ) :
2023-11-01 19:07:09 +01:00
enPassantTarget = move . targetSquare + piece . color . bottomSide ( )
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-15 12:04:50 +02:00
if move . startSquare . rank = = piece . getStartRank ( ) :
if move . startSquare . file = = 0 :
2023-10-21 18:19:41 +02:00
# Queen side
castlingAvailable . white . queen = false
2024-04-15 12:04:50 +02:00
elif move . startSquare . file = = 7 :
2023-10-21 18:19:41 +02:00
# King side
castlingAvailable . white . king = false
of Black :
2024-04-15 12:04:50 +02:00
if move . startSquare . rank = = piece . getStartRank ( ) :
if move . startSquare . file = = 0 :
2023-10-21 18:19:41 +02:00
# Queen side
castlingAvailable . black . queen = false
2024-04-15 12:04:50 +02:00
elif move . startSquare . file = = 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 ,
turn : self . getActiveColor ( ) . opposite ,
castlingAvailable : castlingAvailable ,
pieces : self . position . pieces ,
2024-04-15 12:04:50 +02:00
enPassantSquare : enPassantTarget ,
bitboards : self . position . bitboards
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-08 20:28:31 +02:00
flags : uint16
2023-11-13 09:52:37 +01:00
if move . getCastlingType ( ) = = CastleShort :
2024-04-15 12:04:50 +02:00
square = piece . color . kingSideRook ( )
2023-10-21 18:19:41 +02:00
target = shortCastleRook ( )
2024-04-08 20:28:31 +02:00
flags = flags or CastleShort . uint16
2023-10-21 18:19:41 +02:00
else :
2024-04-15 12:04:50 +02:00
square = piece . color . queenSideRook ( )
2023-10-21 18:19:41 +02:00
target = longCastleRook ( )
2024-04-08 20:28:31 +02:00
flags = flags or CastleLong . uint16
2024-04-15 12:04:50 +02:00
let rook = self . grid [ square . rank , square . file ]
let move = Move ( startSquare : square , targetSquare : square + target , flags : flags )
2023-10-21 18:19:41 +02:00
self . movePiece ( move , 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
self . removePiece ( move . targetSquare + piece . color . bottomSide ( ) , 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-10 13:45:29 +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 :
self . grid [ i ] = emptyPiece ( )
2024-04-15 12:04:50 +02:00
for sq in self . position . pieces . white . pawns :
self . grid [ sq ] = Piece ( color : White , kind : Pawn )
for sq in self . position . pieces . black . pawns :
self . grid [ sq ] = Piece ( color : Black , kind : Pawn )
for sq in self . position . pieces . white . bishops :
self . grid [ sq ] = Piece ( color : White , kind : Bishop )
for sq in self . position . pieces . black . bishops :
self . grid [ sq ] = Piece ( color : Black , kind : Bishop )
for sq in self . position . pieces . white . knights :
self . grid [ sq ] = Piece ( color : White , kind : Knight )
for sq in self . position . pieces . black . knights :
self . grid [ sq ] = Piece ( color : Black , kind : Knight )
for sq in self . position . pieces . white . rooks :
self . grid [ sq ] = Piece ( color : White , kind : Rook )
for sq in self . position . pieces . black . rooks :
self . grid [ sq ] = Piece ( color : Black , kind : Rook )
for sq in self . position . pieces . white . queens :
self . grid [ sq ] = Piece ( color : White , kind : Queen )
for sq in self . position . pieces . black . queens :
self . grid [ sq ] = Piece ( color : Black , kind : Queen )
self . grid [ self . position . pieces . white . king ] = Piece ( color : White , kind : King )
self . grid [ self . position . pieces . black . king ] = Piece ( color : Black , kind : King )
2023-10-23 18:02:31 +02:00
2023-10-31 23:06:27 +01:00
proc undoLastMove * ( self : ChessBoard ) =
2024-04-08 20:28:31 +02:00
if self . positions . len ( ) > 0 :
self . position = self . positions . pop ( )
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
2023-10-23 18:02:31 +02:00
return move in self . generateMoves ( move . startSquare )
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 ) :
2023-10-16 14:55:43 +02:00
return emptyMove ( )
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 & = " - - - - - - - - "
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 :
let piece = self . grid [ 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()} "
2023-10-15 16:53:44 +02:00
result & = & " {rankToColumn(i + 1) + 1} "
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
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 "
let piece = self . grid [ 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 "
result & = & " \x1b [33;1m{rankToColumn(i + 1) + 1} \x1b [0m "
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 :
let piece = self . grid [ 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
result & = ( if self . getActiveColor ( ) = = White : " w " else : " b " )
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-15 12:04:50 +02:00
if self . getEnPassantTarget ( ) = = emptySquare ( ) :
2023-10-28 02:32:50 +02:00
result & = " - "
else :
2024-04-15 12:04:50 +02:00
result & = self . getEnPassantTarget ( ) . squareToAlgebraic ( )
2023-10-28 02:32:50 +02:00
result & = " "
# Halfmove clock
result & = $ self . getHalfMoveCount ( )
result & = " "
# Fullmove number
result & = $ self . getMoveCount ( )
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
2023-10-31 23:06:27 +01:00
let moves = self . generateAllMoves ( )
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-15 12:04:50 +02:00
echo & " {move.startSquare.squareToAlgebraic()}{move.targetSquare.squareToAlgebraic()}{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 :
let canCastle = self . canCastle ( self . getActiveColor ( ) )
2023-10-31 23:06:27 +01:00
echo & " Ply (from root): {self.position.plyFromRoot} "
2024-04-15 12:04:50 +02:00
echo & " Move: {move.startSquare.squareToAlgebraic()}{move.targetSquare.squareToAlgebraic()}, from ({move.startSquare.rank}, {move.startSquare.file}) to ({move.targetSquare.rank}, {move.targetSquare.file}) "
2023-10-28 02:32:50 +02:00
echo & " Turn: {self.getActiveColor()} "
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-15 12:04:50 +02:00
if self . getEnPassantTarget ( ) ! = emptySquare ( ) :
echo self . getEnPassantTarget ( ) . squareToAlgebraic ( )
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 :
2023-10-30 14:46:27 +01:00
let canCastle = self . canCastle ( self . getActiveColor ( ) )
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 )
2023-10-31 23:06:27 +01:00
self . undoLastMove ( )
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-15 12:04:50 +02:00
echo & " {move.startSquare.squareToAlgebraic()}{move.targetSquare.squareToAlgebraic()}{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
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
try :
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
2023-11-13 11:03:54 +01:00
flags : uint16
2023-11-13 09:52:37 +01:00
try :
2024-04-15 12:04:50 +02:00
startSquare = moveString [ 0 .. 1 ] . algebraicToSquare ( )
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-15 12:04:50 +02:00
targetSquare = moveString [ 2 .. 3 ] . algebraicToSquare ( )
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 :
2023-11-13 11:03:54 +01:00
flags = flags or Capture . uint16
2023-11-13 09:52:37 +01:00
2024-04-15 12:04:50 +02:00
elif board . grid [ startSquare ] . kind = = Pawn and abs ( startSquare . rank - targetSquare . rank ) = = 2 :
2023-11-13 11:03:54 +01:00
flags = flags or DoublePush . uint16
2023-11-13 09:52:37 +01:00
if len ( moveString ) = = 5 :
# Promotion
case moveString [ 4 ] :
of ' b ' :
2023-11-13 11:03:54 +01:00
flags = flags or PromoteToBishop . uint16
2023-11-13 09:52:37 +01:00
of ' n ' :
2023-11-13 11:03:54 +01:00
flags = flags or PromoteToKnight . uint16
2023-11-13 09:52:37 +01:00
of ' q ' :
2023-11-13 11:03:54 +01:00
flags = flags or PromoteToQueen . uint16
2023-11-13 09:52:37 +01:00
of ' r ' :
2023-11-13 11:03:54 +01:00
flags = flags or PromoteToRook . uint16
2023-11-13 09:52:37 +01:00
else :
echo & " Error: move: invalid promotion type "
return
2024-04-08 20:28:31 +02:00
var move = Move ( startSquare : startSquare , targetSquare : targetSquare , flags : flags )
2024-04-15 12:45:47 +02:00
if board . getPiece ( move . startSquare ) . kind = = King and move . startSquare = = board . getActiveColor ( ) . getKingStartingSquare ( ) :
2024-04-08 20:28:31 +02:00
if move . targetSquare = = move . startSquare + longCastleKing ( ) :
move . flags = move . flags or CastleLong . uint16
elif move . targetSquare = = move . startSquare + shortCastleKing ( ) :
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 )
if result = = emptyMove ( ) :
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 ( ) :
if handleMoveCommand ( tempBoard , @ [ " move " , args [ j ] ] ) = = emptyMove ( ) :
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 ( ) :
if handleMoveCommand ( tempBoard , @ [ " move " , args [ j ] ] ) = = emptyMove ( ) :
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 " :
2023-11-13 09:52:37 +01:00
board . undoLastMove ( )
2024-04-08 20:28:31 +02:00
of " turn " :
echo & " Active color: {board.getActiveColor()} "
of " ep " :
let target = board . getEnPassantTarget ( )
2024-04-15 12:04:50 +02:00
if target ! = emptySquare ( ) :
echo & " En passant target: {target.squareToAlgebraic()} "
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-08 20:28:31 +02:00
of " castle " :
let canCastle = board . canCastle ( )
echo & " Castling rights for {( $board .getActiveColor()).toLowerAscii()}: \n - King side: {(if canCastle.king: \" yes \" else: \" no \" )} \n - Queen side: {(if canCastle.queen: \" yes \" else: \" no \" )} "
of " check " :
echo & " {board.getActiveColor()} king in check: {(if board.inCheck(): \" 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 "
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-15 12:04:50 +02:00
# Ensure pieces are in the correct squares
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-15 12:04:50 +02:00
b . makeMove ( createMove ( " a2 " , " a4 " , @ [ DoublePush ] ) )
2024-04-15 12:45:47 +02:00
let whitePawns = b . getBitboard ( Pawn , White )
echo whitePawns . pretty ( )
for square in whitePawns :
echo square
doAssert b . getEnPassantTarget ( ) = = makeSquare ( 5 , 0 )
#[setControlCHook(proc () {.noconv.} = quit(0))
2023-10-31 23:06:27 +01:00
2024-04-15 12:45:47 +02:00
quit ( main ( ) ) ] #