Improve bitboard interface

This commit is contained in:
Mattia Giambirtone 2024-04-15 12:45:47 +02:00
parent 6115191ed6
commit c9988cd939
1 changed files with 66 additions and 23 deletions

View File

@ -131,15 +131,16 @@ type
# A bunch of simple utility functions and forward declarations
func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
func emptySquare*: Square {.inline.} = (-1 , -1)
func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White)
proc algebraicToSquare*(s: string): Square {.inline.}
func algebraicToSquare*(s: string): Square {.inline.}
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.}
func emptyMove*: Move {.inline.} = Move(startSquare: emptySquare(), targetSquare: emptySquare())
func `+`*(a, b: Square): Square = (a.rank + b.rank, a.file + b.file)
func `-`*(a: Square): Square = (-a.rank, -a.file)
func `-`*(a, b: Square): Square = (a.rank - b.rank, a.file - b.file)
func `+`*(a, b: Square): Square {.inline.} = (a.rank + b.rank, a.file + b.file)
func `-`*(a: Square): Square {.inline.} = (-a.rank, -a.file)
func `-`*(a, b: Square): Square{.inline.} = (a.rank - b.rank, a.file - b.file)
func isValid*(a: Square): bool {.inline.} = a.rank in 0..7 and a.file in 0..7
func isLightSquare(a: Square): bool {.inline.} = (a.rank + a.file and 2) == 0
proc generateMoves(self: ChessBoard, square: Square): seq[Move]
@ -169,6 +170,16 @@ proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} =
proc updateBoard*(self: ChessBoard)
func createMove(startSquare, targetSquare: string, flags: seq[MoveFlag] = @[]): Move =
result = Move(startSquare: startSquare.algebraicToSquare(),
targetSquare: targetSquare.algebraicToSquare(), flags: Default.uint16)
for flag in flags:
result.flags = result.flags or flag.uint16
func makeSquare(rank, file: SomeInteger): Square = (rank: rank.int8, file: file.int8)
# Due to our board layout, directions of movement are reversed for white and black, so
# we need these helpers to avoid going mad with integer tuples and minus signs everywhere
func topLeftDiagonal(color: PieceColor): Square {.inline.} = (if color == White: (-1, -1) else: (1, 1))
@ -285,7 +296,7 @@ func getStartRank(piece: Piece): int {.inline.} =
return 0
func getKingStartingPosition(color: PieceColor): Square {.inline.} =
func getKingStartingSquare(color: PieceColor): Square {.inline.} =
## Retrieves the starting square of the king
## for the given color
case color:
@ -334,7 +345,7 @@ proc newChessboard: ChessBoard =
# Handy wrappers and utilities for handling low-level stuff
func coordToIndex(row, col: SomeInteger): int {.inline.} = (row * 8) + col
func coordToIndex(square: Square): int {.inline.} = coordToIndex(square.rank, square.file)
func indexToCoord(index: SomeInteger): Square {.inline.} = (index / 8, index mod 8)
func indexToCoord(index: SomeInteger): Square {.inline.} = ((index div 8).int8, (index mod 8).int8)
# Indexing operations
func `[]`(self: array[64, Piece], row, column: Natural): Piece {.inline.} = self[coordToIndex(row, column)]
@ -360,6 +371,40 @@ func toBin(x: Bitboard, b: Positive = 64): string = toBin(BiggestInt(x), b)
func toBin(x: uint64, b: Positive = 64): string = toBin(Bitboard(x), b)
iterator items(self: Bitboard): Square =
## Iterates ove the given bitboard
## and returns all the squares that
## are set
var bits = self.uint64
while bits != 0:
yield Square(bits.countTrailingZeroBits().indexToCoord())
bits = bits and bits - 1
func pretty(self: Bitboard): string =
iterator items(self: Bitboard): uint8 =
## Iterates over all the bits in the
## given bitboard
for i in 0..63:
yield self.uint64.bitsliced(i..i).uint8
iterator pairs(self: Bitboard): (int, uint8) =
var i = 0
for bit in self:
yield (i, bit)
inc(i)
## Returns a prettyfied version of
## the given bitboard
for i, bit in self:
if i > 0 and i mod 8 == 0:
result &= "\n"
result &= $bit
func computeDiagonalBitboards: array[14, Bitboard] {.compileTime.} =
## Precomputes all the bitboards for diagonals
## at compile time
@ -702,7 +747,7 @@ func rowToFile(row: int): int8 {.inline.} =
return indeces[row]
proc algebraicToSquare*(s: string): Square =
func algebraicToSquare*(s: string): Square =
## Converts a square square from algebraic
## notation to its corresponding row and column
## in the chess grid (0 indexed)
@ -722,7 +767,7 @@ proc algebraicToSquare*(s: string): Square =
func squareToAlgebraic*(square: Square): string {.inline.} =
## Converts a square from our internal row, column
## Converts a square from our internal rank/file
## notation to a square in algebraic notation
return &"{char(uint8(square.file) + uint8('a'))}{rowToFile(square.rank)}"
@ -875,7 +920,7 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
# perform them because they're less expensive
# King is not on its starting square
if self.getKing(color) != getKingStartingPosition(color):
if self.getKing(color) != getKingStartingSquare(color):
return (false, false)
if self.inCheck(color):
# King can not castle out of check
@ -2334,7 +2379,7 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discarda
var move = Move(startSquare: startSquare, targetSquare: targetSquare, flags: flags)
if board.getPiece(move.startSquare).kind == King and move.startSquare == board.getActiveColor().getKingStartingPosition():
if board.getPiece(move.startSquare).kind == King and move.startSquare == board.getActiveColor().getKingStartingSquare():
if move.targetSquare == move.startSquare + longCastleKing():
move.flags = move.flags or CastleLong.uint16
elif move.targetSquare == move.startSquare + shortCastleKing():
@ -2527,13 +2572,6 @@ proc main: int =
return 0
func createMove(startSquare, targetSquare: string, flags: seq[MoveFlag] = @[]): Move =
result = Move(startSquare: startSquare.algebraicToSquare(),
targetSquare: targetSquare.algebraicToSquare(), flags: Default.uint16)
for flag in flags:
result.flags = result.flags or flag.uint16
when isMainModule:
@ -2588,10 +2626,15 @@ when isMainModule:
# Queens
testPiece(b.getPiece("d1"), Queen, White)
testPiece(b.getPiece("d8"), Queen, Black)
echo b.getBitboard(b.getPiece("a2")).toBin()
b.makeMove(createMove("a2", "a4", @[DoublePush]))
echo b.getBitboard(b.getPiece("a4")).toBin()
echo b.getEnPassantTarget()
setControlCHook(proc () {.noconv.} = quit(0))
# quit(main())
b.makeMove(createMove("a2", "a4", @[DoublePush]))
let whitePawns = b.getBitboard(Pawn, White)
echo whitePawns.pretty()
for square in whitePawns:
echo square
doAssert b.getEnPassantTarget() == makeSquare(5, 0)
#[setControlCHook(proc () {.noconv.} = quit(0))
quit(main())]#