Improve modularity and add bitboard tests
This commit is contained in:
parent
e1ccdc728e
commit
b5181317ef
|
@ -0,0 +1,148 @@
|
|||
## Implements low-level bit operations
|
||||
|
||||
|
||||
import std/bitops
|
||||
import std/strutils
|
||||
|
||||
|
||||
import pieces
|
||||
|
||||
|
||||
type
|
||||
Bitboard* = distinct uint64
|
||||
## A bitboard
|
||||
|
||||
|
||||
# Overloaded operators and functions for our bitboard type
|
||||
func `shl`*(a: Bitboard, x: Positive): Bitboard = Bitboard(a.uint64 shl x)
|
||||
func `shr`*(a: Bitboard, x: Positive): Bitboard = Bitboard(a.uint64 shr x)
|
||||
func `and`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 and b.uint64)
|
||||
func `shr`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 and b.uint64)
|
||||
func `+`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 + b.uint64)
|
||||
func `-`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 - b.uint64)
|
||||
func `div`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 div b.uint64)
|
||||
func `*`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 * b.uint64)
|
||||
func `+`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 + b)
|
||||
func `-`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 - b.uint64)
|
||||
func `div`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 div b)
|
||||
func `*`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 * b)
|
||||
func `==`*(a, b: Bitboard): bool {.inline.} = a.uint64 == b.uint64
|
||||
func `==`*(a: Bitboard, b: SomeInteger): bool {.inline.} = a.uint64 == b.uint64
|
||||
|
||||
|
||||
func getFileMask*(file: Positive): Bitboard = Bitboard(0x101010101010101'u64) shl file
|
||||
func getRankMask*(rank: Positive): Bitboard = Bitboard(uint64.high()) shl Positive(8 * (rank + 1))
|
||||
func squareToBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64
|
||||
func squareToBitboard*(square: Square): Bitboard = squareToBitboard(coordToIndex(square))
|
||||
|
||||
proc bitboardToSquare*(b: Bitboard): Square = Square(b.uint64.countTrailingZeroBits().indexToCoord())
|
||||
|
||||
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
|
||||
while bits != 0:
|
||||
yield bits.bitboardToSquare()
|
||||
bits = bits and bits - 1
|
||||
|
||||
|
||||
iterator pairs*(self: Bitboard): tuple[i: int, sq: Square] =
|
||||
var i = 0
|
||||
for item in self:
|
||||
yield (i, item)
|
||||
inc(i)
|
||||
|
||||
|
||||
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 computeDiagonalpieces: array[14, Bitboard] {.compileTime.} =
|
||||
## Precomputes all the pieces for diagonals
|
||||
## at compile time
|
||||
result[0] = Bitboard(0x8040201008040201'u64)
|
||||
var
|
||||
col = 1
|
||||
i = 0
|
||||
# Left to right
|
||||
while col < 8:
|
||||
result[col] = Bitboard(0x8040201008040201'u64) shl (8 * col)
|
||||
inc(col)
|
||||
inc(i)
|
||||
result[i] = Bitboard(0x102040810204080'u64)
|
||||
inc(i)
|
||||
col = 1
|
||||
# Right to left
|
||||
while col < 7:
|
||||
result[i] = Bitboard(0x102040810204080'u64) shr (8 * col)
|
||||
inc(i)
|
||||
inc(col)
|
||||
|
||||
|
||||
const diagonalpieces = computeDiagonalpieces()
|
||||
|
||||
|
||||
func getDirectionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard =
|
||||
## Get a bitmask for the given direction for a piece
|
||||
## of the given color
|
||||
case color:
|
||||
of White:
|
||||
case direction:
|
||||
of Forward:
|
||||
return squareToBitboard(square) shl 8
|
||||
of Backward:
|
||||
return squareToBitboard(square) shr 8
|
||||
of ForwardRight:
|
||||
return squareToBitboard(square) shl 9
|
||||
of ForwardLeft:
|
||||
return squareToBitboard(square) shr 9
|
||||
of BackwardRight:
|
||||
return squareToBitboard(square) shl 17
|
||||
of BackwardLeft:
|
||||
return squareToBitboard(square) shr 17
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
# The directions for black are just the opposite of those for white,
|
||||
# so we avoid duplicating any code
|
||||
case direction:
|
||||
of Forward:
|
||||
return getDirectionMask(square, White, Backward)
|
||||
of Backward:
|
||||
return getDirectionMask(square, White, Forward)
|
||||
of ForwardRight:
|
||||
return getDirectionMask(square, White, ForwardLeft)
|
||||
of ForwardLeft:
|
||||
return getDirectionMask(square, White, ForwardRight)
|
||||
of BackwardRight:
|
||||
return getDirectionMask(square, White, BackwardLeft)
|
||||
of BackwardLeft:
|
||||
return getDirectionMask(square, White, BackwardRight)
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,41 @@
|
|||
## Handling of moves
|
||||
import pieces
|
||||
|
||||
|
||||
type
|
||||
MoveFlag* = enum
|
||||
## An enumeration of move flags
|
||||
Default = 0'u16, # No flag
|
||||
EnPassant = 1, # Move is a capture with en passant
|
||||
Capture = 2, # Move is a capture
|
||||
DoublePush = 4, # Move is a double pawn push
|
||||
# Castling metadata
|
||||
CastleLong = 8,
|
||||
CastleShort = 16,
|
||||
# Pawn promotion metadata
|
||||
PromoteToQueen = 32,
|
||||
PromoteToRook = 64,
|
||||
PromoteToBishop = 128,
|
||||
PromoteToKnight = 256
|
||||
|
||||
Move* = object
|
||||
## A chess move
|
||||
startSquare*: Square
|
||||
targetSquare*: Square
|
||||
flags*: uint16
|
||||
|
||||
|
||||
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 createMove*(startSquare, targetSquare: Square, flags: seq[MoveFlag] = @[]): Move =
|
||||
result = Move(startSquare: startSquare, targetSquare: targetSquare, flags: Default.uint16)
|
||||
for flag in flags:
|
||||
result.flags = result.flags or flag.uint16
|
||||
|
||||
|
||||
func nullMove*: Move {.inline.} = createMove(nullSquare(), nullSquare())
|
|
@ -0,0 +1,96 @@
|
|||
## Low-level handling of squares, board indeces and pieces
|
||||
import std/strutils
|
||||
import std/strformat
|
||||
|
||||
|
||||
type
|
||||
Square* = tuple[rank, file: int8]
|
||||
## A square
|
||||
|
||||
PieceColor* = enum
|
||||
## A piece color enumeration
|
||||
None = 0'i8,
|
||||
White,
|
||||
Black
|
||||
|
||||
PieceKind* = enum
|
||||
## A chess piece enumeration
|
||||
Empty = 0'i8, # No piece
|
||||
Bishop = 'b',
|
||||
King = 'k'
|
||||
Knight = 'n',
|
||||
Pawn = 'p',
|
||||
Queen = 'q',
|
||||
Rook = 'r',
|
||||
|
||||
Direction* = enum
|
||||
## A move direction enumeration
|
||||
Forward,
|
||||
Backward,
|
||||
Left,
|
||||
Right
|
||||
ForwardLeft,
|
||||
ForwardRight,
|
||||
BackwardLeft,
|
||||
BackwardRight
|
||||
|
||||
Piece* = object
|
||||
## A chess piece
|
||||
color*: PieceColor
|
||||
kind*: PieceKind
|
||||
|
||||
|
||||
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 div 8).int8, (index mod 8).int8)
|
||||
func nullPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
|
||||
func nullSquare*: Square {.inline.} = (-1 , -1)
|
||||
func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White)
|
||||
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
|
||||
func makeSquare*(rank, file: SomeInteger): Square = (rank: rank.int8, file: file.int8)
|
||||
|
||||
|
||||
func fileToColumn*(file: int): int8 {.inline.} =
|
||||
## Converts a chess file (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
|
||||
const indeces: array[8, int8] = [7, 6, 5, 4, 3, 2, 1, 0]
|
||||
return indeces[file - 1]
|
||||
|
||||
|
||||
func rowToRank*(row: int): int8 {.inline.} =
|
||||
## Converts a row into our grid into
|
||||
## a chess rank
|
||||
const indeces: array[8, int8] = [8, 7, 6, 5, 4, 3, 2, 1]
|
||||
return indeces[row]
|
||||
|
||||
|
||||
func algebraicToSquare*(s: string): Square =
|
||||
## Converts a square square 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]}')")
|
||||
|
||||
let rank = int8(uint8(s[0]) - uint8('a'))
|
||||
# Convert the file character to a number
|
||||
let file = fileToColumn(int8(uint8(s[1]) - uint8('0')))
|
||||
return (file, rank)
|
||||
|
||||
|
||||
func squareToAlgebraic*(square: Square): string {.inline.} =
|
||||
## Converts a square from our internal rank/file
|
||||
## notation to a square in algebraic notation
|
||||
return &"{rowToRank(square.rank)}{char(uint8(square.file) + uint8('a'))}"
|
Loading…
Reference in New Issue