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 .. / util / matrix
2023-10-12 11:55:12 +02:00
export matrix
2023-03-18 18:14:30 +01:00
import std / strutils
2023-10-12 10:14:37 +02:00
import std / strformat
2023-10-20 02:23:07 +02:00
import std / sequtils
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
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-10-20 02:23:07 +02:00
Default = 0 'i8 , # No flag
2023-10-25 22:41:04 +02:00
EnPassant , # Move is a capture with en passant
Capture , # Move is a capture
2023-10-20 02:23:07 +02:00
DoublePush , # Move is a double pawn push
# Castling metadata
2023-10-17 22:16:01 +02:00
CastleLong ,
CastleShort ,
2023-10-20 02:23:07 +02:00
# Pawn promotion metadata
2023-10-16 14:55:43 +02:00
PromoteToQueen ,
PromoteToRook ,
PromoteToBishop ,
PromoteToKnight
2023-10-25 22:41:04 +02:00
# Useful type aliases
Location * = tuple [ row , col : int8 ]
2023-10-28 02:32:50 +02:00
Attacked = seq [ tuple [ source , target , direction : Location ] ]
2023-10-25 22:41:04 +02:00
Pieces = tuple [ king : Location , queens : seq [ Location ] , rooks : seq [ Location ] ,
bishops : seq [ Location ] , knights : seq [ Location ] ,
pawns : seq [ Location ] ]
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
startSquare * : Location
targetSquare * : Location
2023-10-16 14:55:43 +02:00
flag * : MoveFlag
2023-10-15 16:53:44 +02:00
2023-10-17 15:08:46 +02:00
Position * = ref object
## A chess position
move : Move
2023-10-17 22:16:01 +02:00
# Did the rooks on either side/the king move?
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
2023-10-18 10:45:54 +02:00
plyFromRoot : int16
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
2023-10-18 10:45:54 +02:00
fullMoveCount : int16
2023-03-18 18:14:30 +01:00
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
2023-10-31 23:06:27 +01:00
enPassantSquare * : Location
2023-10-12 11:55:12 +02:00
# Locations of all pieces
pieces : tuple [ white : Pieces , black : Pieces ]
2023-10-31 23:06:27 +01:00
# Squares attacked by both sides
2023-10-21 18:19:41 +02:00
attacked : tuple [ white : Attacked , black : Attacked ]
2023-10-31 23:06:27 +01:00
# Pieces pinned by both sides
2023-10-28 02:32:50 +02:00
pinned : tuple [ white : Attacked , black : Attacked ]
# The original piece captured to reach this position (may be empty)
2023-10-31 23:06:27 +01:00
#captured: Piece
2023-10-28 02:32:50 +02:00
# The piece that moved to reach this position (needed to undo moves)
2023-10-31 23:06:27 +01:00
#moved: Piece
2023-10-17 15:08:46 +02:00
# Active color
turn : PieceColor
2023-10-28 02:32:50 +02:00
CacheEntry [ T ] = ref object
valid : bool
data : T
Cache = object
canCastle : tuple [ white , black : CacheEntry [ tuple [ queen , king : bool ] ] ]
inCheck : tuple [ white , black : CacheEntry [ bool ] ]
2023-10-17 15:08:46 +02:00
ChessBoard * = ref object
## A chess board object
grid : Matrix [ Piece ]
position : Position
# List of reached positions
positions : seq [ Position ]
2023-10-28 02:32:50 +02:00
# Cached results of expensive
# functions in the current position
2023-10-31 23:06:27 +01:00
#cache: Cache
2023-10-12 11:55:12 +02:00
2023-03-18 18:14:30 +01:00
# Initialized only once, copied every time
var empty : seq [ Piece ] = @ [ ]
for _ in countup ( 0 , 63 ) :
empty . add ( Piece ( kind : Empty , color : None ) )
2023-10-15 16:53:44 +02:00
func emptyPiece * : Piece {. inline . } = Piece ( kind : Empty , color : None )
func emptyLocation * : Location {. inline . } = ( - 1 , - 1 )
func opposite * ( c : PieceColor ) : PieceColor {. inline . } = ( if c = = White : Black else : White )
2023-10-17 16:38:43 +02:00
proc algebraicToLocation * ( s : string ) : Location {. inline . }
2023-10-21 18:19:41 +02:00
proc makeMove * ( self : ChessBoard , move : Move ) : Move {. discardable . }
2023-10-28 02:32:50 +02:00
func emptyMove * : Move {. inline . } = Move ( startSquare : emptyLocation ( ) , targetSquare : emptyLocation ( ) )
2023-10-16 22:14:58 +02:00
func ` + ` * ( a , b : Location ) : Location = ( a . row + b . row , a . col + b . col )
2023-10-25 22:41:04 +02:00
func ` - ` * ( a : Location ) : Location = ( - a . row , - a . col )
func ` - ` * ( a , b : Location ) : Location = ( a . row - b . row , a . col - b . col )
2023-10-18 10:45:54 +02:00
func isValid * ( a : Location ) : bool {. inline . } = a . row in 0 .. 7 and a . col in 0 .. 7
2023-10-16 22:14:58 +02:00
proc generateMoves ( self : ChessBoard , location : Location ) : seq [ Move ]
2023-10-28 02:32:50 +02:00
proc getAttackers * ( self : ChessBoard , loc : Location , color : PieceColor ) : seq [ Location ]
proc getAttackFor * ( self : ChessBoard , source , target : Location ) : tuple [ source , target , direction : Location ]
2023-10-23 18:02:31 +02:00
proc isAttacked * ( self : ChessBoard , loc : Location , color : PieceColor = None ) : bool
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
2023-10-21 18:19:41 +02:00
proc spawnPiece ( self : ChessBoard , location : Location , piece : Piece )
proc updateAttackedSquares ( self : ChessBoard )
2023-10-25 22:41:04 +02:00
proc getPinnedDirections ( self : ChessBoard , loc : Location ) : seq [ Location ]
2023-10-28 02:32:50 +02:00
proc getAttacks * ( self : ChessBoard , loc : Location ) : Attacked
proc getSlidingAttacks ( self : ChessBoard , loc : Location ) : tuple [ attacks : Attacked , pins : Attacked ]
2023-10-31 23:06:27 +01:00
#[func invalidateCache(self: ChessBoard) {.inline.}]#
2023-10-30 17:46:06 +01:00
proc inCheck * ( self : ChessBoard , color : PieceColor = None ) : bool
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 )
2023-10-16 22:14:58 +02:00
2023-10-17 12:42:15 +02:00
# Due to our board layout, directions of movement are reversed for white/black so
# we need these helpers to avoid going mad with integer tuples and minus signs
# everywhere
2023-10-17 22:16:01 +02:00
func topLeftDiagonal ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( - 1 , - 1 ) else : ( 1 , 1 ) )
func topRightDiagonal ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( - 1 , 1 ) else : ( 1 , - 1 ) )
func bottomLeftDiagonal ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( 1 , - 1 ) else : ( - 1 , 1 ) )
func bottomRightDiagonal ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( 1 , 1 ) else : ( - 1 , - 1 ) )
func leftSide ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( 0 , - 1 ) else : ( 0 , 1 ) )
func rightSide ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( 0 , 1 ) else : ( 0 , - 1 ) )
2023-10-25 22:41:04 +02:00
func topSide ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( - 1 , 0 ) else : ( 1 , 0 ) )
2023-10-17 22:16:01 +02:00
func bottomSide ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( 1 , 0 ) else : ( - 1 , 0 ) )
func forward ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( - 1 , 0 ) else : ( 1 , 0 ) )
func doublePush ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( - 2 , 0 ) else : ( 2 , 0 ) )
2023-10-20 02:23:07 +02:00
func longCastleKing : Location {. inline . } = ( 0 , - 2 )
func shortCastleKing : Location {. inline . } = ( 0 , 2 )
func longCastleRook : Location {. inline . } = ( 0 , 3 )
func shortCastleRook : Location {. inline . } = ( 0 , - 2 )
2023-10-17 22:16:01 +02:00
func kingSideRook ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( 7 , 7 ) else : ( 0 , 7 ) )
func queenSideRook ( color : PieceColor ) : Location {. inline . } = ( if color = = White : ( 7 , 0 ) else : ( 0 , 0 ) )
func bottomLeftKnightMove ( color : PieceColor , long : bool = true ) : Location {. inline . } =
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
2023-10-17 22:16:01 +02:00
func bottomRightKnightMove ( color : PieceColor , long : bool = true ) : Location {. inline . } =
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
2023-10-17 22:16:01 +02:00
func topLeftKnightMove ( color : PieceColor , long : bool = true ) : Location {. inline . } =
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 )
2023-10-17 22:16:01 +02:00
func topRightKnightMove ( color : PieceColor , long : bool = true ) : Location {. inline . } =
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
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
2023-10-28 02:32:50 +02:00
func getEnPassantTarget * ( self : ChessBoard ) : Location {. 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
2023-10-16 14:55:43 +02:00
func getStartRow ( piece : Piece ) : int {. inline . } =
## 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
2023-10-16 15:25:48 +02:00
func getLastRow ( color : PieceColor ) : int {. inline . } =
## Retrieves the location of the last
## row 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 )
# Turns our flat sequence into an 8x8 grid
result . grid = newMatrixFromSeq [ Piece ] ( empty , ( 8 , 8 ) )
2023-10-31 23:06:27 +01:00
#result.cache = Cache(canCastle: (white: CacheEntry[tuple[queen, king: bool]](), black: CacheEntry[tuple[queen, king: bool]]()),
# inCheck: (white: CacheEntry[bool](), black: CacheEntry[bool]()))
2023-10-17 15:08:46 +02:00
result . position = Position ( attacked : ( @ [ ] , @ [ ] ) ,
2023-10-31 23:06:27 +01:00
enPassantSquare : emptyLocation ( ) ,
2023-10-17 15:08:46 +02:00
move : emptyMove ( ) ,
2023-10-20 02:23:07 +02:00
turn : White ,
2023-10-25 22:41:04 +02:00
fullMoveCount : 1 ,
pieces : ( white : ( king : emptyLocation ( ) ,
queens : @ [ ] ,
rooks : @ [ ] ,
bishops : @ [ ] ,
knights : @ [ ] ,
pawns : @ [ ] ) ,
black : ( king : emptyLocation ( ) ,
queens : @ [ ] ,
rooks : @ [ ] ,
bishops : @ [ ] ,
knights : @ [ ] ,
pawns : @ [ ] ) ) )
2023-10-17 15:08:46 +02:00
2023-10-12 11:55:12 +02:00
proc newChessboardFromFEN * ( state : string ) : ChessBoard =
## Initializes a chessboard with the
## state encoded by the given FEN string
result = newChessboard ( )
2023-03-18 18:14:30 +01:00
var
# Current location 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-10-12 11:55:12 +02:00
# Temporary variable to store the piece
piece : Piece
2023-03-18 18:14:30 +01:00
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
while index < = state . high ( ) :
var c = state [ index ]
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 ' :
2023-10-12 11:55:12 +02:00
# We know for a fact these values are in our
# enumeration, so all is good
{. push . }
{. warning [ HoleEnumConv ] : off . }
piece = Piece ( kind : PieceKind ( c . toLowerAscii ( ) ) , color : if c . isUpperAscii ( ) : White else : Black )
{. pop . }
case piece . color :
of Black :
case piece . kind :
of Pawn :
2023-10-17 15:08:46 +02:00
result . position . pieces . black . pawns . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of Bishop :
2023-10-17 15:08:46 +02:00
result . position . pieces . black . bishops . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of Knight :
2023-10-17 15:08:46 +02:00
result . position . pieces . black . knights . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of Rook :
2023-10-17 15:08:46 +02:00
result . position . pieces . black . rooks . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of Queen :
2023-10-17 15:08:46 +02:00
result . position . pieces . black . queens . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of King :
2023-10-17 15:08:46 +02:00
result . position . pieces . black . king = ( row , column )
2023-10-12 11:55:12 +02:00
else :
discard
of White :
case piece . kind :
of Pawn :
2023-10-17 15:08:46 +02:00
result . position . pieces . white . pawns . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of Bishop :
2023-10-17 15:08:46 +02:00
result . position . pieces . white . bishops . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of Knight :
2023-10-17 15:08:46 +02:00
result . position . pieces . white . knights . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of Rook :
2023-10-17 15:08:46 +02:00
result . position . pieces . white . rooks . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of Queen :
2023-10-17 15:08:46 +02:00
result . position . pieces . white . queens . add ( ( row , column ) )
2023-10-12 11:55:12 +02:00
of King :
2023-10-17 15:08:46 +02:00
result . position . pieces . white . king = ( row , column )
2023-10-12 11:55:12 +02:00
else :
discard
else :
discard
result . grid [ row , column ] = 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-03-18 18:14:30 +01:00
raise newException ( ValueError , " invalid skip value (> 8) in FEN string " )
2023-10-18 10:45:54 +02:00
column + = int8 ( x )
2023-03-18 18:14:30 +01:00
else :
raise newException ( ValueError , " invalid piece identifier in FEN string " )
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-10-12 10:14:37 +02:00
raise newException ( ValueError , " invalid active color identifier in FEN string " )
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 :
raise newException ( ValueError , " invalid castling availability in FEN string " )
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 :
2023-10-31 23:06:27 +01:00
result . position . enPassantSquare = state [ index .. index + 1 ] . algebraicToLocation ( )
2023-03-18 18:14:30 +01:00
# Square metadata is 2 bytes long
inc ( index )
of 4 :
# Halfmove clock
var s = " "
while not state [ index ] . isSpaceAscii ( ) :
s . add ( state [ index ] )
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 = " "
while index < = state . high ( ) :
s . add ( state [ index ] )
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 :
raise newException ( ValueError , " too many fields in FEN string " )
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 ) :
# Opponent king cannot be captured!
raise newException ( ValueError , " invalid position: opponent king can be captured " )
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
## the given color and type in the given
## position
2023-10-15 16:53:44 +02:00
case color :
of White :
case kind :
of Pawn :
2023-10-17 15:08:46 +02:00
return self . position . pieces . white . pawns . len ( )
2023-10-15 16:53:44 +02:00
of Bishop :
2023-10-17 15:08:46 +02:00
return self . position . pieces . white . bishops . len ( )
2023-10-15 16:53:44 +02:00
of Knight :
2023-10-17 15:08:46 +02:00
return self . position . pieces . white . knights . len ( )
2023-10-15 16:53:44 +02:00
of Rook :
2023-10-17 15:08:46 +02:00
return self . position . pieces . white . rooks . len ( )
2023-10-15 16:53:44 +02:00
of Queen :
2023-10-17 15:08:46 +02:00
return self . position . pieces . white . queens . len ( )
2023-10-15 16:53:44 +02:00
of King :
# There shall be only one, forever
return 1
else :
2023-10-28 02:32:50 +02:00
raise newException ( ValueError , " invalid piece type " )
2023-10-15 16:53:44 +02:00
of Black :
case kind :
of Pawn :
2023-10-17 15:08:46 +02:00
return self . position . pieces . black . pawns . len ( )
2023-10-15 16:53:44 +02:00
of Bishop :
2023-10-17 15:08:46 +02:00
return self . position . pieces . black . bishops . len ( )
2023-10-15 16:53:44 +02:00
of Knight :
2023-10-17 15:08:46 +02:00
return self . position . pieces . black . knights . len ( )
2023-10-15 16:53:44 +02:00
of Rook :
2023-10-17 15:08:46 +02:00
return self . position . pieces . black . rooks . len ( )
2023-10-15 16:53:44 +02:00
of Queen :
2023-10-17 15:08:46 +02:00
return self . position . pieces . black . queens . len ( )
2023-10-15 16:53:44 +02:00
of King :
2023-10-16 14:55:43 +02:00
# In perpetuity
2023-10-15 16:53:44 +02:00
return 1
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 ]
2023-10-28 02:32:50 +02:00
func rowToRank ( row : int ) : int {. inline . } =
## Converts a row into our grid into
## a chess rank
2023-10-17 17:27:33 +02:00
const indeces = [ 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 ]
return indeces [ row ]
2023-10-17 16:38:43 +02:00
proc algebraicToLocation * ( s : string ) : Location =
2023-10-15 16:53:44 +02:00
## Converts a square location from algebraic
## 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
2023-10-17 22:16:01 +02:00
func locationToAlgebraic * ( loc : Location ) : string {. inline . } =
2023-10-17 16:38:43 +02:00
## Converts a location from our internal row, column
## notation to a square in algebraic notation
2023-10-17 17:27:33 +02:00
return & " {char(uint8(loc.col) + uint8( ' a ' ))}{rowToRank(loc.row)} "
2023-10-17 16:38:43 +02:00
2023-10-28 02:32:50 +02:00
func getPiece * ( self : ChessBoard , loc : Location ) : Piece {. inline . } =
2023-10-21 18:19:41 +02:00
## Gets the piece at the given location
return self . grid [ loc . row , loc . col ]
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
2023-10-21 18:19:41 +02:00
return self . getPiece ( square . algebraicToLocation ( ) )
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 . } =
## Returns whrther the given move is a
## pawn promotion or not
return move . flag in [ PromoteToBishop , PromoteToKnight , PromoteToRook , PromoteToQueen ]
2023-10-16 22:14:58 +02:00
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-31 23:06:27 +01:00
#[if self.cache.inCheck.white.valid:
return self . cache . inCheck . white . data ] #
2023-10-28 02:32:50 +02:00
result = self . isAttacked ( self . position . pieces . white . king , Black )
of Black :
2023-10-31 23:06:27 +01:00
#[if self.cache.inCheck.black.valid:
return self . cache . inCheck . black . data ] #
2023-10-28 02:32:50 +02:00
result = self . isAttacked ( self . position . pieces . black . king , White )
else :
# Unreachable
discard
2023-10-31 23:06:27 +01:00
#[case color:
2023-10-28 02:32:50 +02:00
of White :
self . cache . inCheck . white . valid = true
self . cache . inCheck . white . data = result
2023-10-17 22:16:01 +02:00
of Black :
2023-10-28 02:32:50 +02:00
self . cache . inCheck . black . valid = true
self . cache . inCheck . black . data = result
2023-10-20 02:23:07 +02:00
else :
# Unreachable
2023-10-31 23:06:27 +01:00
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 ( )
# If the rooks or king have been moved, castling
# rights have been lost
case color :
of White :
2023-10-31 23:06:27 +01:00
#[if self.cache.canCastle.white.valid:
return self . cache . canCastle . white . data ] #
2023-10-17 22:16:01 +02:00
result . king = self . position . castlingAvailable . white . king
result . queen = self . position . castlingAvailable . white . queen
of Black :
2023-10-31 23:06:27 +01:00
#[if self.cache.canCastle.black.valid:
return self . cache . canCastle . black . data #]#
2023-10-17 22:16:01 +02:00
result . king = self . position . castlingAvailable . black . king
result . queen = self . position . castlingAvailable . black . queen
of None :
# Unreachable
discard
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
loc : Location
queenSide : Location
kingSide : Location
# If the path between the king and rook on a given side is blocked or any of the
# squares where the king would travel to are attacked by the opponent,
# then castling is (temporarily) prohibited on that side
case color :
of White :
loc = self . position . pieces . white . king
queenSide = color . leftSide ( )
kingSide = color . rightSide ( )
of Black :
loc = self . position . pieces . black . king
queenSide = color . rightSide ( )
kingSide = color . leftSide ( )
of None :
# Unreachable
discard
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
location = loc
otherPiece : Piece
2023-10-31 23:06:27 +01:00
while location ! = loc + shortCastleKing ( ) :
2023-10-23 18:02:31 +02:00
location = location + kingSide
2023-10-25 22:41:04 +02:00
otherPiece = self . grid [ location . row , location . col ]
2023-10-31 23:06:27 +01:00
if otherPiece . color ! = None or self . isAttacked ( location , color . opposite ( ) ) :
2023-10-23 18:02:31 +02:00
result . king = false
break
2023-10-31 23:06:27 +01:00
2023-10-23 18:02:31 +02:00
if result . queen :
# Long castle
2023-10-25 22:41:04 +02:00
var
location = loc
otherPiece : Piece
2023-10-31 23:06:27 +01:00
while location ! = loc + longCastleKing ( ) :
2023-10-23 18:02:31 +02:00
location = location + queenSide
2023-10-25 22:41:04 +02:00
otherPiece = self . grid [ location . row , location . col ]
2023-10-31 23:06:27 +01:00
if otherPiece . color ! = None or self . isAttacked ( location , color . opposite ( ) ) :
2023-10-28 02:32:50 +02:00
result . queen = false
2023-10-23 18:02:31 +02:00
break
2023-10-31 23:06:27 +01:00
#[case color:
2023-10-28 02:32:50 +02:00
of White :
self . cache . canCastle . white . data = result
self . cache . canCastle . white . valid = true
of Black :
self . cache . canCastle . black . data = result
2023-10-31 23:06:27 +01:00
self . cache . canCastle . black . valid = true
2023-10-28 02:32:50 +02:00
else :
2023-10-31 23:06:27 +01:00
discard ] #
2023-10-28 02:32:50 +02:00
proc getCheckResolutions ( self : ChessBoard , color : PieceColor ) : seq [ Location ] =
## Returns the squares that need to be covered to
## resolve the current check (includes capturing
## the checking piece). In case of double check, an
## empty list is returned (as the king must move)
var king : Location
case color :
of White :
king = self . position . pieces . white . king
of Black :
king = self . position . pieces . black . king
else :
return @ [ ]
let attackers : seq [ Location ] = self . getAttackers ( king , color . opposite ( ) )
if attackers . len ( ) > 1 :
# Double checks require to move the king
return @ [ ]
let
attacker = attackers [ 0 ]
attackerPiece = self . grid [ attacker . row , attacker . col ]
attack = self . getAttackFor ( attacker , king )
# Capturing the piece resolves the check
result . add ( attacker )
# 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
# that piece has to be captured)
if attackerPiece . kind notin [ Knight , Pawn ] :
var location = attacker
while location ! = king :
location = location + attack . direction
if not location . isValid ( ) :
break
result . add ( location )
2023-10-30 14:46:27 +01:00
func getKing ( self : ChessBoard , color : PieceColor ) : Location {. inline . } =
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
2023-10-16 14:55:43 +02:00
proc generatePawnMoves ( self : ChessBoard , location : Location ) : seq [ Move ] =
2023-10-16 15:25:48 +02:00
## Generates the possible moves for the pawn in the given
## location
2023-10-16 14:55:43 +02:00
var
piece = self . grid [ location . row , location . col ]
2023-10-16 15:25:48 +02:00
locations : seq [ Location ] = @ [ ]
2023-10-20 02:23:07 +02:00
flags : seq [ MoveFlag ] = @ [ ]
2023-10-16 14:55:43 +02:00
doAssert piece . kind = = Pawn , & " generatePawnMoves called on a {piece.kind} "
2023-10-16 22:14:58 +02:00
# Pawns can move forward one square
2023-10-23 18:02:31 +02:00
let forward = ( piece . color . forward ( ) + location )
2023-10-20 02:23:07 +02:00
# Only if the square is empty though
2023-10-16 22:14:58 +02:00
if forward . isValid ( ) and self . grid [ forward . row , forward . col ] . color = = None :
2023-10-23 18:02:31 +02:00
locations . add ( forward )
2023-10-20 02:23:07 +02:00
flags . add ( Default )
2023-10-16 22:14:58 +02:00
# If the pawn is on its first rank, it can push two squares
if location . row = = piece . getStartRow ( ) :
2023-10-23 18:02:31 +02:00
let double = location + piece . color . doublePush ( )
# Check that both squares are empty
2023-10-20 02:23:07 +02:00
if double . isValid ( ) and self . grid [ forward . row , forward . col ] . color = = None and self . grid [ double . row , double . col ] . color = = None :
2023-10-23 18:02:31 +02:00
locations . add ( double )
2023-10-20 02:23:07 +02:00
flags . add ( DoublePush )
2023-10-31 23:06:27 +01:00
let enPassantPiece = self . getEnPassantTarget ( ) + piece . color . opposite ( ) . topSide ( )
let enPassantPawn = self . grid [ enPassantPiece . row , enPassantPiece . col ]
2023-10-16 22:14:58 +02:00
# They can also move on either diagonal one
2023-10-28 02:32:50 +02:00
# square, but only to capture or for en passant
for diagonal in [ location + piece . color . topRightDiagonal ( ) , location + piece . color . topLeftDiagonal ( ) ] :
if diagonal . isValid ( ) :
2023-10-31 23:06:27 +01:00
if enPassantPawn . color = = piece . color . opposite ( ) and diagonal = = self . position . enPassantSquare :
2023-10-30 14:46:27 +01:00
# Ensure en passant doesn't create a check
let king = self . getKing ( piece . color )
var ok = true
if king . row = = location . row :
var current = location + piece . color . rightSide ( )
while true :
current = current + piece . color . rightSide ( )
if not current . isValid ( ) :
break
let p = self . grid [ current . row , current . col ]
if p . color = = piece . color :
break
if p . color = = None :
continue
# Bishops can't create checks through en passant (I'm pretty sure at least)
if p . color = = piece . color . opposite ( ) and p . kind in [ Queen , Rook ] :
ok = false
if ok :
locations . add ( diagonal )
flags . add ( EnPassant )
2023-10-28 02:32:50 +02:00
elif self . grid [ diagonal . row , diagonal . col ] . color = = piece . color . opposite ( ) and self . grid [ diagonal . row , diagonal . col ] . kind ! = King :
locations . add ( diagonal )
flags . add ( Capture )
2023-10-25 22:41:04 +02:00
var
newLocation : Location
2023-10-28 02:32:50 +02:00
newFlags : seq [ MoveFlag ]
2023-10-25 22:41:04 +02:00
newLocations : seq [ Location ]
2023-10-28 02:32:50 +02:00
# Check for pins
2023-10-25 22:41:04 +02:00
let pins = self . getPinnedDirections ( location )
for pin in pins :
newLocation = location + pin
2023-10-28 02:32:50 +02:00
let loc = locations . find ( newLocation )
if loc ! = - 1 :
# Pin direction is legal for this piece
2023-10-25 22:41:04 +02:00
newLocations . add ( newLocation )
2023-10-28 02:32:50 +02:00
newFlags . add ( flags [ loc ] )
2023-10-25 22:41:04 +02:00
if pins . len ( ) > 0 :
locations = newLocations
2023-10-28 02:32:50 +02:00
flags = newFlags
let checked = self . inCheck ( )
let resolutions = if not checked : @ [ ] else : self . getCheckResolutions ( piece . color )
2023-10-23 18:02:31 +02:00
var targetPiece : Piece
2023-10-20 02:23:07 +02:00
for ( target , flag ) in zip ( locations , flags ) :
2023-10-28 02:32:50 +02:00
if checked and target notin resolutions :
continue
2023-10-23 18:02:31 +02:00
targetPiece = self . grid [ target . row , target . col ]
if target . row = = piece . color . getLastRow ( ) :
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 ] :
2023-10-28 02:32:50 +02:00
result . add ( Move ( startSquare : location , targetSquare : target , flag : promotionType ) )
2023-10-16 22:14:58 +02:00
continue
2023-10-28 02:32:50 +02:00
result . add ( Move ( startSquare : location , targetSquare : target , flag : flag ) )
2023-10-17 16:38:43 +02:00
2023-10-16 14:55:43 +02:00
proc generateSlidingMoves ( self : ChessBoard , location : Location ) : seq [ Move ] =
2023-10-16 22:14:58 +02:00
## Generates moves for the sliding piece in the given location
2023-10-25 22:41:04 +02:00
let piece = self . grid [ location . row , location . col ]
2023-10-16 14:55:43 +02:00
doAssert piece . kind in [ Bishop , Rook , Queen ] , & " generateSlidingMoves called on a {piece.kind} "
2023-10-16 23:02:58 +02:00
var directions : seq [ Location ] = @ [ ]
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 ( ) )
2023-10-25 22:41:04 +02:00
let pinned = self . getPinnedDirections ( location )
if pinned . len ( ) > 0 :
directions = pinned
2023-10-28 02:32:50 +02:00
let checked = self . inCheck ( )
let 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
square : Location = location
otherPiece : Piece
while true :
square = square + direction
# End of board reached
if not square . isValid ( ) :
break
otherPiece = self . grid [ square . row , square . col ]
# 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 :
continue
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
2023-10-28 02:32:50 +02:00
result . add ( Move ( startSquare : location , targetSquare : square , flag : Capture ) )
2023-10-17 10:31:38 +02:00
break
# Target square is empty
2023-10-28 02:32:50 +02:00
result . add ( Move ( startSquare : location , targetSquare : square ) )
2023-10-16 14:55:43 +02:00
2023-10-17 12:08:07 +02:00
proc generateKingMoves ( self : ChessBoard , location : Location ) : seq [ Move ] =
## Generates moves for the king in the given location
var
piece = self . grid [ location . row , location . col ]
doAssert piece . kind = = King , & " generateKingMoves called on a {piece.kind} "
2023-10-17 22:16:01 +02:00
var directions : seq [ Location ] = @ [ piece . color . topLeftDiagonal ( ) ,
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
let square : Location = location + direction
# 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
2023-10-17 12:08:07 +02:00
let otherPiece = self . grid [ square . row , square . col ]
2023-10-20 02:23:07 +02:00
if otherPiece . color = = self . getActiveColor . opposite ( ) :
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!
2023-10-28 02:32:50 +02:00
result . add ( Move ( startSquare : location , targetSquare : square , flag : flag ) )
2023-10-17 12:42:15 +02:00
proc generateKnightMoves ( self : ChessBoard , location : Location ) : seq [ Move ] =
## Generates moves for the knight in the given location
var
piece = self . grid [ location . row , location . col ]
doAssert piece . kind = = Knight , & " generateKnightMoves called on a {piece.kind} "
2023-10-17 22:16:01 +02:00
var directions : seq [ Location ] = @ [ piece . color . bottomLeftKnightMove ( ) ,
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 ) ]
2023-10-25 22:41:04 +02:00
let pinned = self . getPinnedDirections ( location )
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
let square : Location = location + direction
# End of board reached
if not square . isValid ( ) :
continue
let otherPiece = self . grid [ square . row , square . col ]
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
2023-10-28 02:32:50 +02:00
result . add ( Move ( startSquare : location , targetSquare : square , flag : Capture ) )
2023-10-23 18:02:31 +02:00
else :
# Target square is empty
2023-10-28 02:32:50 +02:00
result . add ( Move ( startSquare : location , targetSquare : square ) )
2023-10-17 12:42:15 +02:00
2023-10-17 12:08:07 +02:00
2023-10-16 14:55:43 +02:00
proc generateMoves ( self : ChessBoard , location : Location ) : seq [ Move ] =
2023-10-23 18:02:31 +02:00
## Returns the list of possible legal chess moves for the
2023-10-16 14:55:43 +02:00
## piece in the given location
let piece = self . grid [ location . row , location . col ]
2023-10-16 15:25:48 +02:00
case piece . kind :
of Queen , Bishop , Rook :
return self . generateSlidingMoves ( location )
of Pawn :
return self . generatePawnMoves ( location )
2023-10-16 22:14:58 +02:00
of King :
2023-10-17 12:08:07 +02:00
return self . generateKingMoves ( location )
2023-10-17 12:42:15 +02:00
of Knight :
return self . generateKnightMoves ( location )
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
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
for i , row in self . grid :
for j , piece in row :
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
2023-10-23 18:02:31 +02:00
proc isAttacked * ( self : ChessBoard , loc : Location , color : PieceColor = None ) : bool =
2023-10-17 10:31:38 +02:00
## Returns whether the given location 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 :
2023-10-25 22:41:04 +02:00
if attack . target = = loc :
return true
of White :
for attack in self . position . attacked . white :
if attack . target = = loc :
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
2023-10-28 02:32:50 +02:00
proc getAttackers * ( self : ChessBoard , loc : Location , color : PieceColor ) : seq [ Location ] =
## Returns all the attackers of the given color
## for the given square
case color :
of Black :
for attack in self . position . attacked . black :
if attack . target = = loc :
result . add ( attack . source )
of White :
for attack in self . position . attacked . white :
if attack . target = = loc :
result . add ( attack . source )
of None :
discard
proc getAttacks * ( self : ChessBoard , loc : Location ) : Attacked =
## Returns all the squares attacked by the piece in the given
## location
let piece = self . grid [ loc . row , loc . col ]
case piece . color :
of Black :
for attack in self . position . attacked . black :
if attack . source = = loc :
result . add ( attack )
of White :
for attack in self . position . attacked . white :
if attack . source = = loc :
result . add ( attack )
of None :
discard
proc getAttackFor * ( self : ChessBoard , source , target : Location ) : tuple [ source , target , direction : Location ] =
## Returns the first attacks of the piece in the given
## source location that also attacks the target location
let piece = self . grid [ source . row , source . col ]
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
2023-10-17 16:38:43 +02:00
return self . isAttacked ( square . algebraicToLocation ( ) )
2023-10-17 10:31:38 +02:00
2023-10-28 02:32:50 +02:00
func addAttack ( self : ChessBoard , attack : tuple [ source , target , direction : Location ] , 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
2023-10-25 22:41:04 +02:00
proc getPinnedDirections ( self : ChessBoard , loc : Location ) : seq [ Location ] =
let piece = self . grid [ loc . row , loc . col ]
case piece . color :
of None :
discard
of White :
for pin in self . position . pinned . black :
if pin . target = = loc :
result . add ( pin . direction )
of Black :
for pin in self . position . pinned . white :
if pin . target = = loc :
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
for loc in self . position . pieces . white . pawns :
# Pawns are special in how they capture (i.e. the
# squares they can move to do not match the squares
# they can capture on. Sneaky fucks)
2023-10-28 02:32:50 +02:00
self . addAttack ( ( loc , loc + White . topRightDiagonal ( ) , White . topRightDiagonal ( ) ) , White )
self . addAttack ( ( loc , loc + White . topLeftDiagonal ( ) , White . topRightDiagonal ( ) ) , White )
2023-10-21 18:19:41 +02:00
# We do the same thing for black
for loc in self . position . pieces . black . pawns :
2023-10-28 02:32:50 +02:00
self . addAttack ( ( loc , loc + Black . topRightDiagonal ( ) , Black . topRightDiagonal ( ) ) , Black )
self . addAttack ( ( loc , loc + Black . topLeftDiagonal ( ) , Black . topRightDiagonal ( ) ) , 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 )
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 )
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
2023-10-28 02:32:50 +02:00
proc getSlidingAttacks ( self : ChessBoard , loc : Location ) : tuple [ attacks : Attacked , pins : Attacked ] =
2023-10-21 18:19:41 +02:00
## Internal helper of updateSlidingAttacks
var
directions : seq [ Location ] = @ [ ]
let piece = self . grid [ loc . row , loc . col ]
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
square = loc
otherPiece : Piece
2023-10-21 18:19:41 +02:00
# Slide in this direction as long as it's possible
while true :
square = square + direction
# End of board reached
if not square . isValid ( ) :
break
otherPiece = self . grid [ square . row , square . col ]
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)
2023-10-28 02:32:50 +02:00
result . attacks . add ( ( loc , square , 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
# to check if we've pinned a piece
var
otherSquare : Location = square
behindPiece : Piece
while true :
otherSquare = otherSquare + direction
if not otherSquare . isValid ( ) :
break
behindPiece = self . grid [ otherSquare . row , otherSquare . col ]
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
result . pins . add ( ( loc , square , direction ) )
2023-10-30 17:46:06 +01:00
result . pins . add ( ( loc , square , - direction ) )
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 ( ) :
2023-10-31 23:06:27 +01:00
result . attacks . add ( ( loc , 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 )
# 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-28 02:32:50 +02:00
# Invalidate the cache whenever updates to the
# metadata are made
2023-10-31 23:06:27 +01:00
#self.invalidateCache()
2023-10-16 14:55:43 +02:00
2023-10-23 18:02:31 +02:00
proc removePiece ( self : ChessBoard , location : Location , attack : bool = true ) =
2023-10-15 16:53:44 +02:00
## Removes a piece from the board, updating necessary
## metadata
var piece = self . grid [ location . row , location . col ]
2023-10-23 18:02:31 +02:00
self . grid [ location . row , location . col ] = emptyPiece ( )
2023-10-15 16:53:44 +02:00
case piece . color :
of White :
case piece . kind :
of Pawn :
2023-10-17 15:08:46 +02:00
self . position . pieces . white . pawns . delete ( self . position . pieces . white . pawns . find ( location ) )
2023-10-15 16:53:44 +02:00
of Bishop :
2023-10-17 15:08:46 +02:00
self . position . pieces . white . pawns . delete ( self . position . pieces . white . bishops . find ( location ) )
2023-10-15 16:53:44 +02:00
of Knight :
2023-10-17 15:08:46 +02:00
self . position . pieces . white . pawns . delete ( self . position . pieces . white . knights . find ( location ) )
2023-10-15 16:53:44 +02:00
of Rook :
2023-10-17 15:08:46 +02:00
self . position . pieces . white . rooks . delete ( self . position . pieces . white . rooks . find ( location ) )
2023-10-15 16:53:44 +02:00
of Queen :
2023-10-17 15:08:46 +02:00
self . position . pieces . white . queens . delete ( self . position . pieces . white . queens . find ( location ) )
2023-10-15 16:53:44 +02:00
of King :
doAssert false , " removePiece: attempted to remove the white king "
else :
discard
of Black :
case piece . kind :
of Pawn :
2023-10-20 02:23:07 +02:00
self . position . pieces . black . pawns . delete ( self . position . pieces . black . pawns . find ( location ) )
2023-10-15 16:53:44 +02:00
of Bishop :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . bishops . delete ( self . position . pieces . black . bishops . find ( location ) )
2023-10-15 16:53:44 +02:00
of Knight :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . knights . delete ( self . position . pieces . black . knights . find ( location ) )
2023-10-15 16:53:44 +02:00
of Rook :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . rooks . delete ( self . position . pieces . black . rooks . find ( location ) )
2023-10-15 16:53:44 +02:00
of Queen :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . queens . delete ( self . position . pieces . black . queens . find ( location ) )
2023-10-15 16:53:44 +02:00
of King :
doAssert false , " removePiece: attempted to remove the black king "
else :
discard
else :
discard
2023-10-23 18:02:31 +02:00
if attack :
self . updateAttackedSquares ( )
2023-10-15 16:53:44 +02:00
2023-10-20 02:23:07 +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
2023-10-28 02:32:50 +02:00
let piece = self . grid [ move . startSquare . row , move . startSquare . col ]
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 :
# The way things are structured, we don't care about the order
# of this list, so we can add and remove entries as we please
2023-10-17 15:08:46 +02:00
self . position . pieces . white . pawns . delete ( self . position . pieces . white . pawns . find ( move . startSquare ) )
self . position . pieces . white . pawns . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of Bishop :
2023-10-17 15:08:46 +02:00
self . position . pieces . white . bishops . delete ( self . position . pieces . white . bishops . find ( move . startSquare ) )
self . position . pieces . white . bishops . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of Knight :
2023-10-17 15:08:46 +02:00
self . position . pieces . white . knights . delete ( self . position . pieces . white . knights . find ( move . startSquare ) )
self . position . pieces . white . knights . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of Rook :
2023-10-17 15:08:46 +02:00
self . position . pieces . white . rooks . delete ( self . position . pieces . white . rooks . find ( move . startSquare ) )
self . position . pieces . white . rooks . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of Queen :
2023-10-17 22:16:01 +02:00
self . position . pieces . white . queens . delete ( self . position . pieces . white . queens . find ( move . startSquare ) )
2023-10-17 15:08:46 +02:00
self . position . pieces . white . queens . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of King :
2023-10-17 15:08:46 +02:00
self . position . pieces . white . king = move . targetSquare
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 :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . pawns . delete ( self . position . pieces . black . pawns . find ( move . startSquare ) )
self . position . pieces . black . pawns . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of Bishop :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . bishops . delete ( self . position . pieces . black . bishops . find ( move . startSquare ) )
self . position . pieces . black . bishops . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of Knight :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . knights . delete ( self . position . pieces . black . knights . find ( move . startSquare ) )
self . position . pieces . black . knights . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of Rook :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . rooks . delete ( self . position . pieces . black . rooks . find ( move . startSquare ) )
self . position . pieces . black . rooks . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of Queen :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . queens . delete ( self . position . pieces . black . queens . find ( move . startSquare ) )
self . position . pieces . black . queens . add ( move . targetSquare )
2023-10-15 16:53:44 +02:00
of King :
2023-10-17 15:08:46 +02:00
self . position . pieces . black . king = move . targetSquare
2023-10-15 16:53:44 +02:00
else :
discard
else :
2023-10-17 15:08:46 +02:00
discard
2023-10-21 18:19:41 +02:00
# Empty out the starting square
2023-10-16 14:55:43 +02:00
self . grid [ move . startSquare . row , move . startSquare . col ] = emptyPiece ( )
2023-10-15 16:53:44 +02:00
# Actually move the piece
2023-10-28 02:32:50 +02:00
self . grid [ move . targetSquare . row , move . targetSquare . col ] = piece
2023-10-20 02:23:07 +02:00
if attack :
self . updateAttackedSquares ( )
2023-10-31 23:06:27 +01:00
#else:
2023-10-28 02:32:50 +02:00
# Just to be sure
2023-10-31 23:06:27 +01:00
# self.invalidateCache()
2023-10-20 02:23:07 +02:00
proc movePiece ( self : ChessBoard , startSquare , targetSquare : Location , attack : bool = true ) =
## Like the other movePiece(), but with two locations
2023-10-28 02:32:50 +02:00
self . movePiece ( Move ( startSquare : startSquare , targetSquare : targetSquare ) , attack )
2023-10-17 15:08:46 +02:00
2023-10-31 23:06:27 +01:00
#[func invalidateCache(self: ChessBoard) {.inline.} =
2023-10-28 02:32:50 +02:00
## Invalidates the internal caches
self . cache . canCastle . white . valid = false
self . cache . canCastle . black . valid = false
self . cache . inCheck . white . valid = false
2023-10-31 23:06:27 +01:00
self . cache . inCheck . black . valid = false ] #
2023-10-28 02:32:50 +02:00
2023-10-15 16:53:44 +02:00
proc doMove ( self : ChessBoard , move : Move ) =
## Internal function called by makeMove after
## performing legality checks on the given move. 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
2023-10-28 02:32:50 +02:00
let piece = self . grid [ move . startSquare . row , move . startSquare . col ]
2023-10-17 22:16:01 +02:00
2023-10-17 15:08:46 +02:00
# Needed to detect draw by the 50 move rule
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
2023-10-28 02:32:50 +02:00
if piece . kind = = Pawn or move . flag = = Capture :
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 )
# 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 :
2023-10-28 02:32:50 +02:00
if move . startSquare . row = = piece . getStartRow ( ) :
2023-10-21 18:19:41 +02:00
if move . startSquare . col = = 0 :
# Queen side
castlingAvailable . white . queen = false
elif move . startSquare . col = = 7 :
# King side
castlingAvailable . white . king = false
of Black :
2023-10-28 02:32:50 +02:00
if move . startSquare . row = = piece . getStartRow ( ) :
2023-10-21 18:19:41 +02:00
if move . startSquare . col = = 0 :
# Queen side
castlingAvailable . black . queen = false
elif move . startSquare . col = = 7 :
# King side
castlingAvailable . black . king = false
else :
discard
# Has a rook been captured?
2023-10-28 02:32:50 +02:00
if move . flag = = Capture :
2023-10-31 23:06:27 +01:00
let captured = self . grid [ move . targetSquare . row , move . targetSquare . col ]
if captured . kind = = Rook :
2023-10-21 18:19:41 +02:00
case piece . 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-10-28 02:32:50 +02:00
if piece . kind = = King or move . flag in [ CastleLong , CastleShort ] :
# 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
# Create new position
self . position = Position ( plyFromRoot : self . position . plyFromRoot + 1 ,
2023-10-31 23:06:27 +01:00
halfMoveClock : halfMoveClock ,
fullMoveCount : fullMoveCount ,
#captured: if move.flag == Capture: self.grid[move.targetSquare.row, move.targetSquare.col] else: emptyPiece(),
turn : self . getActiveColor ( ) . opposite ,
castlingAvailable : castlingAvailable ,
move : move ,
pieces : self . position . pieces ,
enPassantSquare : if move . flag = = EnPassant : move . targetSquare + piece . color . bottomSide ( ) else : emptyLocation ( )
2023-10-21 18:19:41 +02:00
)
2023-10-31 23:06:27 +01:00
# Update position metadata
2023-10-28 02:32:50 +02:00
if move . flag in [ CastleShort , CastleLong ] :
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
location : Location
target : Location
if move . flag = = CastleShort :
2023-10-28 02:32:50 +02:00
location = piece . color . kingSideRook ( )
2023-10-21 18:19:41 +02:00
target = shortCastleRook ( )
else :
2023-10-28 02:32:50 +02:00
location = piece . color . queenSideRook ( )
2023-10-21 18:19:41 +02:00
target = longCastleRook ( )
let rook = self . grid [ location . row , location . col ]
2023-10-28 02:32:50 +02:00
let move = Move ( startSquare : location , targetSquare : location + target , flag : move . flag )
2023-10-21 18:19:41 +02:00
self . movePiece ( move , attack = false )
2023-10-17 22:16:01 +02:00
2023-10-31 23:06:27 +01:00
if move . flag = = Capture :
# Get rid of captured pieces
#self.position.captured = self.grid[move.targetSquare.row, move.targetSquare.col]
self . removePiece ( move . targetSquare , attack = false )
2023-10-30 14:46:27 +01:00
2023-10-28 02:32:50 +02:00
if move . flag = = EnPassant :
2023-10-31 23:06:27 +01:00
# Make the en passant pawn disappear
self . removePiece ( move . targetSquare + piece . color . bottomSide ( ) , attack = false )
2023-10-28 02:32:50 +02:00
2023-10-31 23:06:27 +01:00
self . movePiece ( move , attack = false )
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
self . removePiece ( move . targetSquare , attack = false )
case move . flag :
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 :
discard
2023-10-31 23:06:27 +01:00
# Update attack metadata
self . updateAttackedSquares ( )
2023-10-17 15:08:46 +02:00
proc spawnPiece ( self : ChessBoard , location : Location , piece : Piece ) =
## Internal helper to "spawn" a given piece at the given
## location. Note that this will overwrite whatever piece
## was previously located there: use with caution. Does
## not automatically update the attacked square metadata
## or other positional information
case piece . color :
of White :
case piece . kind :
of Pawn :
self . position . pieces . white . pawns . add ( location )
of Knight :
self . position . pieces . white . knights . add ( location )
of Bishop :
self . position . pieces . white . bishops . add ( location )
of Rook :
self . position . pieces . white . rooks . add ( location )
of Queen :
self . position . pieces . white . queens . add ( location )
2023-10-17 22:16:01 +02:00
of King :
self . position . pieces . white . king = location
2023-10-17 15:08:46 +02:00
else :
discard
of Black :
case piece . kind :
of Pawn :
self . position . pieces . black . pawns . add ( location )
of Knight :
self . position . pieces . black . knights . add ( location )
of Bishop :
self . position . pieces . black . bishops . add ( location )
of Rook :
self . position . pieces . black . rooks . add ( location )
of Queen :
self . position . pieces . black . queens . add ( location )
2023-10-17 22:16:01 +02:00
of King :
self . position . pieces . black . king = location
2023-10-17 15:08:46 +02:00
else :
discard
else :
# Unreachable
discard
self . grid [ location . row , location . col ] = piece
2023-10-23 18:02:31 +02:00
proc resetBoard * ( self : ChessBoard ) =
## Resets the internal grid representation
## according to the positional data stored
## in the chessboard. Warning: this can be
## expensive, especially in critical paths
## or tight loops
self . grid = newMatrixFromSeq [ Piece ] ( empty , ( 8 , 8 ) )
for loc in self . position . pieces . white . pawns :
self . grid [ loc . row , loc . col ] = Piece ( color : White , kind : Pawn )
for loc in self . position . pieces . black . pawns :
self . grid [ loc . row , loc . col ] = Piece ( color : Black , kind : Pawn )
for loc in self . position . pieces . white . bishops :
self . grid [ loc . row , loc . col ] = Piece ( color : White , kind : Bishop )
for loc in self . position . pieces . black . bishops :
self . grid [ loc . row , loc . col ] = Piece ( color : Black , kind : Bishop )
for loc in self . position . pieces . white . knights :
self . grid [ loc . row , loc . col ] = Piece ( color : White , kind : Knight )
for loc in self . position . pieces . black . knights :
self . grid [ loc . row , loc . col ] = Piece ( color : Black , kind : Knight )
for loc in self . position . pieces . white . rooks :
self . grid [ loc . row , loc . col ] = Piece ( color : White , kind : Rook )
for loc in self . position . pieces . black . rooks :
self . grid [ loc . row , loc . col ] = Piece ( color : Black , kind : Rook )
for loc in self . position . pieces . white . queens :
self . grid [ loc . row , loc . col ] = Piece ( color : White , kind : Queen )
for loc in self . position . pieces . black . queens :
self . grid [ loc . row , loc . col ] = Piece ( color : Black , kind : Queen )
self . grid [ self . position . pieces . white . king . row , self . position . pieces . white . king . col ] = Piece ( color : White , kind : King )
self . grid [ self . position . pieces . black . king . row , self . position . pieces . black . king . col ] = Piece ( color : Black , kind : King )
2023-10-31 23:06:27 +01:00
proc undoLastMove * ( self : ChessBoard ) =
2023-10-17 15:08:46 +02:00
if self . positions . len ( ) = = 0 :
return
2023-10-31 23:06:27 +01:00
self . position = self . positions . pop ( )
self . resetBoard ( )
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-10-15 16:53:44 +02:00
proc `$` * ( self : ChessBoard ) : string =
result & = " - - - - - - - - "
for i , row in self . grid :
result & = " \n "
for piece in row :
if piece . kind = = Empty :
2023-10-15 22:46:22 +02:00
result & = " x "
2023-10-15 16:53:44 +02:00
continue
if piece . color = = White :
result & = & " {char(piece.kind).toUpperAscii()} "
else :
result & = & " {char(piece.kind)} "
result & = & " {rankToColumn(i + 1) + 1} "
result & = " \n - - - - - - - - "
result & = " \n a b c d e f g h "
2023-10-28 02:32:50 +02:00
proc toChar * ( piece : Piece ) : char =
if piece . color = = White :
return char ( piece . kind ) . toUpperAscii ( )
return char ( piece . kind )
2023-10-15 22:46:22 +02:00
proc pretty * ( self : ChessBoard ) : string =
## Returns a colorized version of the
## board for easier visualization
result & = " - - - - - - - - "
for i , row in self . grid :
result & = " \n "
for j , piece in row :
if piece . kind = = Empty :
result & = " \x1b [36;1mx "
# Avoids the color overflowing
# onto the numbers
if j < 7 :
result & = " \x1b [0m "
else :
result & = " \x1b [0m "
continue
if piece . color = = White :
result & = & " \x1b [37;1m{char(piece.kind).toUpperAscii()} \x1b [0m "
else :
result & = & " \x1b [30;1m{char(piece.kind)} "
result & = & " \x1b [33;1m{rankToColumn(i + 1) + 1} \x1b [0m "
result & = " \n - - - - - - - - "
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
for i , row in self . grid :
skip = 0
for j , piece in row :
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
if self . getEnPassantTarget ( ) = = emptyLocation ( ) :
result & = " - "
else :
result & = self . getEnPassantTarget ( ) . locationToAlgebraic ( )
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
if ply = = 0 :
2023-10-30 14:46:27 +01:00
return ( 1 , 0 , 0 , 0 , 0 , 0 , 0 )
2023-10-31 23:06:27 +01:00
let moves = self . generateAllMoves ( )
if ply = = 1 and bulk :
if divide :
var postfix = " "
for move in moves :
case move . flag :
of PromoteToBishop :
postfix = " b "
of PromoteToKnight :
postfix = " n "
of PromoteToRook :
postfix = " r "
of PromoteToQueen :
postfix = " q "
else :
postfix = " "
echo & " {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}{postfix}: 1 "
if verbose :
echo " "
2023-10-30 14:46:27 +01:00
return ( uint64 ( len ( moves ) ) , 0 , 0 , 0 , 0 , 0 , 0 )
if len ( moves ) = = 0 :
inc ( result . checkmates )
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} "
2023-10-28 02:32:50 +02:00
echo & " Move: {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}, from ({move.startSquare.row}, {move.startSquare.col}) to ({move.targetSquare.row}, {move.targetSquare.col}) "
echo & " Turn: {self.getActiveColor()} "
echo & " Piece: {self.grid[move.startSquare.row, move.startSquare.col].kind} "
echo & " Flag: {move.flag} "
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: " )
if self . getEnPassantTarget ( ) ! = emptyLocation ( ) :
echo self . getEnPassantTarget ( ) . locationToAlgebraic ( )
else :
echo " None "
echo " \n " , self . pretty ( )
2023-10-28 02:32:50 +02:00
self . doMove ( move )
2023-10-30 14:46:27 +01:00
case move . flag :
of Capture :
inc ( result . captures )
of CastleShort , CastleLong :
inc ( result . castles )
of PromoteToBishop , PromoteToKnight , PromoteToQueen , PromoteToRook :
inc ( result . promotions )
of EnPassant :
inc ( result . enPassant )
else :
discard
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 = " "
case move . flag :
of PromoteToBishop :
postfix = " b "
of PromoteToKnight :
postfix = " n "
of PromoteToRook :
postfix = " r "
of PromoteToQueen :
postfix = " q "
else :
discard
echo & " {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}{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-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) "
var board = newDefaultChessboard ( )
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 :
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 ] :
of " clear " :
echo " \x1B c "
of " help " :
echo " TODO "
of " go " :
if len ( cmd ) < 2 :
echo & " Error: go: invalid number of arguments "
continue
case cmd [ 1 ] :
of " perft " :
if len ( cmd ) = = 2 :
echo & " Error: go: perft: invalid number of arguments "
continue
var
args = cmd [ 2 ] . splitWhitespace ( )
bulk = false
2023-10-31 23:06:27 +01:00
verbose = false
2023-10-30 14:46:27 +01:00
if args . len ( ) > 1 :
2023-10-31 23:06:27 +01:00
var ok = true
for arg in args [ 1 .. ^ 1 ] :
case arg :
of " bulk-count " , " bulk " :
bulk = true
of " verbose " :
verbose = true
else :
echo & " Error: go: perft: invalid argument ' {args[1]} ' "
ok = false
break
if not ok :
continue
2023-10-30 14:46:27 +01:00
try :
let ply = parseInt ( args [ 0 ] )
if bulk :
2023-10-31 23:06:27 +01:00
echo & " \n Nodes searched (bulk-counting: on): {board.perft(ply, divide=true, bulk=true, verbose=verbose).nodes} \n "
2023-10-30 14:46:27 +01:00
else :
2023-10-31 23:06:27 +01:00
let data = board . perft ( ply , divide = true , verbose = verbose )
echo & " \n Nodes searched (bulk-counting: off): {data.nodes} "
2023-10-30 14:46:27 +01:00
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 " "
except ValueError :
echo " Error: go: perft: invalid depth "
continue
else :
echo & " Error: go: unknown subcommand ' {cmd[1]} ' "
continue
of " position " :
case len ( cmd ) :
of 2 :
case cmd [ 1 ] :
of " startpos " :
board = newDefaultChessboard ( )
of " current " , " cur " :
echo & " Current position: {board.toFEN()} "
of " pretty " :
echo board . pretty ( )
of " print " , " show " :
echo board
else :
echo & " Error: position: invalid argument ' {cmd[1]} ' "
continue
of 3 :
case cmd [ 1 ] :
of " fen " :
try :
board = newChessboardFromFEN ( cmd [ 2 ] )
except ValueError :
echo & " Error: position: invalid FEN string ' {cmd[2]} ' : {getCurrentExceptionMsg()} "
else :
echo & " Error: position: unknown subcommand ' {cmd[1]} ' "
else :
echo & " Error: position: invalid number of arguments "
continue
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 " "
return - 1
except EOFError :
echo " "
return 0
2023-10-21 18:19:41 +02:00
2023-10-13 11:54:57 +02:00
when isMainModule :
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 )
# Ensure pieces are in the correct location
# 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 )
2023-10-31 23:06:27 +01:00
setControlCHook ( proc ( ) {. noconv . } = quit ( 0 ) )
#b = newChessboardFromFEN("r3k2r/Pppp1ppp/1b3nbN/nP6/BBPPP3/q4N2/Pp4PP/R2Q1RK1 b kq d3 0 1")
#let m = Move(startSquare: "c7".algebraicToLocation, targetSquare: "c5".algebraicToLocation, flag: DoublePush)
#b.makeMove()
2023-10-30 14:46:27 +01:00
quit ( main ( ) )