Make position bitboard management more idiomatic

This commit is contained in:
Mattia Giambirtone 2024-04-20 13:28:14 +02:00
parent 2b16b5ec61
commit 77ff697df7
4 changed files with 104 additions and 229 deletions

View File

@ -14,7 +14,6 @@
import std/strutils
import std/strformat
import std/bitops
import nimfishpkg/bitboards
@ -49,9 +48,9 @@ type
# The side to move
sideToMove: PieceColor
# Positional bitboards for all pieces
pieces: tuple[white, black: tuple[king, queens, rooks, bishops, knights, pawns: Bitboard]]
# Pinned pieces for each side
pins: tuple[white, black: Bitboard]
pieces: array[2, array[6, Bitboard]]
# Pinned pieces for the current side to move
pins: Bitboard
# Pieces checking the current side to move
checkers: Bitboard
@ -135,6 +134,7 @@ func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White
proc inCheck*(self: ChessBoard): bool
proc fromChar*(c: char): Piece
proc newChessboard: ChessBoard =
@ -142,50 +142,25 @@ proc newChessboard: ChessBoard =
new(result)
for i in 0..63:
result.grid[i] = nullPiece()
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White)
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White, pieces: [
[Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)],
[Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)]])
# Indexing operations
func `[]`(self: array[64, Piece], square: Square): Piece {.inline.} = self[square.int8]
func `[]=`(self: var array[64, Piece], square: Square, piece: Piece) {.inline.} = self[square.int8] = piece
func `[]`(self: array[2, array[6, Bitboard]], color: PieceColor): ptr array[6, Bitboard] {.inline.} = addr self[color.int]
func `[]`(self: array[6, Bitboard], kind: PieceKind): Bitboard {.inline.} = self[kind.int]
func `[]=`(self: var array[6, Bitboard], kind: PieceKind, bitboard: Bitboard) {.inline.} = self[kind.int] = bitboard
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.pieces.white.pawns
of Knight:
return self.position.pieces.white.knights
of Bishop:
return self.position.pieces.white.bishops
of Rook:
return self.position.pieces.white.rooks
of Queen:
return self.position.pieces.white.queens
of King:
return self.position.pieces.white.king
else:
discard
of Black:
case kind:
of Pawn:
return self.position.pieces.black.pawns
of Knight:
return self.position.pieces.black.knights
of Bishop:
return self.position.pieces.black.bishops
of Rook:
return self.position.pieces.black.rooks
of Queen:
return self.position.pieces.black.queens
of King:
return self.position.pieces.black.king
else:
discard
else:
discard
return self.position.pieces[color.int][kind.int]
func getBitboard*(self: ChessBoard, piece: Piece): Bitboard =
@ -221,52 +196,11 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
case c.toLowerAscii():
# Piece
of 'r', 'n', 'b', 'q', 'k', 'p':
let
square: Square = makeSquare(row, column)
bitIndex = square.int8
# 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:
result.position.pieces.black.pawns.uint64.uint64.setBit(bitIndex)
of Bishop:
result.position.pieces.black.bishops.uint64.setBit(bitIndex)
of Knight:
result.position.pieces.black.knights.uint64.setBit(bitIndex)
of Rook:
result.position.pieces.black.rooks.uint64.setBit(bitIndex)
of Queen:
result.position.pieces.black.queens.uint64.setBit(bitIndex)
of King:
if result.position.pieces.black.king != 0:
raise newException(ValueError, "invalid position: exactly one king of each color must be present")
result.position.pieces.black.king.uint64.setBit(bitIndex)
else:
discard
of White:
case piece.kind:
of Pawn:
result.position.pieces.white.pawns.uint64.setBit(bitIndex)
of Bishop:
result.position.pieces.white.bishops.uint64.setBit(bitIndex)
of Knight:
result.position.pieces.white.knights.uint64.setBit(bitIndex)
of Rook:
result.position.pieces.white.rooks.uint64.setBit(bitIndex)
of Queen:
result.position.pieces.white.queens.uint64.setBit(bitIndex)
of King:
if result.position.pieces.white.king != 0:
raise newException(ValueError, "invalid position: exactly one king of each color must be present")
result.position.pieces.white.king.uint64.setBit(bitIndex)
else:
discard
else:
discard
let square: Square = makeSquare(row, column)
piece = c.fromChar()
var b = result.position.pieces[piece.color][piece.kind]
b.setBit(square)
result.position.pieces[piece.color][piece.kind] = b
result.grid[square] = piece
inc(column)
of '/':
@ -340,9 +274,6 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
else:
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
inc(index)
if result.position.pieces.white.king == 0 or result.position.pieces.black.king == 0:
# Both kings must be on the board
raise newException(ValueError, "invalid position: exactly one king of each color must be present")
proc newDefaultChessboard*: ChessBoard {.inline.} =
@ -355,41 +286,8 @@ proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int =
## Returns the number of pieces with
## the given color and type in the
## current position
case color:
of White:
case kind:
of Pawn:
return self.position.pieces.white.pawns.uint64.countSetBits()
of Bishop:
return self.position.pieces.white.bishops.uint64.countSetBits()
of Knight:
return self.position.pieces.white.knights.uint64.countSetBits()
of Rook:
return self.position.pieces.white.rooks.uint64.countSetBits()
of Queen:
return self.position.pieces.white.queens.uint64.countSetBits()
of King:
return self.position.pieces.white.king.uint64.countSetBits()
else:
raise newException(ValueError, "invalid piece type")
of Black:
case kind:
of Pawn:
return self.position.pieces.black.pawns.uint64.countSetBits()
of Bishop:
return self.position.pieces.black.bishops.uint64.countSetBits()
of Knight:
return self.position.pieces.black.knights.uint64.countSetBits()
of Rook:
return self.position.pieces.black.rooks.uint64.countSetBits()
of Queen:
return self.position.pieces.black.queens.uint64.countSetBits()
of King:
return self.position.pieces.black.king.uint64.countSetBits()
else:
raise newException(ValueError, "invalid piece type")
of None:
raise newException(ValueError, "invalid piece color")
return self.position.pieces[color][kind].countSetBits()
func countPieces*(self: ChessBoard, piece: Piece): int {.inline.} =
@ -474,16 +372,9 @@ func getFlags*(move: Move): seq[MoveFlag] =
proc getOccupancyFor(self: ChessBoard, color: PieceColor): Bitboard =
## Get the occupancy bitboard for every piece of the given color
case color:
of White:
let b = self.position.pieces.white
return b.pawns or b.knights or b.bishops or b.rooks or b.queens or b.king
of Black:
let b = self.position.pieces.black
return b.pawns or b.knights or b.bishops or b.rooks or b.queens or b.king
else:
# huh?
discard
result = Bitboard(0)
for b in self.position.pieces[color][]:
result = result or b
proc getOccupancy(self: ChessBoard): Bitboard =
@ -738,81 +629,17 @@ 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]
case piece.color:
of White:
case piece.kind:
of Pawn:
self.position.pieces.white.pawns.uint64.clearBit(square.int8)
of Bishop:
self.position.pieces.white.bishops.uint64.clearBit(square.int8)
of Knight:
self.position.pieces.white.knights.uint64.clearBit(square.int8)
of Rook:
self.position.pieces.white.rooks.uint64.clearBit(square.int8)
of Queen:
self.position.pieces.white.queens.uint64.clearBit(square.int8)
of King:
self.position.pieces.white.king.uint64.clearBit(square.int8)
of Empty:
doAssert false, &"cannot remove empty white piece from {square}"
of Black:
case piece.kind:
of Pawn:
self.position.pieces.black.pawns.uint64.clearBit(square.int8)
of Bishop:
self.position.pieces.black.bishops.uint64.clearBit(square.int8)
of Knight:
self.position.pieces.black.knights.uint64.clearBit(square.int8)
of Rook:
self.position.pieces.black.rooks.uint64.clearBit(square.int8)
of Queen:
self.position.pieces.black.queens.uint64.clearBit(square.int8)
of King:
self.position.pieces.black.king.uint64.clearBit(square.int8)
of Empty:
doAssert false, &"cannot remove empty black piece from {square}"
else:
doAssert false, &"cannot remove empty piece from colorless square {square}"
var b = self.position.pieces[piece.color][piece.kind]
b.clearBit(square)
self.position.pieces[piece.color][piece.kind] = b
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.pieces.white.pawns.uint64.setBit(square.int8)
of Bishop:
self.position.pieces.white.bishops.uint64.setBit(square.int8)
of Knight:
self.position.pieces.white.knights.uint64.setBit(square.int8)
of Rook:
self.position.pieces.white.rooks.uint64.setBit(square.int8)
of Queen:
self.position.pieces.white.queens.uint64.setBit(square.int8)
of King:
self.position.pieces.white.king.uint64.setBit(square.int8)
else:
discard
of Black:
case piece.kind:
of Pawn:
self.position.pieces.black.pawns.uint64.setBit(square.int8)
of Bishop:
self.position.pieces.black.bishops.uint64.setBit(square.int8)
of Knight:
self.position.pieces.black.knights.uint64.setBit(square.int8)
of Rook:
self.position.pieces.black.rooks.uint64.setBit(square.int8)
of Queen:
self.position.pieces.black.queens.uint64.setBit(square.int8)
of King:
self.position.pieces.black.king.uint64.setBit(square.int8)
else:
discard
else:
discard
var b = self.position.pieces[piece.color][piece.kind]
b.setBit(square)
self.position.pieces[piece.color][piece.kind] = b
proc removePiece(self: ChessBoard, square: Square) =
@ -924,29 +751,29 @@ proc update*(self: ChessBoard) =
## in the chessboard
for i in 0..63:
self.grid[i] = nullPiece()
for sq in self.position.pieces.white.pawns:
for sq in self.position.pieces[White][Pawn]:
self.grid[sq] = Piece(color: White, kind: Pawn)
for sq in self.position.pieces.black.pawns:
for sq in self.position.pieces[Black][Pawn]:
self.grid[sq] = Piece(color: Black, kind: Pawn)
for sq in self.position.pieces.white.bishops:
for sq in self.position.pieces[White][Bishop]:
self.grid[sq] = Piece(color: White, kind: Bishop)
for sq in self.position.pieces.black.bishops:
for sq in self.position.pieces[Black][Bishop]:
self.grid[sq] = Piece(color: Black, kind: Bishop)
for sq in self.position.pieces.white.knights:
for sq in self.position.pieces[White][Knight]:
self.grid[sq] = Piece(color: White, kind: Knight)
for sq in self.position.pieces.black.knights:
for sq in self.position.pieces[Black][Knight]:
self.grid[sq] = Piece(color: Black, kind: Knight)
for sq in self.position.pieces.white.rooks:
for sq in self.position.pieces[White][Rook]:
self.grid[sq] = Piece(color: White, kind: Rook)
for sq in self.position.pieces.black.rooks:
for sq in self.position.pieces[Black][Rook]:
self.grid[sq] = Piece(color: Black, kind: Rook)
for sq in self.position.pieces.white.queens:
for sq in self.position.pieces[White][Queen]:
self.grid[sq] = Piece(color: White, kind: Queen)
for sq in self.position.pieces.black.queens:
for sq in self.position.pieces[Black][Queen]:
self.grid[sq] = Piece(color: Black, kind: Queen)
for sq in self.position.pieces.white.king:
for sq in self.position.pieces[White][King]:
self.grid[sq] = Piece(color: White, kind: King)
for sq in self.position.pieces.black.king:
for sq in self.position.pieces[Black][King]:
self.grid[sq] = Piece(color: Black, kind: King)
@ -973,9 +800,47 @@ proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
proc toChar*(piece: Piece): char =
case piece.kind:
of Bishop:
result = 'b'
of King:
result = 'k'
of Knight:
result = 'n'
of Pawn:
result = 'p'
of Queen:
result = 'q'
of Rook:
result = 'r'
else:
discard
if piece.color == White:
return char(piece.kind).toUpperAscii()
return char(piece.kind)
result = result.toUpperAscii()
proc fromChar*(c: char): Piece =
var
kind: PieceKind
color = Black
case c.toLowerAscii():
of 'b':
kind = Bishop
of 'k':
kind = King
of 'n':
kind = Knight
of 'p':
kind = Pawn
of 'q':
kind = Queen
of 'r':
kind = Rook
else:
discard
if c.isUpperAscii():
color = White
result = Piece(kind: kind, color: color)
proc `$`*(self: ChessBoard): string =

