Refactor directory structure. Fix magic bitboard generation and add utilities to dump them to disk
This commit is contained in:
parent
6a548bf372
commit
82cef11cc4
|
@ -0,0 +1,24 @@
|
||||||
|
# Package
|
||||||
|
|
||||||
|
version = "0.1.0"
|
||||||
|
author = "nocturn9x"
|
||||||
|
description = "A chess engine written in nim"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
srcDir = "src"
|
||||||
|
binDir = "bin"
|
||||||
|
installExt = @["nim"]
|
||||||
|
bin = @["nimfish"]
|
||||||
|
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
requires "nim >= 2.1.1"
|
||||||
|
requires "jsony >= 1.1.5"
|
||||||
|
|
||||||
|
|
||||||
|
after build:
|
||||||
|
exec "nimble test"
|
||||||
|
|
||||||
|
|
||||||
|
task test, "Runs the test suite":
|
||||||
|
exec "python tests/suite.py -d 5 --bulk"
|
|
@ -18,9 +18,11 @@ import std/times
|
||||||
import std/math
|
import std/math
|
||||||
import std/bitops
|
import std/bitops
|
||||||
|
|
||||||
import bitboards
|
|
||||||
import pieces
|
import src/bitboards
|
||||||
import moves
|
import src/magics
|
||||||
|
import src/pieces
|
||||||
|
import src/moves
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -648,9 +650,49 @@ proc generatePawnMoves(self: ChessBoard, moves: var MoveList) =
|
||||||
self.generatePawnPromotions(moves)
|
self.generatePawnPromotions(moves)
|
||||||
|
|
||||||
|
|
||||||
|
proc generateRookMovements(self: ChessBoard, moves: var MoveList) =
|
||||||
|
## Helper of generateRookMoves to generate all non-capture
|
||||||
|
## rook moves
|
||||||
|
let
|
||||||
|
sideToMove = self.getSideToMove()
|
||||||
|
occupancy = self.getOccupancy()
|
||||||
|
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||||
|
rooks = self.getBitboard(Rook, sideToMove)
|
||||||
|
for square in rooks:
|
||||||
|
let blockers = occupancy and Rook.getRelevantBlockers(square)
|
||||||
|
var moveset = getRookMoves(square, blockers)
|
||||||
|
# Can't capture our own pieces
|
||||||
|
moveset = moveset and not friendlyPieces
|
||||||
|
for target in moveset:
|
||||||
|
moves.add(createMove(square, target))
|
||||||
|
|
||||||
|
|
||||||
|
proc generateRookCaptures(self: ChessBoard, moves: var MoveList) =
|
||||||
|
## Helper of generateRookMoves to generate all capture
|
||||||
|
## rook moves
|
||||||
|
let
|
||||||
|
sideToMove = self.getSideToMove()
|
||||||
|
occupancy = self.getOccupancy()
|
||||||
|
enemyPieces = self.getCapturablePieces(sideToMove.opposite())
|
||||||
|
rooks = self.getBitboard(Rook, sideToMove)
|
||||||
|
for square in rooks:
|
||||||
|
let blockers = occupancy and Rook.getRelevantBlockers(square)
|
||||||
|
var moveset = getRookMoves(square, blockers)
|
||||||
|
# Can only cature enemy pieces
|
||||||
|
moveset = moveset and not enemyPieces
|
||||||
|
for target in moveset:
|
||||||
|
moves.add(createMove(square, target))
|
||||||
|
|
||||||
|
proc generateRookMoves(self: ChessBoard, moves: var MoveList) =
|
||||||
|
## Helper of generateSlidingMoves to generate rook moves
|
||||||
|
self.generateRookMovements(moves)
|
||||||
|
self.generateRookCaptures(moves)
|
||||||
|
|
||||||
|
|
||||||
proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) =
|
proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) =
|
||||||
## Generates all legal sliding moves for the side to move
|
## Generates all legal sliding moves for the side to move
|
||||||
|
self.generateRookMoves(moves)
|
||||||
|
|
||||||
|
|
||||||
proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
|
proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
|
||||||
## Generates all legal king moves for the side to move
|
## Generates all legal king moves for the side to move
|
||||||
|
@ -758,6 +800,7 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) =
|
||||||
self.generatePawnMoves(moves)
|
self.generatePawnMoves(moves)
|
||||||
self.generateKingMoves(moves)
|
self.generateKingMoves(moves)
|
||||||
self.generateKnightMoves(moves)
|
self.generateKnightMoves(moves)
|
||||||
|
self.generateRookMoves(moves)
|
||||||
# TODO: all pieces
|
# TODO: all pieces
|
||||||
|
|
||||||
|
|
||||||
|
@ -962,25 +1005,26 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
|
|
||||||
# Castling check: have the rooks moved?
|
# Castling check: have the rooks moved?
|
||||||
if piece.kind == Rook:
|
if piece.kind == Rook:
|
||||||
case piece.color:
|
discard
|
||||||
of White:
|
# case piece.color:
|
||||||
if rowFromSquare(move.startSquare) == piece.getStartRank():
|
# of White:
|
||||||
if colFromSquare(move.startSquare) == 0:
|
# if rowFromSquare(move.startSquare) == piece.getStartRank():
|
||||||
# Queen side
|
# if columnFromSquare(move.startSquare) == 0:
|
||||||
castlingAvailable.white.queen = false
|
# # Queen side
|
||||||
elif colfromSquare(move.startSquare) == 7:
|
# castlingAvailable.white.queen = false
|
||||||
# King side
|
# elif columnfromSquare(move.startSquare) == 7:
|
||||||
castlingAvailable.white.king = false
|
# # King side
|
||||||
of Black:
|
# castlingAvailable.white.king = false
|
||||||
if rowFromSquare(move.startSquare) == piece.getStartRank():
|
# of Black:
|
||||||
if colFromSquare(move.startSquare) == 0:
|
# if rowFromSquare(move.startSquare) == piece.getStartRank():
|
||||||
# Queen side
|
# if columnFromSquare(move.startSquare) == 0:
|
||||||
castlingAvailable.black.queen = false
|
# # Queen side
|
||||||
elif colFromSquare(move.startSquare) == 7:
|
# castlingAvailable.black.queen = false
|
||||||
# King side
|
# elif columnFromSquare(move.startSquare) == 7:
|
||||||
castlingAvailable.black.king = false
|
# # King side
|
||||||
else:
|
# castlingAvailable.black.king = false
|
||||||
discard
|
# else:
|
||||||
|
# discard
|
||||||
# Has a rook been captured?
|
# Has a rook been captured?
|
||||||
if move.isCapture():
|
if move.isCapture():
|
||||||
let captured = self.grid[move.targetSquare]
|
let captured = self.grid[move.targetSquare]
|
||||||
|
@ -1475,8 +1519,8 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discarda
|
||||||
if board.grid[targetSquare].kind != Empty:
|
if board.grid[targetSquare].kind != Empty:
|
||||||
flags.add(Capture)
|
flags.add(Capture)
|
||||||
|
|
||||||
elif board.grid[startSquare].kind == Pawn and abs(rowFromSquare(startSquare) - rowFromSquare(targetSquare)) == 2:
|
#elif board.grid[startSquare].kind == Pawn and abs(rowFromSquare(startSquare) - rowFromSquare(targetSquare)) == 2:
|
||||||
flags.add(DoublePush)
|
# flags.add(DoublePush)
|
||||||
|
|
||||||
if len(moveString) == 5:
|
if len(moveString) == 5:
|
||||||
# Promotion
|
# Promotion
|
||||||
|
@ -1794,13 +1838,15 @@ when isMainModule:
|
||||||
testPieceBitboard(blackRooks, blackRookSquares)
|
testPieceBitboard(blackRooks, blackRookSquares)
|
||||||
testPieceBitboard(blackQueens, blackQueenSquares)
|
testPieceBitboard(blackQueens, blackQueenSquares)
|
||||||
testPieceBitboard(blackKing, blackKingSquares)
|
testPieceBitboard(blackKing, blackKingSquares)
|
||||||
|
|
||||||
|
|
||||||
|
b = newChessboardFromFEN("8/5R2/8/8/7k/8/5K2/8 w - - 0 1")
|
||||||
var m = MoveList()
|
var m = MoveList()
|
||||||
b.generateMoves(m)
|
b.generateRookMovements(m)
|
||||||
echo &"There are {len(m)} legal moves for {b.getSideToMove()} at {b.toFEN()}: "
|
echo &"There are {len(m)} legal moves for {b.getSideToMove()} at {b.toFEN()}: "
|
||||||
for move in m:
|
for move in m:
|
||||||
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
|
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
|
||||||
echo b.pretty()
|
echo b.pretty()
|
||||||
echo b.getAttacksTo("f3".toSquare(), White)
|
|
||||||
# setControlCHook(proc () {.noconv.} = quit(0))
|
# setControlCHook(proc () {.noconv.} = quit(0))
|
||||||
# quit(main())
|
# quit(main())
|
|
@ -68,6 +68,8 @@ func createMove*(startSquare, targetSquare: Bitboard, flags: varargs[MoveFlag]):
|
||||||
func toBin*(x: Bitboard, b: Positive = 64): string = toBin(BiggestInt(x), b)
|
func toBin*(x: Bitboard, b: Positive = 64): string = toBin(BiggestInt(x), b)
|
||||||
func toBin*(x: uint64, b: Positive = 64): string = toBin(Bitboard(x), b)
|
func toBin*(x: uint64, b: Positive = 64): string = toBin(Bitboard(x), b)
|
||||||
|
|
||||||
|
func contains*(self: Bitboard, square: Square): bool = (self and square.toBitboard()) != 0
|
||||||
|
|
||||||
|
|
||||||
iterator items*(self: Bitboard): Square =
|
iterator items*(self: Bitboard): Square =
|
||||||
## Iterates ove the given bitboard
|
## Iterates ove the given bitboard
|
|
@ -8,6 +8,11 @@ import pieces
|
||||||
|
|
||||||
import std/random
|
import std/random
|
||||||
import std/bitops
|
import std/bitops
|
||||||
|
import std/tables
|
||||||
|
import std/os
|
||||||
|
|
||||||
|
|
||||||
|
import jsony
|
||||||
|
|
||||||
|
|
||||||
export pieces
|
export pieces
|
||||||
|
@ -23,9 +28,8 @@ type
|
||||||
|
|
||||||
|
|
||||||
# Yeah uh, don't look too closely at this...
|
# Yeah uh, don't look too closely at this...
|
||||||
proc generateRookMasks(blockers: bool = false): array[64, Bitboard] {.compileTime.} =
|
proc generateRookBlockers: array[64, Bitboard] {.compileTime.} =
|
||||||
## Generates all movement masks for rooks (only generates
|
## Generates all blocker masks for rooks
|
||||||
## blocker masks if blockers equals true)
|
|
||||||
for rank in 0..7:
|
for rank in 0..7:
|
||||||
for file in 0..7:
|
for file in 0..7:
|
||||||
let
|
let
|
||||||
|
@ -37,37 +41,36 @@ proc generateRookMasks(blockers: bool = false): array[64, Bitboard] {.compileTim
|
||||||
last = makeSquare(rank, 7).toBitboard()
|
last = makeSquare(rank, 7).toBitboard()
|
||||||
while true:
|
while true:
|
||||||
current = current.rightRelativeTo(White)
|
current = current.rightRelativeTo(White)
|
||||||
if (current == last and blockers) or current == 0:
|
if current == last or current == 0:
|
||||||
break
|
break
|
||||||
result[i] = result[i] or current
|
result[i] = result[i] or current
|
||||||
current = bitboard
|
current = bitboard
|
||||||
last = makeSquare(rank, 0).toBitboard()
|
last = makeSquare(rank, 0).toBitboard()
|
||||||
while true:
|
while true:
|
||||||
current = current.leftRelativeTo(White)
|
current = current.leftRelativeTo(White)
|
||||||
if (current == last and blockers) or current == 0:
|
if current == last or current == 0:
|
||||||
break
|
break
|
||||||
result[i] = result[i] or current
|
result[i] = result[i] or current
|
||||||
current = bitboard
|
current = bitboard
|
||||||
last = makeSquare(0, file).toBitboard()
|
last = makeSquare(0, file).toBitboard()
|
||||||
while true:
|
while true:
|
||||||
current = current.forwardRelativeTo(White)
|
current = current.forwardRelativeTo(White)
|
||||||
if (current == last and blockers) or current == 0:
|
if current == last or current == 0:
|
||||||
break
|
break
|
||||||
result[i] = result[i] or current
|
result[i] = result[i] or current
|
||||||
current = bitboard
|
current = bitboard
|
||||||
last = makeSquare(7, file).toBitboard()
|
last = makeSquare(7, file).toBitboard()
|
||||||
while true:
|
while true:
|
||||||
current = current.backwardRelativeTo(White)
|
current = current.backwardRelativeTo(White)
|
||||||
if (current == last and blockers) or current == 0:
|
if current == last or current == 0:
|
||||||
break
|
break
|
||||||
result[i] = result[i] or current
|
result[i] = result[i] or current
|
||||||
|
|
||||||
|
|
||||||
# Okay this is fucking clever tho. Which is obvious, considering I didn't come up with it.
|
# Okay this is fucking clever tho. Which is obvious, considering I didn't come up with it.
|
||||||
# Or, well, the trick at the end isn't mine
|
# Or, well, the trick at the end isn't mine
|
||||||
func generateBishopMasks(blockers = false): array[64, Bitboard] {.compileTime.} =
|
func generateBishopBlockers: array[64, Bitboard] {.compileTime.} =
|
||||||
## Generates all movement masks for bishops (only generates
|
## Generates all blocker masks for bishops
|
||||||
## blocker masks if blockers equals true)
|
|
||||||
for rank in 0..7:
|
for rank in 0..7:
|
||||||
for file in 0..7:
|
for file in 0..7:
|
||||||
# Generate all possible movement masks
|
# Generate all possible movement masks
|
||||||
|
@ -100,37 +103,36 @@ func generateBishopMasks(blockers = false): array[64, Bitboard] {.compileTime.}
|
||||||
if current == 0:
|
if current == 0:
|
||||||
break
|
break
|
||||||
result[i] = result[i] or current
|
result[i] = result[i] or current
|
||||||
if blockers:
|
# Mask off the edges
|
||||||
# Mask off the edges
|
|
||||||
|
|
||||||
# Yeah, this is the trick. I know, not a big deal, but
|
# Yeah, this is the trick. I know, not a big deal, but
|
||||||
# I'm an idiot so what do I know. Credit to @__arandomnoob
|
# I'm an idiot so what do I know. Credit to @__arandomnoob
|
||||||
# on the engine programming discord server for the tip!
|
# on the engine programming discord server for the tip!
|
||||||
result[i] = result[i] and not getFileMask(0)
|
result[i] = result[i] and not getFileMask(0)
|
||||||
result[i] = result[i] and not getFileMask(7)
|
result[i] = result[i] and not getFileMask(7)
|
||||||
result[i] = result[i] and not getRankMask(0)
|
result[i] = result[i] and not getRankMask(0)
|
||||||
result[i] = result[i] and not getRankMask(7)
|
result[i] = result[i] and not getRankMask(7)
|
||||||
|
|
||||||
|
|
||||||
func getIndex(magic: MagicEntry, blockers: Bitboard): uint {.inline.} =
|
func getIndex*(magic: MagicEntry, blockers: Bitboard): uint {.inline.} =
|
||||||
## Computes an index into the magic bitboard table using
|
## Computes an index into the magic bitboard table using
|
||||||
## the given magic entry and the blockers bitboard
|
## the given magic entry and the blockers bitboard
|
||||||
let
|
let
|
||||||
blockers = blockers and magic.mask
|
blockers = blockers and magic.mask
|
||||||
hash = blockers * magic.value
|
hash = blockers * magic.value
|
||||||
index = hash shr (64 - magic.indexBits)
|
index = hash shr (64'u8 - magic.indexBits)
|
||||||
return index.uint
|
return index.uint
|
||||||
|
|
||||||
|
|
||||||
# Magic number tables and their corresponding moves
|
# Magic number tables and their corresponding moves
|
||||||
var
|
var
|
||||||
ROOK_MAGICS: seq[MagicEntry]
|
ROOK_MAGICS: array[64, MagicEntry]
|
||||||
ROOK_MOVES: array[64, seq[Bitboard]]
|
ROOK_MOVES: array[64, seq[Bitboard]]
|
||||||
BISHOP_MAGICS: seq[MagicEntry]
|
BISHOP_MAGICS: array[64, MagicEntry]
|
||||||
BISHOP_MOVES: array[64, seq[Bitboard]]
|
BISHOP_MOVES: array[64, seq[Bitboard]]
|
||||||
|
|
||||||
|
|
||||||
proc getRookMoves(square: Square, blockers: Bitboard): Bitboard =
|
proc getRookMoves*(square: Square, blockers: Bitboard): Bitboard =
|
||||||
## Returns the move bitboard for the rook at the given
|
## Returns the move bitboard for the rook at the given
|
||||||
## square with the given blockers bitboard
|
## square with the given blockers bitboard
|
||||||
let
|
let
|
||||||
|
@ -139,7 +141,7 @@ proc getRookMoves(square: Square, blockers: Bitboard): Bitboard =
|
||||||
return moves[getIndex(magic, blockers)]
|
return moves[getIndex(magic, blockers)]
|
||||||
|
|
||||||
|
|
||||||
proc getBishopMoves(square: Square, blockers: Bitboard): Bitboard =
|
proc getBishopMoves*(square: Square, blockers: Bitboard): Bitboard =
|
||||||
## Returns the move bitboard for the bishop at the given
|
## Returns the move bitboard for the bishop at the given
|
||||||
## square with the given blockers bitboard
|
## square with the given blockers bitboard
|
||||||
let
|
let
|
||||||
|
@ -153,13 +155,11 @@ proc getBishopMoves(square: Square, blockers: Bitboard): Bitboard =
|
||||||
# regardless of color
|
# regardless of color
|
||||||
const
|
const
|
||||||
# mfw Nim's compile time VM *graciously* allows me to call perfectly valid code: :D
|
# mfw Nim's compile time VM *graciously* allows me to call perfectly valid code: :D
|
||||||
ROOK_BLOCKERS = generateRookMasks(blockers=true)
|
ROOK_BLOCKERS = generateRookBlockers()
|
||||||
BISHOP_BLOCKERS = generateBishopMasks(blockers=true)
|
BISHOP_BLOCKERS = generateBishopBlockers()
|
||||||
ROOK_MOVEMENTS = generateRookMasks()
|
|
||||||
BISHOP_MOVEMENTS = generateBishopMasks()
|
|
||||||
|
|
||||||
|
|
||||||
func getRelevantBlockers(kind: PieceKind, square: Square): Bitboard =
|
func getRelevantBlockers*(kind: PieceKind, square: Square): Bitboard =
|
||||||
## Returns the relevant blockers mask for the given piece
|
## Returns the relevant blockers mask for the given piece
|
||||||
## type at the given square
|
## type at the given square
|
||||||
case kind:
|
case kind:
|
||||||
|
@ -169,7 +169,40 @@ func getRelevantBlockers(kind: PieceKind, square: Square): Bitboard =
|
||||||
return BISHOP_BLOCKERS[square.uint]
|
return BISHOP_BLOCKERS[square.uint]
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
# Thanks analog :D
|
||||||
|
const
|
||||||
|
ROOK_DELTAS = [(1, 0), (0, -1), (-1, 0), (0, 1)]
|
||||||
|
BISHOP_DELTAS = [(1, 1), (1, -1), (-1, -1), (-1, 1)]
|
||||||
|
# These are technically (file, rank), but it's all symmetric anyway
|
||||||
|
|
||||||
|
|
||||||
|
func tryOffset(square: Square, df, dr: SomeInteger): Square =
|
||||||
|
let
|
||||||
|
file = fileFromSquare(square)
|
||||||
|
rank = rankFromSquare(square)
|
||||||
|
if file + df notin 0..7:
|
||||||
|
return nullSquare()
|
||||||
|
if rank + dr notin 0..7:
|
||||||
|
return nullSquare()
|
||||||
|
return makeSquare(rank + dr, file + df)
|
||||||
|
|
||||||
|
|
||||||
|
proc getMoveSet*(kind: PieceKind, square: Square, blocker: Bitboard): Bitboard =
|
||||||
|
## A naive implementation of sliding attacks. Returns the moves that can
|
||||||
|
## be performed from the given piece at the given square with the given
|
||||||
|
## blocker mask
|
||||||
|
result = Bitboard(0)
|
||||||
|
let deltas = if kind == Rook: ROOK_DELTAS else: BISHOP_DELTAS
|
||||||
|
for (file, rank) in deltas:
|
||||||
|
var ray = square
|
||||||
|
while not blocker.contains(ray):
|
||||||
|
if (let shifted = ray.tryOffset(file, rank); shifted) != nullSquare():
|
||||||
|
ray = shifted
|
||||||
|
result = result or ray.toBitboard()
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
proc attemptMagicTableCreation(kind: PieceKind, square: Square, entry: MagicEntry): tuple[success: bool, table: seq[Bitboard]] =
|
proc attemptMagicTableCreation(kind: PieceKind, square: Square, entry: MagicEntry): tuple[success: bool, table: seq[Bitboard]] =
|
||||||
## Tries to create a magic bitboard table for the given piece
|
## Tries to create a magic bitboard table for the given piece
|
||||||
|
@ -185,19 +218,16 @@ proc attemptMagicTableCreation(kind: PieceKind, square: Square, entry: MagicEntr
|
||||||
for blocker in entry.mask.subsets():
|
for blocker in entry.mask.subsets():
|
||||||
let index = getIndex(entry, blocker)
|
let index = getIndex(entry, blocker)
|
||||||
# Get the moves the piece can make from the given
|
# Get the moves the piece can make from the given
|
||||||
# square with this specific blocker configuration
|
# square with this specific blocker configuration.
|
||||||
var moves: Bitboard
|
# Note that this will return the same set of moves
|
||||||
case kind:
|
# for several different blocker configurations, as
|
||||||
of Rook:
|
# many of them (while different) produce the same
|
||||||
moves = ROOK_MOVEMENTS[square.uint]
|
# results
|
||||||
of Bishop:
|
var moves = kind.getMoveSet(square, blocker)
|
||||||
moves = BISHOP_MOVEMENTS[square.uint]
|
|
||||||
else:
|
|
||||||
discard
|
|
||||||
if result.table[index] == 0:
|
if result.table[index] == 0:
|
||||||
# No entry here, yet, so no problem!
|
# No entry here, yet, so no problem!
|
||||||
result.table[index] = moves
|
result.table[index] = moves
|
||||||
elif (result.table[index] or blocker) != moves:
|
elif result.table[index] != moves:
|
||||||
# We found a non-constructive collision, fail :(
|
# We found a non-constructive collision, fail :(
|
||||||
# Notes for future self: A "constructive" collision
|
# Notes for future self: A "constructive" collision
|
||||||
# is one which doesn't affect the result, because some
|
# is one which doesn't affect the result, because some
|
||||||
|
@ -214,14 +244,14 @@ proc attemptMagicTableCreation(kind: PieceKind, square: Square, entry: MagicEntr
|
||||||
# direction to find what pieces are actually blocking the
|
# direction to find what pieces are actually blocking the
|
||||||
# the slider's path and which aren't for every single lookup,
|
# the slider's path and which aren't for every single lookup,
|
||||||
# which is the whole thing we're trying to avoid by doing all
|
# which is the whole thing we're trying to avoid by doing all
|
||||||
# this magic bitboard stuff and it is basically how the old mailbox
|
# this magic bitboard stuff, and it is basically how the old mailbox
|
||||||
# move generator worked anyway (thanks to Sebastian Lague on YouTube
|
# move generator worked anyway (thanks to Sebastian Lague on YouTube
|
||||||
# for the insight)
|
# for the insight)
|
||||||
return (false, @[])
|
return (false, @[])
|
||||||
# We have found a constructive collision: all good
|
# We have found a constructive collision: all good
|
||||||
|
|
||||||
|
|
||||||
proc findMagic(kind: PieceKind, square: Square, indexBits: uint8): tuple[entry: MagicEntry, table: seq[Bitboard]] =
|
proc findMagic(kind: PieceKind, square: Square, indexBits: uint8): tuple[entry: MagicEntry, table: seq[Bitboard], iterations: int] =
|
||||||
## Constructs a (sort of) perfect hash function that fits all
|
## Constructs a (sort of) perfect hash function that fits all
|
||||||
## the possible blocking configurations for the given piece at
|
## the possible blocking configurations for the given piece at
|
||||||
## the given square into a table of size 2^indexBits
|
## the given square into a table of size 2^indexBits
|
||||||
|
@ -229,32 +259,92 @@ proc findMagic(kind: PieceKind, square: Square, indexBits: uint8): tuple[entry:
|
||||||
# The best way to find a good magic number? Literally just
|
# The best way to find a good magic number? Literally just
|
||||||
# bruteforce the shit out of it!
|
# bruteforce the shit out of it!
|
||||||
var rand = initRand()
|
var rand = initRand()
|
||||||
|
result.iterations = 0
|
||||||
while true:
|
while true:
|
||||||
|
inc(result.iterations)
|
||||||
# Again, this is stolen from the article. A magic number
|
# Again, this is stolen from the article. A magic number
|
||||||
# is only useful if it is small (i.e. has a low number of
|
# is only useful if it has high bit sparsity, so we AND
|
||||||
# bits set), so we AND together 3 random numbers to get a
|
# together a bunch of random values to get a number that's
|
||||||
# number with (hopefully) not that many bits set
|
# hopefully better
|
||||||
let
|
let
|
||||||
magic = rand.next() and rand.next() and rand.next()
|
magic = rand.next() and rand.next() and rand.next()
|
||||||
entry = MagicEntry(mask: mask, value: magic, indexBits: indexBits)
|
entry = MagicEntry(mask: mask, value: magic, indexBits: indexBits)
|
||||||
var attempt = attemptMagicTableCreation(kind, square, entry)
|
var attempt = attemptMagicTableCreation(kind, square, entry)
|
||||||
if attempt.success:
|
if attempt.success:
|
||||||
return (entry, attempt.table)
|
# Huzzah! Our search for the mighty magic number is complete
|
||||||
|
# (for this square)
|
||||||
|
result.entry = entry
|
||||||
|
result.table = attempt.table
|
||||||
|
return
|
||||||
# Not successful? No problem, we'll just try again until
|
# Not successful? No problem, we'll just try again until
|
||||||
# the heat death of the universe!
|
# the heat death of the universe! (Not reallty though: finding
|
||||||
|
# magics is pretty fast even if you're unlucky)
|
||||||
|
|
||||||
|
|
||||||
import std/strformat
|
proc computeMagics*: int {.discardable.} =
|
||||||
|
## Fills in our magic number tables and returns
|
||||||
|
## the total number of iterations that were performed
|
||||||
|
## to find them
|
||||||
|
for i in 0..63:
|
||||||
|
let square = Square(i)
|
||||||
|
var magic = findMagic(Rook, square, Rook.getRelevantBlockers(square).uint64.countSetBits().uint8)
|
||||||
|
inc(result, magic.iterations)
|
||||||
|
ROOK_MAGICS[i] = magic.entry
|
||||||
|
ROOK_MOVES[i] = magic.table
|
||||||
|
magic = findMagic(Bishop, square, Bishop.getRelevantBlockers(square).uint64.countSetBits().uint8)
|
||||||
|
inc(result, magic.iterations)
|
||||||
|
BISHOP_MAGICS[i] = magic.entry
|
||||||
|
BISHOP_MOVES[i] = magic.table
|
||||||
|
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
import std/strformat
|
||||||
|
import std/strutils
|
||||||
|
import std/times
|
||||||
|
import std/math
|
||||||
|
|
||||||
for i in 0..63:
|
|
||||||
let square = Square(i)
|
echo "Generating magic bitboards"
|
||||||
var result = findMagic(Rook, square, Rook.getRelevantBlockers(square).uint64.countSetBits().uint8)
|
let start = cpuTime()
|
||||||
echo &"Found magic bitboard for rooks at {square}"
|
let it {.used.} = computeMagics()
|
||||||
ROOK_MAGICS.add(result.entry)
|
let tot = round(cpuTime() - start, 3)
|
||||||
ROOK_MOVES[i] = result.table
|
|
||||||
result = findMagic(Bishop, square, Bishop.getRelevantBlockers(square).uint64.countSetBits().uint8)
|
echo &"Generated magic bitboards in {tot} seconds with {it} iterations"
|
||||||
echo &"Found magic bitboard for bishops at {square}"
|
var
|
||||||
BISHOP_MAGICS.add(result.entry)
|
rookTableSize = 0
|
||||||
BISHOP_MOVES[i] = result.table
|
rookTableCount = 0
|
||||||
|
bishopTableSize = 0
|
||||||
|
bishopTableCount = 0
|
||||||
|
for i in 0..63:
|
||||||
|
inc(rookTableCount, len(ROOK_MOVES[i]))
|
||||||
|
inc(bishopTableCount, len(BISHOP_MOVES[i]))
|
||||||
|
inc(rookTableSize, len(ROOK_MOVES[i]) * sizeof(Bitboard) + sizeof(seq[Bitboard]))
|
||||||
|
inc(bishopTableSize, len(BISHOP_MOVES[i]) * sizeof(Bitboard) + sizeof(seq[Bitboard]))
|
||||||
|
|
||||||
|
echo &"There are {rookTableCount} entries in the move table for rooks (total size: ~{round(rookTableSize / 1024, 3)} KiB)"
|
||||||
|
echo &"There are {bishopTableCount} entries in the move table for bishops (total size: ~{round(bishopTableSize / 1024, 3)} KiB)"
|
||||||
|
var magics = newTable[string, array[64, MagicEntry]]()
|
||||||
|
var moves = newTable[string, array[64, seq[Bitboard]]]()
|
||||||
|
magics["rooks"] = ROOK_MAGICS
|
||||||
|
magics["bishops"] = BISHOP_MAGICS
|
||||||
|
moves["rooks"] = ROOK_MOVES
|
||||||
|
moves["bishops"] = BISHOP_MOVES
|
||||||
|
let
|
||||||
|
magicsJson = magics.toJSON()
|
||||||
|
movesJson = moves.toJSON()
|
||||||
|
var path = joinPath(getCurrentDir(), "src/resources")
|
||||||
|
if path.lastPathPart() == "nimfish":
|
||||||
|
path = joinPath("src", path)
|
||||||
|
writeFile(joinPath(path, "magics.json"), magicsJson)
|
||||||
|
writeFile(joinPath(path, "movesets.json"), movesJson)
|
||||||
|
echo &"Dumped data to disk (approx. {round(((len(movesJson) + len(magicsJson)) / 1024) / 1024, 2)} MiB)"
|
||||||
|
else:
|
||||||
|
var path = joinPath(getCurrentDir(), "src/resources")
|
||||||
|
if path.lastPathPart() == "nimfish":
|
||||||
|
path = joinPath("src", path)
|
||||||
|
var magics = readFile(joinPath(path, "magics.json")).fromJson(TableRef[string, array[64, MagicEntry]])
|
||||||
|
var moves = readFile(joinPath(path, "movesets.json")).fromJson(TableRef[string, array[64, seq[Bitboard]]])
|
||||||
|
ROOK_MAGICS = magics["rooks"]
|
||||||
|
BISHOP_MAGICS = magics["bishops"]
|
||||||
|
ROOK_MOVES = moves["rooks"]
|
||||||
|
BISHOP_MOVES = moves["bishops"]
|
|
@ -43,9 +43,8 @@ func `+`*(a, b: Square): Square {.inline.} = Square(a.int8 + b.int8)
|
||||||
func `+`*(a: Square, b: SomeInteger): Square {.inline.} = Square(a.int8 + b.int8)
|
func `+`*(a: Square, b: SomeInteger): Square {.inline.} = Square(a.int8 + b.int8)
|
||||||
func `+`*(a: SomeInteger, b: Square): Square {.inline.} = Square(a.int8 + b.int8)
|
func `+`*(a: SomeInteger, b: Square): Square {.inline.} = Square(a.int8 + b.int8)
|
||||||
|
|
||||||
func colFromSquare*(square: Square): int8 = square.int8 mod 8 + 1
|
func fileFromSquare*(square: Square): int8 = square.int8 mod 8
|
||||||
func rowFromSquare*(square: Square): int8 = square.int8 div 8 + 1
|
func rankFromSquare*(square: Square): int8 = square.int8 div 8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func makeSquare*(rank, file: SomeInteger): Square = Square((rank * 8) + file)
|
func makeSquare*(rank, file: SomeInteger): Square = Square((rank * 8) + file)
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -12,6 +12,6 @@ in semiconductor technology smart enough to play tic tac toe.
|
||||||
- Chess -> WIP
|
- Chess -> WIP
|
||||||
|
|
||||||
|
|
||||||
All of these games will be played using decision trees searched using the minimax algorithm (maybe a bit of neural networks too, who knows).
|
All of these games will be played using decision trees searched using the minimax algorithm or variations thereof (maybe a bit of neural networks too, who knows).
|
||||||
Ideally I'd like to implement a bunch of stuff such as move reordering, alpha-beta pruning and transpositions in order to improve both
|
Ideally I'd like to implement a bunch of stuff such as move reordering, alpha-beta pruning and transpositions in order to improve both
|
||||||
processing time and decision quality. Very much WIP.
|
processing time and decision quality. Very much WIP.
|
|
@ -1,60 +0,0 @@
|
||||||
import board as chess
|
|
||||||
import std/strformat
|
|
||||||
import std/strutils
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
|
||||||
setControlCHook(proc () {.noconv.} = echo ""; quit(0))
|
|
||||||
const fen = "rnbqkbnr/2p/8/8/8/8/P7/RNBQKBNR w KQkq - 0 1"
|
|
||||||
var
|
|
||||||
board = newChessboardFromFEN(fen)
|
|
||||||
canCastle: tuple[queen, king: bool]
|
|
||||||
data: string
|
|
||||||
move: Move
|
|
||||||
|
|
||||||
echo "\x1Bc"
|
|
||||||
while true:
|
|
||||||
canCastle = board.canCastle()
|
|
||||||
echo &"{board.pretty()}"
|
|
||||||
echo &"Turn: {board.getSideToMove()}"
|
|
||||||
echo &"Moves: {board.getMoveCount()} full, {board.getHalfMoveCount()} half"
|
|
||||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
|
||||||
stdout.write(&"En passant target: ")
|
|
||||||
if board.getEnPassantTarget() != emptyLocation():
|
|
||||||
echo board.getEnPassantTarget().locationToAlgebraic()
|
|
||||||
else:
|
|
||||||
echo "None"
|
|
||||||
stdout.write(&"Check: ")
|
|
||||||
if board.inCheck():
|
|
||||||
echo &"Yes"
|
|
||||||
else:
|
|
||||||
echo "No"
|
|
||||||
stdout.write("\nMove(s) -> ")
|
|
||||||
try:
|
|
||||||
data = readLine(stdin).strip(chars={'\0', ' '})
|
|
||||||
except IOError:
|
|
||||||
echo ""
|
|
||||||
break
|
|
||||||
if data == "undo":
|
|
||||||
echo &"\x1BcUndo: {board.undoLastMove()}"
|
|
||||||
continue
|
|
||||||
if data == "reset":
|
|
||||||
echo &"\x1BcBoard reset"
|
|
||||||
board = newChessboardFromFEN(fen)
|
|
||||||
continue
|
|
||||||
for moveChars in data.split(" "):
|
|
||||||
if len(moveChars) != 4:
|
|
||||||
echo "\x1BcError: invalid move"
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
move = board.makeMove(moveChars[0..1], moveChars[2..3])
|
|
||||||
except ValueError:
|
|
||||||
echo &"\x1BcError: {getCurrentExceptionMsg()}"
|
|
||||||
if move == emptyMove():
|
|
||||||
echo &"\x1BcError: move '{moveChars}' is illegal"
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
echo "\x1Bc"
|
|
Loading…
Reference in New Issue