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/bitops
|
||||
|
||||
import bitboards
|
||||
import pieces
|
||||
import moves
|
||||
|
||||
import src/bitboards
|
||||
import src/magics
|
||||
import src/pieces
|
||||
import src/moves
|
||||
|
||||
|
||||
type
|
||||
|
@ -648,9 +650,49 @@ proc generatePawnMoves(self: ChessBoard, moves: var MoveList) =
|
|||
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) =
|
||||
## Generates all legal sliding moves for the side to move
|
||||
|
||||
self.generateRookMoves(moves)
|
||||
|
||||
|
||||
proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
|
||||
## 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.generateKingMoves(moves)
|
||||
self.generateKnightMoves(moves)
|
||||
self.generateRookMoves(moves)
|
||||
# TODO: all pieces
|
||||
|
||||
|
||||
|
@ -962,25 +1005,26 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
|
||||
# Castling check: have the rooks moved?
|
||||
if piece.kind == Rook:
|
||||
case piece.color:
|
||||
of White:
|
||||
if rowFromSquare(move.startSquare) == piece.getStartRank():
|
||||
if colFromSquare(move.startSquare) == 0:
|
||||
# Queen side
|
||||
castlingAvailable.white.queen = false
|
||||
elif colfromSquare(move.startSquare) == 7:
|
||||
# King side
|
||||
castlingAvailable.white.king = false
|
||||
of Black:
|
||||
if rowFromSquare(move.startSquare) == piece.getStartRank():
|
||||
if colFromSquare(move.startSquare) == 0:
|
||||
# Queen side
|
||||
castlingAvailable.black.queen = false
|
||||
elif colFromSquare(move.startSquare) == 7:
|
||||
# King side
|
||||
castlingAvailable.black.king = false
|
||||
else:
|
||||
discard
|
||||
discard
|
||||
# case piece.color:
|
||||
# of White:
|
||||
# if rowFromSquare(move.startSquare) == piece.getStartRank():
|
||||
# if columnFromSquare(move.startSquare) == 0:
|
||||
# # Queen side
|
||||
# castlingAvailable.white.queen = false
|
||||
# elif columnfromSquare(move.startSquare) == 7:
|
||||
# # King side
|
||||
# castlingAvailable.white.king = false
|
||||
# of Black:
|
||||
# if rowFromSquare(move.startSquare) == piece.getStartRank():
|
||||
# if columnFromSquare(move.startSquare) == 0:
|
||||
# # Queen side
|
||||
# castlingAvailable.black.queen = false
|
||||
# elif columnFromSquare(move.startSquare) == 7:
|
||||
# # King side
|
||||
# castlingAvailable.black.king = false
|
||||
# else:
|
||||
# discard
|
||||
# Has a rook been captured?
|
||||
if move.isCapture():
|
||||
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:
|
||||
flags.add(Capture)
|
||||
|
||||
elif board.grid[startSquare].kind == Pawn and abs(rowFromSquare(startSquare) - rowFromSquare(targetSquare)) == 2:
|
||||
flags.add(DoublePush)
|
||||
#elif board.grid[startSquare].kind == Pawn and abs(rowFromSquare(startSquare) - rowFromSquare(targetSquare)) == 2:
|
||||
# flags.add(DoublePush)
|
||||
|
||||
if len(moveString) == 5:
|
||||
# Promotion
|
||||
|
@ -1794,13 +1838,15 @@ when isMainModule:
|
|||
testPieceBitboard(blackRooks, blackRookSquares)
|
||||
testPieceBitboard(blackQueens, blackQueenSquares)
|
||||
testPieceBitboard(blackKing, blackKingSquares)
|
||||
|
||||
|
||||
|
||||
b = newChessboardFromFEN("8/5R2/8/8/7k/8/5K2/8 w - - 0 1")
|
||||
var m = MoveList()
|
||||
b.generateMoves(m)
|
||||
b.generateRookMovements(m)
|
||||
echo &"There are {len(m)} legal moves for {b.getSideToMove()} at {b.toFEN()}: "
|
||||
for move in m:
|
||||
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
|
||||
echo b.pretty()
|
||||
echo b.getAttacksTo("f3".toSquare(), White)
|
||||
|
||||
# setControlCHook(proc () {.noconv.} = quit(0))
|
||||
# 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: 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 =
|
||||
## Iterates ove the given bitboard
|
|
@ -8,6 +8,11 @@ import pieces
|
|||
|
||||
import std/random
|
||||
import std/bitops
|
||||
import std/tables
|
||||
import std/os
|
||||
|
||||
|
||||
import jsony
|
||||
|
||||
|
||||
export pieces
|
||||
|
@ -23,9 +28,8 @@ type
|
|||
|
||||
|
||||
# Yeah uh, don't look too closely at this...
|
||||
proc generateRookMasks(blockers: bool = false): array[64, Bitboard] {.compileTime.} =
|
||||
## Generates all movement masks for rooks (only generates
|
||||
## blocker masks if blockers equals true)
|
||||
proc generateRookBlockers: array[64, Bitboard] {.compileTime.} =
|
||||
## Generates all blocker masks for rooks
|
||||
for rank in 0..7:
|
||||
for file in 0..7:
|
||||
let
|
||||
|
@ -37,37 +41,36 @@ proc generateRookMasks(blockers: bool = false): array[64, Bitboard] {.compileTim
|
|||
last = makeSquare(rank, 7).toBitboard()
|
||||
while true:
|
||||
current = current.rightRelativeTo(White)
|
||||
if (current == last and blockers) or current == 0:
|
||||
if current == last or current == 0:
|
||||
break
|
||||
result[i] = result[i] or current
|
||||
current = bitboard
|
||||
last = makeSquare(rank, 0).toBitboard()
|
||||
while true:
|
||||
current = current.leftRelativeTo(White)
|
||||
if (current == last and blockers) or current == 0:
|
||||
if current == last or current == 0:
|
||||
break
|
||||
result[i] = result[i] or current
|
||||
current = bitboard
|
||||
last = makeSquare(0, file).toBitboard()
|
||||
while true:
|
||||
current = current.forwardRelativeTo(White)
|
||||
if (current == last and blockers) or current == 0:
|
||||
if current == last or current == 0:
|
||||
break
|
||||
result[i] = result[i] or current
|
||||
current = bitboard
|
||||
last = makeSquare(7, file).toBitboard()
|
||||
while true:
|
||||
current = current.backwardRelativeTo(White)
|
||||
if (current == last and blockers) or current == 0:
|
||||
if current == last or current == 0:
|
||||
break
|
||||
result[i] = result[i] or current
|
||||
|
||||
|
||||
# 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
|
||||
func generateBishopMasks(blockers = false): array[64, Bitboard] {.compileTime.} =
|
||||
## Generates all movement masks for bishops (only generates
|
||||
## blocker masks if blockers equals true)
|
||||
func generateBishopBlockers: array[64, Bitboard] {.compileTime.} =
|
||||
## Generates all blocker masks for bishops
|
||||
for rank in 0..7:
|
||||
for file in 0..7:
|
||||
# Generate all possible movement masks
|
||||
|
@ -100,37 +103,36 @@ func generateBishopMasks(blockers = false): array[64, Bitboard] {.compileTime.}
|
|||
if current == 0:
|
||||
break
|
||||
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
|
||||
# I'm an idiot so what do I know. Credit to @__arandomnoob
|
||||
# on the engine programming discord server for the tip!
|
||||
result[i] = result[i] and not getFileMask(0)
|
||||
result[i] = result[i] and not getFileMask(7)
|
||||
result[i] = result[i] and not getRankMask(0)
|
||||
result[i] = result[i] and not getRankMask(7)
|
||||
# Yeah, this is the trick. I know, not a big deal, but
|
||||
# I'm an idiot so what do I know. Credit to @__arandomnoob
|
||||
# on the engine programming discord server for the tip!
|
||||
result[i] = result[i] and not getFileMask(0)
|
||||
result[i] = result[i] and not getFileMask(7)
|
||||
result[i] = result[i] and not getRankMask(0)
|
||||
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
|
||||
## the given magic entry and the blockers bitboard
|
||||
let
|
||||
blockers = blockers and magic.mask
|
||||
hash = blockers * magic.value
|
||||
index = hash shr (64 - magic.indexBits)
|
||||
index = hash shr (64'u8 - magic.indexBits)
|
||||
return index.uint
|
||||
|
||||
|
||||
# Magic number tables and their corresponding moves
|
||||
var
|
||||
ROOK_MAGICS: seq[MagicEntry]
|
||||
ROOK_MAGICS: array[64, MagicEntry]
|
||||
ROOK_MOVES: array[64, seq[Bitboard]]
|
||||
BISHOP_MAGICS: seq[MagicEntry]
|
||||
BISHOP_MAGICS: array[64, MagicEntry]
|
||||
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
|
||||
## square with the given blockers bitboard
|
||||
let
|
||||
|
@ -139,7 +141,7 @@ proc getRookMoves(square: Square, blockers: Bitboard): Bitboard =
|
|||
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
|
||||
## square with the given blockers bitboard
|
||||
let
|
||||
|
@ -153,13 +155,11 @@ proc getBishopMoves(square: Square, blockers: Bitboard): Bitboard =
|
|||
# regardless of color
|
||||
const
|
||||
# mfw Nim's compile time VM *graciously* allows me to call perfectly valid code: :D
|
||||
ROOK_BLOCKERS = generateRookMasks(blockers=true)
|
||||
BISHOP_BLOCKERS = generateBishopMasks(blockers=true)
|
||||
ROOK_MOVEMENTS = generateRookMasks()
|
||||
BISHOP_MOVEMENTS = generateBishopMasks()
|
||||
ROOK_BLOCKERS = generateRookBlockers()
|
||||
BISHOP_BLOCKERS = generateBishopBlockers()
|
||||
|
||||
|
||||
func getRelevantBlockers(kind: PieceKind, square: Square): Bitboard =
|
||||
func getRelevantBlockers*(kind: PieceKind, square: Square): Bitboard =
|
||||
## Returns the relevant blockers mask for the given piece
|
||||
## type at the given square
|
||||
case kind:
|
||||
|
@ -169,7 +169,40 @@ func getRelevantBlockers(kind: PieceKind, square: Square): Bitboard =
|
|||
return BISHOP_BLOCKERS[square.uint]
|
||||
else:
|
||||
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]] =
|
||||
## 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():
|
||||
let index = getIndex(entry, blocker)
|
||||
# Get the moves the piece can make from the given
|
||||
# square with this specific blocker configuration
|
||||
var moves: Bitboard
|
||||
case kind:
|
||||
of Rook:
|
||||
moves = ROOK_MOVEMENTS[square.uint]
|
||||
of Bishop:
|
||||
moves = BISHOP_MOVEMENTS[square.uint]
|
||||
else:
|
||||
discard
|
||||
# square with this specific blocker configuration.
|
||||
# Note that this will return the same set of moves
|
||||
# for several different blocker configurations, as
|
||||
# many of them (while different) produce the same
|
||||
# results
|
||||
var moves = kind.getMoveSet(square, blocker)
|
||||
if result.table[index] == 0:
|
||||
# No entry here, yet, so no problem!
|
||||
result.table[index] = moves
|
||||
elif (result.table[index] or blocker) != moves:
|
||||
elif result.table[index] != moves:
|
||||
# We found a non-constructive collision, fail :(
|
||||
# Notes for future self: A "constructive" collision
|
||||
# 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
|
||||
# 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
|
||||
# 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
|
||||
# for the insight)
|
||||
return (false, @[])
|
||||
# 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
|
||||
## the possible blocking configurations for the given piece at
|
||||
## 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
|
||||
# bruteforce the shit out of it!
|
||||
var rand = initRand()
|
||||
result.iterations = 0
|
||||
while true:
|
||||
inc(result.iterations)
|
||||
# Again, this is stolen from the article. A magic number
|
||||
# is only useful if it is small (i.e. has a low number of
|
||||
# bits set), so we AND together 3 random numbers to get a
|
||||
# number with (hopefully) not that many bits set
|
||||
# is only useful if it has high bit sparsity, so we AND
|
||||
# together a bunch of random values to get a number that's
|
||||
# hopefully better
|
||||
let
|
||||
magic = rand.next() and rand.next() and rand.next()
|
||||
entry = MagicEntry(mask: mask, value: magic, indexBits: indexBits)
|
||||
var attempt = attemptMagicTableCreation(kind, square, entry)
|
||||
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
|
||||
# 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)
|
||||
var result = findMagic(Rook, square, Rook.getRelevantBlockers(square).uint64.countSetBits().uint8)
|
||||
echo &"Found magic bitboard for rooks at {square}"
|
||||
ROOK_MAGICS.add(result.entry)
|
||||
ROOK_MOVES[i] = result.table
|
||||
result = findMagic(Bishop, square, Bishop.getRelevantBlockers(square).uint64.countSetBits().uint8)
|
||||
echo &"Found magic bitboard for bishops at {square}"
|
||||
BISHOP_MAGICS.add(result.entry)
|
||||
BISHOP_MOVES[i] = result.table
|
||||
|
||||
echo "Generating magic bitboards"
|
||||
let start = cpuTime()
|
||||
let it {.used.} = computeMagics()
|
||||
let tot = round(cpuTime() - start, 3)
|
||||
|
||||
echo &"Generated magic bitboards in {tot} seconds with {it} iterations"
|
||||
var
|
||||
rookTableSize = 0
|
||||
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: SomeInteger, b: Square): Square {.inline.} = Square(a.int8 + b.int8)
|
||||
|
||||
func colFromSquare*(square: Square): int8 = square.int8 mod 8 + 1
|
||||
func rowFromSquare*(square: Square): int8 = square.int8 div 8 + 1
|
||||
|
||||
func fileFromSquare*(square: Square): int8 = square.int8 mod 8
|
||||
func rankFromSquare*(square: Square): int8 = square.int8 div 8
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
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