View File

@ -45,6 +45,13 @@ func `==`*(a, b: Bitboard): bool {.inline.} = a.uint64 == b.uint64
func `==`*(a: Bitboard, b: SomeInteger): bool {.inline.} = a.uint64 == b.uint64
func `!=`*(a, b: Bitboard): bool {.inline.} = a.uint64 != b.uint64
func `!=`*(a: Bitboard, b: SomeInteger): bool {.inline.} = a.uint64 != b.uint64
func countSetBits*(a: Bitboard): int = a.uint64.countSetBits()
func countLeadingZeroBits*(a: Bitboard): int = a.uint64.countLeadingZeroBits()
func countTrailingZeroBits*(a: Bitboard): int = a.uint64.countTrailingZeroBits()
func clearBit*(a: var Bitboard, bit: SomeInteger) = a.uint64.clearBit(bit)
func setBit*(a: var Bitboard, bit: SomeInteger) = a.uint64.setBit(bit)
func clearBit*(a: var Bitboard, bit: Square) = a.uint64.clearBit(bit.int)
func setBit*(a: var Bitboard, bit: Square) = a.uint64.setBit(bit.int)
func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64

View File

@ -9,25 +9,26 @@ type
PieceColor* = enum
## A piece color enumeration
None = 0'i8
White
Black
White = 0'i8
Black = 1
None
PieceKind* = enum
## A chess piece enumeration
Empty = 0'i8, # No piece
Bishop = 'b',
King = 'k'
Knight = 'n',
Pawn = 'p',
Queen = 'q',
Rook = 'r',
Bishop = 0'i8
King = 1
Knight = 2
Pawn = 3
Queen = 4
Rook = 5
Empty = 6 # No piece
Piece* = object
## A chess piece
color*: PieceColor
kind*: PieceKind
func nullPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
func nullSquare*: Square {.inline.} = Square(-1'i8)

View File

@ -421,6 +421,8 @@ proc commandLoop*: int =
echo &"Castling rights for {($board.getSideToMove()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
of "check":
echo &"{board.getSideToMove()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
of "quit":
return 0
else:
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
except IOError: