Refactoring and bug fixes
This commit is contained in:
parent
a4954a971b
commit
77129855df
|
@ -14,7 +14,8 @@
|
||||||
|
|
||||||
import std/strutils
|
import std/strutils
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import std/sequtils
|
import std/times
|
||||||
|
import std/math
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -75,7 +76,6 @@ type
|
||||||
|
|
||||||
Position* = ref object
|
Position* = ref object
|
||||||
## A chess position
|
## A chess position
|
||||||
move: Move
|
|
||||||
# Did the rooks on either side/the king move?
|
# Did the rooks on either side/the king move?
|
||||||
castlingAvailable: tuple[white, black: tuple[queen, king: bool]]
|
castlingAvailable: tuple[white, black: tuple[queen, king: bool]]
|
||||||
# Number of half-moves that were performed
|
# Number of half-moves that were performed
|
||||||
|
@ -103,12 +103,12 @@ type
|
||||||
ChessBoard* = ref object
|
ChessBoard* = ref object
|
||||||
## A chess board object
|
## A chess board object
|
||||||
|
|
||||||
# An 8x8 matrix we use for constant
|
# The actual board where pieces live
|
||||||
# time lookup of pieces by their location
|
# (flattened 8x8 matrix)
|
||||||
grid: seq[Piece]
|
grid: seq[Piece]
|
||||||
# The current position
|
# The current position
|
||||||
position: Position
|
position: Position
|
||||||
# List of all reached positions
|
# List of all previously reached positions
|
||||||
positions: seq[Position]
|
positions: seq[Position]
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,12 +137,15 @@ proc getPinnedDirections(self: ChessBoard, loc: Location): seq[Location]
|
||||||
proc getAttacks*(self: ChessBoard, loc: Location): Attacked
|
proc getAttacks*(self: ChessBoard, loc: Location): Attacked
|
||||||
proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked, pins: Attacked]
|
proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked, pins: Attacked]
|
||||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool
|
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool
|
||||||
|
proc toFEN*(self: ChessBoard): string
|
||||||
|
proc undoLastMove*(self: ChessBoard)
|
||||||
|
|
||||||
|
|
||||||
proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} =
|
proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} =
|
||||||
for x in other:
|
for x in other:
|
||||||
self.add(x)
|
self.add(x)
|
||||||
|
|
||||||
|
proc resetBoard*(self: ChessBoard)
|
||||||
|
|
||||||
# Due to our board layout, directions of movement are reversed for white and black, so
|
# 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
|
# we need these helpers to avoid going mad with integer tuples and minus signs everywhere
|
||||||
|
@ -260,6 +263,18 @@ func getStartRow(piece: Piece): int {.inline.} =
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
func getKingStartingPosition(color: PieceColor): Location {.inline.} =
|
||||||
|
## Retrieves the starting location of the king
|
||||||
|
## for the given color
|
||||||
|
case color:
|
||||||
|
of White:
|
||||||
|
return (7, 4)
|
||||||
|
of Black:
|
||||||
|
return (0, 4)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
|
||||||
func getLastRow(color: PieceColor): int {.inline.} =
|
func getLastRow(color: PieceColor): int {.inline.} =
|
||||||
## Retrieves the location of the last
|
## Retrieves the location of the last
|
||||||
## row relative to the given color
|
## row relative to the given color
|
||||||
|
@ -277,9 +292,10 @@ proc newChessboard: ChessBoard =
|
||||||
new(result)
|
new(result)
|
||||||
# Turns our flat sequence into an 8x8 grid
|
# Turns our flat sequence into an 8x8 grid
|
||||||
result.grid = newSeqOfCap[Piece](64)
|
result.grid = newSeqOfCap[Piece](64)
|
||||||
|
for _ in 0..63:
|
||||||
|
result.grid.add(emptyPiece())
|
||||||
result.position = Position(attacked: (@[], @[]),
|
result.position = Position(attacked: (@[], @[]),
|
||||||
enPassantSquare: emptyLocation(),
|
enPassantSquare: emptyLocation(),
|
||||||
move: emptyMove(),
|
|
||||||
turn: White,
|
turn: White,
|
||||||
fullMoveCount: 1,
|
fullMoveCount: 1,
|
||||||
pieces: (white: (king: emptyLocation(),
|
pieces: (white: (king: emptyLocation(),
|
||||||
|
@ -331,10 +347,8 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
|
||||||
of 'r', 'n', 'b', 'q', 'k', 'p':
|
of 'r', 'n', 'b', 'q', 'k', 'p':
|
||||||
# We know for a fact these values are in our
|
# We know for a fact these values are in our
|
||||||
# enumeration, so all is good
|
# enumeration, so all is good
|
||||||
{.push.}
|
|
||||||
{.warning[HoleEnumConv]:off.}
|
{.warning[HoleEnumConv]:off.}
|
||||||
piece = Piece(kind: PieceKind(c.toLowerAscii()), color: if c.isUpperAscii(): White else: Black)
|
piece = Piece(kind: PieceKind(c.toLowerAscii()), color: if c.isUpperAscii(): White else: Black)
|
||||||
{.pop.}
|
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of Black:
|
of Black:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
|
@ -462,8 +476,8 @@ proc newDefaultChessboard*: ChessBoard {.inline.} =
|
||||||
|
|
||||||
proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int =
|
proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int =
|
||||||
## Returns the number of pieces with
|
## Returns the number of pieces with
|
||||||
## the given color and type in the given
|
## the given color and type in the
|
||||||
## position
|
## current position
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
case kind:
|
case kind:
|
||||||
|
@ -519,9 +533,9 @@ func rankToColumn(rank: int): int8 {.inline.} =
|
||||||
return indeces[rank - 1]
|
return indeces[rank - 1]
|
||||||
|
|
||||||
|
|
||||||
func rowToRank(row: int): int {.inline.} =
|
func rowToFile(row: int): int {.inline.} =
|
||||||
## Converts a row into our grid into
|
## Converts a row into our grid into
|
||||||
## a chess rank
|
## a chess file
|
||||||
const indeces = [8, 7, 6, 5, 4, 3, 2, 1]
|
const indeces = [8, 7, 6, 5, 4, 3, 2, 1]
|
||||||
return indeces[row]
|
return indeces[row]
|
||||||
|
|
||||||
|
@ -548,7 +562,7 @@ proc algebraicToLocation*(s: string): Location =
|
||||||
func locationToAlgebraic*(loc: Location): string {.inline.} =
|
func locationToAlgebraic*(loc: Location): string {.inline.} =
|
||||||
## Converts a location from our internal row, column
|
## Converts a location from our internal row, column
|
||||||
## notation to a square in algebraic notation
|
## notation to a square in algebraic notation
|
||||||
return &"{char(uint8(loc.col) + uint8('a'))}{rowToRank(loc.row)}"
|
return &"{char(uint8(loc.col) + uint8('a'))}{rowToFile(loc.row)}"
|
||||||
|
|
||||||
|
|
||||||
func getPiece*(self: ChessBoard, loc: Location): Piece {.inline.} =
|
func getPiece*(self: ChessBoard, loc: Location): Piece {.inline.} =
|
||||||
|
@ -582,7 +596,7 @@ func getPromotionType*(move: Move): MoveFlag {.inline.} =
|
||||||
func isCapture*(move: Move): bool {.inline.} =
|
func isCapture*(move: Move): bool {.inline.} =
|
||||||
## Returns whether the given move is a
|
## Returns whether the given move is a
|
||||||
## cature
|
## cature
|
||||||
result = (move.flags and Capture.uint16) != 0
|
result = (move.flags and Capture.uint16) == Capture.uint16
|
||||||
|
|
||||||
|
|
||||||
func isCastling*(move: Move): bool {.inline.} =
|
func isCastling*(move: Move): bool {.inline.} =
|
||||||
|
@ -614,6 +628,30 @@ func isDoublePush*(move: Move): bool {.inline.} =
|
||||||
result = (move.flags and DoublePush.uint16) != 0
|
result = (move.flags and DoublePush.uint16) != 0
|
||||||
|
|
||||||
|
|
||||||
|
func getFlags*(move: Move): seq[MoveFlag] =
|
||||||
|
## Gets all the flags of this move
|
||||||
|
for flag in [EnPassant, Capture, DoublePush, CastleLong, CastleShort,
|
||||||
|
PromoteToBishop, PromoteToKnight, PromoteToQueen,
|
||||||
|
PromoteToRook]:
|
||||||
|
if (move.flags and flag.uint16) == flag.uint16:
|
||||||
|
result.add(flag)
|
||||||
|
if result.len() == 0:
|
||||||
|
result.add(Default)
|
||||||
|
|
||||||
|
|
||||||
|
func getKing(self: ChessBoard, color: PieceColor): Location {.inline.} =
|
||||||
|
var color = color
|
||||||
|
if color == None:
|
||||||
|
color = self.getActiveColor()
|
||||||
|
case color:
|
||||||
|
of White:
|
||||||
|
return self.position.pieces.white.king
|
||||||
|
of Black:
|
||||||
|
return self.position.pieces.black.king
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
|
||||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
||||||
## Returns whether the given color's
|
## Returns whether the given color's
|
||||||
## king is in check. If the color is
|
## king is in check. If the color is
|
||||||
|
@ -650,6 +688,12 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
|
||||||
of None:
|
of None:
|
||||||
# Unreachable
|
# Unreachable
|
||||||
discard
|
discard
|
||||||
|
# Some of these checks may seem redundant, but we
|
||||||
|
# perform them because they're less expensive
|
||||||
|
|
||||||
|
# King is not on its starting square
|
||||||
|
if self.getKing(color) != getKingStartingPosition(color):
|
||||||
|
return (false, false)
|
||||||
if self.inCheck(color):
|
if self.inCheck(color):
|
||||||
# King can not castle out of check
|
# King can not castle out of check
|
||||||
return (false, false)
|
return (false, false)
|
||||||
|
@ -752,6 +796,7 @@ proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
||||||
# in which case either the king has to move or
|
# in which case either the king has to move or
|
||||||
# that piece has to be captured, but this is
|
# that piece has to be captured, but this is
|
||||||
# already implicitly handled by the loop below)
|
# already implicitly handled by the loop below)
|
||||||
|
|
||||||
var location = attacker
|
var location = attacker
|
||||||
while location != king:
|
while location != king:
|
||||||
location = location + attack.direction
|
location = location + attack.direction
|
||||||
|
@ -760,40 +805,24 @@ proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
||||||
result.add(location)
|
result.add(location)
|
||||||
|
|
||||||
|
|
||||||
func getKing(self: ChessBoard, color: PieceColor): Location {.inline.} =
|
|
||||||
var color = color
|
|
||||||
if color == None:
|
|
||||||
color = self.getActiveColor()
|
|
||||||
case color:
|
|
||||||
of White:
|
|
||||||
return self.position.pieces.white.king
|
|
||||||
of Black:
|
|
||||||
return self.position.pieces.black.king
|
|
||||||
else:
|
|
||||||
discard
|
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
## Generates the possible moves for the pawn in the given
|
## Generates the possible moves for the pawn in the given
|
||||||
## location
|
## location
|
||||||
var
|
var
|
||||||
piece = self.grid[location.row, location.col]
|
piece = self.grid[location.row, location.col]
|
||||||
locations: seq[Location] = @[]
|
targets: seq[Location] = @[]
|
||||||
flags: seq[MoveFlag] = @[]
|
|
||||||
doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}"
|
doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}"
|
||||||
# Pawns can move forward one square
|
# Pawns can move forward one square
|
||||||
let forward = piece.color.topSide() + location
|
let forward = piece.color.topSide() + location
|
||||||
# Only if the square is empty though
|
# Only if the square is empty though
|
||||||
if forward.isValid() and self.grid[forward.row, forward.col].color == None:
|
if forward.isValid() and self.grid[forward.row, forward.col].color == None:
|
||||||
locations.add(forward)
|
targets.add(forward)
|
||||||
flags.add(Default)
|
|
||||||
# If the pawn is on its first rank, it can push two squares
|
# If the pawn is on its first rank, it can push two squares
|
||||||
if location.row == piece.getStartRow():
|
if location.row == piece.getStartRow():
|
||||||
let double = location + piece.color.doublePush()
|
let double = location + piece.color.doublePush()
|
||||||
# Check that both squares are empty
|
# Check that both squares are empty
|
||||||
if double.isValid() and self.grid[forward.row, forward.col].color == None and self.grid[double.row, double.col].color == None:
|
if double.isValid() and self.grid[forward.row, forward.col].color == None and self.grid[double.row, double.col].color == None:
|
||||||
locations.add(double)
|
targets.add(double)
|
||||||
flags.add(DoublePush)
|
|
||||||
let enPassantPawn = self.getEnPassantTarget() + piece.color.opposite().topSide()
|
let enPassantPawn = self.getEnPassantTarget() + piece.color.opposite().topSide()
|
||||||
# They can also move on either diagonal one
|
# They can also move on either diagonal one
|
||||||
# square, but only to capture or for en passant
|
# square, but only to capture or for en passant
|
||||||
|
@ -819,40 +848,37 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
if p.color == piece.color.opposite() and p.kind in [Queen, Rook]:
|
if p.color == piece.color.opposite() and p.kind in [Queen, Rook]:
|
||||||
ok = false
|
ok = false
|
||||||
if ok:
|
if ok:
|
||||||
locations.add(diagonal)
|
targets.add(diagonal)
|
||||||
flags.add(EnPassant)
|
|
||||||
elif otherPiece.color == piece.color.opposite() and otherPiece.kind != King:
|
elif otherPiece.color == piece.color.opposite() and otherPiece.kind != King:
|
||||||
locations.add(diagonal)
|
targets.add(diagonal)
|
||||||
flags.add(Capture)
|
|
||||||
var
|
|
||||||
newLocation: Location
|
|
||||||
newFlags: seq[MoveFlag]
|
|
||||||
newLocations: seq[Location]
|
|
||||||
# Check for pins
|
# Check for pins
|
||||||
let pins = self.getPinnedDirections(location)
|
let pinned = self.getPinnedDirections(location)
|
||||||
for pin in pins:
|
if pinned.len() > 0:
|
||||||
newLocation = location + pin
|
var newTargets: seq[Location] = @[]
|
||||||
let loc = locations.find(newLocation)
|
for target in targets:
|
||||||
if loc != -1:
|
if target in pinned:
|
||||||
# Pin direction is legal for this piece
|
newTargets.add(target)
|
||||||
newLocations.add(newLocation)
|
targets = newTargets
|
||||||
newFlags.add(flags[loc])
|
|
||||||
if pins.len() > 0:
|
|
||||||
locations = newLocations
|
|
||||||
flags = newFlags
|
|
||||||
let checked = self.inCheck()
|
let checked = self.inCheck()
|
||||||
let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color)
|
let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color)
|
||||||
var targetPiece: Piece
|
var targetPiece: Piece
|
||||||
for (target, flag) in zip(locations, flags):
|
for target in targets:
|
||||||
if checked and target notin resolutions:
|
if checked and target notin resolutions:
|
||||||
continue
|
continue
|
||||||
targetPiece = self.grid[target.row, target.col]
|
targetPiece = self.grid[target.row, target.col]
|
||||||
|
var flags: uint16 = Default.uint16
|
||||||
|
if targetPiece.color != None:
|
||||||
|
flags = flags or Capture.uint16
|
||||||
|
elif abs(location.row - target.row) == 2:
|
||||||
|
flags = flags or DoublePush.uint16
|
||||||
|
elif target == self.getEnPassantTarget():
|
||||||
|
flags = flags or EnPassant.uint16
|
||||||
if target.row == piece.color.getLastRow():
|
if target.row == piece.color.getLastRow():
|
||||||
# Pawn reached the other side of the board: generate all potential piece promotions
|
# Pawn reached the other side of the board: generate all potential piece promotions
|
||||||
for promotionType in [PromoteToKnight, PromoteToBishop, PromoteToRook, PromoteToQueen]:
|
for promotionType in [PromoteToKnight, PromoteToBishop, PromoteToRook, PromoteToQueen]:
|
||||||
result.add(Move(startSquare: location, targetSquare: target, flags: promotionType.uint16 or flag.uint16))
|
result.add(Move(startSquare: location, targetSquare: target, flags: promotionType.uint16 or flags))
|
||||||
continue
|
continue
|
||||||
result.add(Move(startSquare: location, targetSquare: target, flags: flag.uint16))
|
result.add(Move(startSquare: location, targetSquare: target, flags: flags))
|
||||||
|
|
||||||
|
|
||||||
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
|
@ -874,7 +900,11 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
directions.add(piece.color.leftSide())
|
directions.add(piece.color.leftSide())
|
||||||
let pinned = self.getPinnedDirections(location)
|
let pinned = self.getPinnedDirections(location)
|
||||||
if pinned.len() > 0:
|
if pinned.len() > 0:
|
||||||
directions = pinned
|
var newDirections: seq[Location] = @[]
|
||||||
|
for direction in directions:
|
||||||
|
if direction in pinned:
|
||||||
|
newDirections.add(direction)
|
||||||
|
directions = newDirections
|
||||||
let checked = self.inCheck()
|
let checked = self.inCheck()
|
||||||
let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color)
|
let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color)
|
||||||
for direction in directions:
|
for direction in directions:
|
||||||
|
@ -892,7 +922,9 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
if otherPiece.color == piece.color:
|
if otherPiece.color == piece.color:
|
||||||
break
|
break
|
||||||
if checked and square notin resolutions:
|
if checked and square notin resolutions:
|
||||||
break
|
# We don't break out of the loop because
|
||||||
|
# we might resolve the check later
|
||||||
|
continue
|
||||||
if otherPiece.color == piece.color.opposite:
|
if otherPiece.color == piece.color.opposite:
|
||||||
# Target square contains an enemy piece: capture
|
# Target square contains an enemy piece: capture
|
||||||
# it and stop going any further
|
# it and stop going any further
|
||||||
|
@ -978,7 +1010,6 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
# A friendly piece or the opponent king is is in the way
|
# A friendly piece or the opponent king is is in the way
|
||||||
if otherPiece.color == piece.color or otherPiece.kind == King:
|
if otherPiece.color == piece.color or otherPiece.kind == King:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if checked and square notin resolutions:
|
if checked and square notin resolutions:
|
||||||
continue
|
continue
|
||||||
if otherPiece.color != None:
|
if otherPiece.color != None:
|
||||||
|
@ -1070,7 +1101,7 @@ proc getAttacks*(self: ChessBoard, loc: Location): Attacked =
|
||||||
|
|
||||||
|
|
||||||
proc getAttackFor*(self: ChessBoard, source, target: Location): tuple[source, target, direction: Location] =
|
proc getAttackFor*(self: ChessBoard, source, target: Location): tuple[source, target, direction: Location] =
|
||||||
## Returns the first attacks of the piece in the given
|
## Returns the first attack of the piece in the given
|
||||||
## source location that also attacks the target location
|
## source location that also attacks the target location
|
||||||
let piece = self.grid[source.row, source.col]
|
let piece = self.grid[source.row, source.col]
|
||||||
case piece.color:
|
case piece.color:
|
||||||
|
@ -1283,11 +1314,12 @@ proc updateAttackedSquares(self: ChessBoard) =
|
||||||
self.updateKingAttacks()
|
self.updateKingAttacks()
|
||||||
|
|
||||||
|
|
||||||
proc removePiece(self: ChessBoard, location: Location, attack: bool = true) =
|
proc removePiece(self: ChessBoard, location: Location, attack: bool = true, empty: bool = true) =
|
||||||
## Removes a piece from the board, updating necessary
|
## Removes a piece from the board, updating necessary
|
||||||
## metadata
|
## metadata
|
||||||
var piece = self.grid[location.row, location.col]
|
var piece = self.grid[location.row, location.col]
|
||||||
self.grid[location.row, location.col] = emptyPiece()
|
if empty:
|
||||||
|
self.grid[location.row, location.col] = emptyPiece()
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of White:
|
of White:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
|
@ -1388,11 +1420,11 @@ proc movePiece(self: ChessBoard, move: Move, attack: bool = true) =
|
||||||
self.updateAttackedSquares()
|
self.updateAttackedSquares()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc movePiece(self: ChessBoard, startSquare, targetSquare: Location, attack: bool = true) =
|
proc movePiece(self: ChessBoard, startSquare, targetSquare: Location, attack: bool = true) =
|
||||||
## Like the other movePiece(), but with two locations
|
## Like the other movePiece(), but with two locations
|
||||||
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare), attack)
|
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare), attack)
|
||||||
|
|
||||||
|
|
||||||
proc doMove(self: ChessBoard, move: Move) =
|
proc doMove(self: ChessBoard, move: Move) =
|
||||||
## Internal function called by makeMove after
|
## Internal function called by makeMove after
|
||||||
## performing legality checks. Can be used in
|
## performing legality checks. Can be used in
|
||||||
|
@ -1482,13 +1514,13 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
castlingAvailable.black.queen = false
|
castlingAvailable.black.queen = false
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
# Create new position
|
# Create new position
|
||||||
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||||
halfMoveClock: halfMoveClock,
|
halfMoveClock: halfMoveClock,
|
||||||
fullMoveCount: fullMoveCount,
|
fullMoveCount: fullMoveCount,
|
||||||
turn: self.getActiveColor().opposite,
|
turn: self.getActiveColor().opposite,
|
||||||
castlingAvailable: castlingAvailable,
|
castlingAvailable: castlingAvailable,
|
||||||
move: move,
|
|
||||||
pieces: self.position.pieces,
|
pieces: self.position.pieces,
|
||||||
enPassantSquare: enPassantTarget
|
enPassantSquare: enPassantTarget
|
||||||
)
|
)
|
||||||
|
@ -1500,26 +1532,23 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
var
|
var
|
||||||
location: Location
|
location: Location
|
||||||
target: Location
|
target: Location
|
||||||
|
flags: uint16
|
||||||
if move.getCastlingType() == CastleShort:
|
if move.getCastlingType() == CastleShort:
|
||||||
location = piece.color.kingSideRook()
|
location = piece.color.kingSideRook()
|
||||||
target = shortCastleRook()
|
target = shortCastleRook()
|
||||||
|
flags = flags or CastleShort.uint16
|
||||||
else:
|
else:
|
||||||
location = piece.color.queenSideRook()
|
location = piece.color.queenSideRook()
|
||||||
target = longCastleRook()
|
target = longCastleRook()
|
||||||
|
flags = flags or CastleLong.uint16
|
||||||
let rook = self.grid[location.row, location.col]
|
let rook = self.grid[location.row, location.col]
|
||||||
let move = Move(startSquare: location, targetSquare: location + target, flags: move.flags)
|
let move = Move(startSquare: location, targetSquare: location + target, flags: flags)
|
||||||
self.movePiece(move, attack=false)
|
self.movePiece(move, attack=false)
|
||||||
|
|
||||||
if move.isCapture():
|
|
||||||
# Get rid of captured pieces
|
|
||||||
self.removePiece(move.targetSquare, attack=false)
|
|
||||||
|
|
||||||
if move.isEnPassant():
|
if move.isEnPassant():
|
||||||
# Make the en passant pawn disappear
|
# Make the en passant pawn disappear
|
||||||
self.removePiece(move.targetSquare + piece.color.bottomSide(), attack=false)
|
self.removePiece(move.targetSquare + piece.color.bottomSide(), attack=false)
|
||||||
|
|
||||||
self.movePiece(move, attack=false)
|
|
||||||
|
|
||||||
if move.isPromotion():
|
if move.isPromotion():
|
||||||
# Move is a pawn promotion: get rid of the pawn
|
# Move is a pawn promotion: get rid of the pawn
|
||||||
# and spawn a new piece
|
# and spawn a new piece
|
||||||
|
@ -1535,9 +1564,15 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
|
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
# Update attack metadata
|
|
||||||
self.updateAttackedSquares()
|
|
||||||
|
|
||||||
|
if move.isCapture():
|
||||||
|
# Get rid of captured pieces
|
||||||
|
self.removePiece(move.targetSquare, attack=false, empty=false)
|
||||||
|
# Move the piece to its target square and update attack metadata
|
||||||
|
self.movePiece(move)
|
||||||
|
# TODO: Remove this, once I figure out what the heck is wrong
|
||||||
|
# with updating the board representation
|
||||||
|
self.resetBoard()
|
||||||
|
|
||||||
|
|
||||||
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||||
|
@ -1588,9 +1623,7 @@ proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||||
proc resetBoard*(self: ChessBoard) =
|
proc resetBoard*(self: ChessBoard) =
|
||||||
## Resets the internal grid representation
|
## Resets the internal grid representation
|
||||||
## according to the positional data stored
|
## according to the positional data stored
|
||||||
## in the chessboard. Warning: this can be
|
## in the chessboard
|
||||||
## expensive, especially in critical paths
|
|
||||||
## or tight loops
|
|
||||||
for i in 0..63:
|
for i in 0..63:
|
||||||
self.grid[i] = emptyPiece()
|
self.grid[i] = emptyPiece()
|
||||||
for loc in self.position.pieces.white.pawns:
|
for loc in self.position.pieces.white.pawns:
|
||||||
|
@ -1618,9 +1651,8 @@ proc resetBoard*(self: ChessBoard) =
|
||||||
|
|
||||||
|
|
||||||
proc undoLastMove*(self: ChessBoard) =
|
proc undoLastMove*(self: ChessBoard) =
|
||||||
if self.positions.len() == 0:
|
if self.positions.len() > 0:
|
||||||
return
|
self.position = self.positions.pop()
|
||||||
self.position = self.positions.pop()
|
|
||||||
self.resetBoard()
|
self.resetBoard()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1778,12 +1810,13 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
## the given number of ply
|
## the given number of ply
|
||||||
|
|
||||||
let moves = self.generateAllMoves()
|
let moves = self.generateAllMoves()
|
||||||
if len(moves) == 0:
|
if not bulk:
|
||||||
result.checkmates = 1
|
if len(moves) == 0 and self.inCheck():
|
||||||
if ply == 0:
|
result.checkmates = 1
|
||||||
result.nodes = 1
|
if ply == 0:
|
||||||
return
|
result.nodes = 1
|
||||||
if ply == 1 and bulk:
|
return
|
||||||
|
elif ply == 1 and bulk:
|
||||||
if divide:
|
if divide:
|
||||||
var postfix = ""
|
var postfix = ""
|
||||||
for move in moves:
|
for move in moves:
|
||||||
|
@ -1810,7 +1843,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
echo &"Move: {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}, from ({move.startSquare.row}, {move.startSquare.col}) to ({move.targetSquare.row}, {move.targetSquare.col})"
|
echo &"Move: {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}, from ({move.startSquare.row}, {move.startSquare.col}) to ({move.targetSquare.row}, {move.targetSquare.col})"
|
||||||
echo &"Turn: {self.getActiveColor()}"
|
echo &"Turn: {self.getActiveColor()}"
|
||||||
echo &"Piece: {self.grid[move.startSquare.row, move.startSquare.col].kind}"
|
echo &"Piece: {self.grid[move.startSquare.row, move.startSquare.col].kind}"
|
||||||
echo &"Flag: {move.flags}"
|
echo &"Flags: {move.getFlags()}"
|
||||||
echo &"In check: {(if self.inCheck(): \"yes\" else: \"no\")}"
|
echo &"In check: {(if self.inCheck(): \"yes\" else: \"no\")}"
|
||||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||||
echo &"Position before move: {self.toFEN()}"
|
echo &"Position before move: {self.toFEN()}"
|
||||||
|
@ -1821,14 +1854,15 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
echo "None"
|
echo "None"
|
||||||
echo "\n", self.pretty()
|
echo "\n", self.pretty()
|
||||||
self.doMove(move)
|
self.doMove(move)
|
||||||
if move.isCapture():
|
if ply == 1:
|
||||||
inc(result.captures)
|
if move.isCapture():
|
||||||
if move.isCastling():
|
inc(result.captures)
|
||||||
inc(result.castles)
|
if move.isCastling():
|
||||||
if move.isPromotion():
|
inc(result.castles)
|
||||||
inc(result.promotions)
|
if move.isPromotion():
|
||||||
if move.isEnPassant():
|
inc(result.promotions)
|
||||||
inc(result.enPassant)
|
if move.isEnPassant():
|
||||||
|
inc(result.enPassant)
|
||||||
if self.inCheck():
|
if self.inCheck():
|
||||||
# Opponent king is in check
|
# Opponent king is in check
|
||||||
inc(result.checks)
|
inc(result.checks)
|
||||||
|
@ -1891,7 +1925,7 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
||||||
var ok = true
|
var ok = true
|
||||||
for arg in args[1..^1]:
|
for arg in args[1..^1]:
|
||||||
case arg:
|
case arg:
|
||||||
of "bulk-count", "bulk":
|
of "bulk":
|
||||||
bulk = true
|
bulk = true
|
||||||
of "verbose":
|
of "verbose":
|
||||||
verbose = true
|
verbose = true
|
||||||
|
@ -1904,8 +1938,12 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
||||||
try:
|
try:
|
||||||
let ply = parseInt(args[0])
|
let ply = parseInt(args[0])
|
||||||
if bulk:
|
if bulk:
|
||||||
echo &"\nNodes searched (bulk-counting: on): {board.perft(ply, divide=true, bulk=true, verbose=verbose).nodes}\n"
|
let t = cpuTime()
|
||||||
|
let nodes = board.perft(ply, divide=true, bulk=true, verbose=verbose).nodes
|
||||||
|
echo &"\nNodes searched (bulk-counting: on): {nodes}"
|
||||||
|
echo &"Time taken: {round(cpuTime() - t, 3)} seconds\n"
|
||||||
else:
|
else:
|
||||||
|
let t = cpuTime()
|
||||||
let data = board.perft(ply, divide=true, verbose=verbose)
|
let data = board.perft(ply, divide=true, verbose=verbose)
|
||||||
echo &"\nNodes searched (bulk-counting: off): {data.nodes}"
|
echo &"\nNodes searched (bulk-counting: off): {data.nodes}"
|
||||||
echo &" - Captures: {data.captures}"
|
echo &" - Captures: {data.captures}"
|
||||||
|
@ -1915,40 +1953,14 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
||||||
echo &" - Castles: {data.castles}"
|
echo &" - Castles: {data.castles}"
|
||||||
echo &" - Promotions: {data.promotions}"
|
echo &" - Promotions: {data.promotions}"
|
||||||
echo ""
|
echo ""
|
||||||
|
echo &"Time taken: {round(cpuTime() - t, 3)} seconds"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo "Error: go: perft: invalid depth"
|
echo "Error: go: perft: invalid depth"
|
||||||
else:
|
else:
|
||||||
echo &"Error: go: unknown subcommand '{command[1]}'"
|
echo &"Error: go: unknown subcommand '{command[1]}'"
|
||||||
|
|
||||||
|
|
||||||
proc handlePositionCommand(board: var ChessBoard, command: seq[string]) =
|
proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discardable.} =
|
||||||
case len(command):
|
|
||||||
of 2:
|
|
||||||
case command[1]:
|
|
||||||
of "startpos":
|
|
||||||
board = newDefaultChessboard()
|
|
||||||
of "current", "cur":
|
|
||||||
echo &"Current position: {board.toFEN()}"
|
|
||||||
of "pretty":
|
|
||||||
echo board.pretty()
|
|
||||||
of "print", "show":
|
|
||||||
echo board
|
|
||||||
else:
|
|
||||||
echo &"Error: position: invalid argument '{command[1]}'"
|
|
||||||
of 3:
|
|
||||||
case command[1]:
|
|
||||||
of "fen":
|
|
||||||
try:
|
|
||||||
board = newChessboardFromFEN(command[2])
|
|
||||||
except ValueError:
|
|
||||||
echo &"Error: position: invalid FEN string '{command[2]}': {getCurrentExceptionMsg()}"
|
|
||||||
else:
|
|
||||||
echo &"Error: position: unknown subcommand '{command[1]}'"
|
|
||||||
else:
|
|
||||||
echo &"Error: position: invalid number of arguments"
|
|
||||||
|
|
||||||
|
|
||||||
proc handleMoveCommand(board: ChessBoard, command: seq[string]) =
|
|
||||||
if len(command) != 2:
|
if len(command) != 2:
|
||||||
echo &"Error: move: invalid number of arguments"
|
echo &"Error: move: invalid number of arguments"
|
||||||
return
|
return
|
||||||
|
@ -1964,18 +1976,22 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]) =
|
||||||
try:
|
try:
|
||||||
startSquare = moveString[0..1].algebraicToLocation()
|
startSquare = moveString[0..1].algebraicToLocation()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo &"Error: move: invalid start square"
|
echo &"Error: move: invalid start square ({moveString[0..1]})"
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
targetSquare = moveString[2..3].algebraicToLocation()
|
targetSquare = moveString[2..3].algebraicToLocation()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo &"Error: move: invalid target square"
|
echo &"Error: move: invalid target square ({moveString[2..3]})"
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Since the user tells us just the source and target square of the move,
|
||||||
|
# we have to figure out all the flags by ourselves (whether it's a double
|
||||||
|
# push, a capture, a promotion, castling, etc.)
|
||||||
|
|
||||||
if board.grid[targetSquare.row, targetSquare.col].kind != Empty:
|
if board.grid[targetSquare.row, targetSquare.col].kind != Empty:
|
||||||
flags = flags or Capture.uint16
|
flags = flags or Capture.uint16
|
||||||
|
|
||||||
if board.grid[startSquare.row, startSquare.col].kind == Pawn and abs(startSquare.row - targetSquare.row) == 2:
|
elif board.grid[startSquare.row, startSquare.col].kind == Pawn and abs(startSquare.row - targetSquare.row) == 2:
|
||||||
flags = flags or DoublePush.uint16
|
flags = flags or DoublePush.uint16
|
||||||
|
|
||||||
if len(moveString) == 5:
|
if len(moveString) == 5:
|
||||||
|
@ -1993,10 +2009,115 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]) =
|
||||||
echo &"Error: move: invalid promotion type"
|
echo &"Error: move: invalid promotion type"
|
||||||
return
|
return
|
||||||
|
|
||||||
let move = Move(startSquare: startSquare, targetSquare: targetSquare, flags: flags)
|
|
||||||
if board.makeMove(move) == emptyMove():
|
|
||||||
echo "Error: move: illegal move"
|
|
||||||
|
|
||||||
|
var move = Move(startSquare: startSquare, targetSquare: targetSquare, flags: flags)
|
||||||
|
if board.getPiece(move.startSquare).kind == King and move.startSquare == board.getActiveColor().getKingStartingPosition():
|
||||||
|
if move.targetSquare == move.startSquare + longCastleKing():
|
||||||
|
move.flags = move.flags or CastleLong.uint16
|
||||||
|
elif move.targetSquare == move.startSquare + shortCastleKing():
|
||||||
|
move.flags = move.flags or CastleShort.uint16
|
||||||
|
result = board.makeMove(move)
|
||||||
|
if result == emptyMove():
|
||||||
|
echo &"Error: move: {moveString} is illegal"
|
||||||
|
|
||||||
|
|
||||||
|
proc handlePositionCommand(board: var ChessBoard, command: seq[string]) =
|
||||||
|
if len(command) < 2:
|
||||||
|
echo "Error: position: invalid number of arguments"
|
||||||
|
return
|
||||||
|
# Makes sure we don't leave the board in an invalid state if
|
||||||
|
# some error occurs
|
||||||
|
var tempBoard = newChessboard()
|
||||||
|
case command[1]:
|
||||||
|
of "startpos":
|
||||||
|
tempBoard = newDefaultChessboard()
|
||||||
|
if command.len() > 2:
|
||||||
|
let args = command[2].splitWhitespace()
|
||||||
|
if args.len() > 0:
|
||||||
|
var i = 0
|
||||||
|
while i < args.len():
|
||||||
|
case args[i]:
|
||||||
|
of "moves":
|
||||||
|
var j = i + 1
|
||||||
|
while j < args.len():
|
||||||
|
if handleMoveCommand(tempBoard, @["move", args[j]]) == emptyMove():
|
||||||
|
return
|
||||||
|
inc(j)
|
||||||
|
inc(i)
|
||||||
|
board = tempBoard
|
||||||
|
of "fen":
|
||||||
|
if len(command) == 2:
|
||||||
|
echo &"Current position: {board.toFEN()}"
|
||||||
|
return
|
||||||
|
var
|
||||||
|
args = command[2].splitWhitespace()
|
||||||
|
fenString = ""
|
||||||
|
stop = 0
|
||||||
|
for i, arg in args:
|
||||||
|
if arg in ["moves", ]:
|
||||||
|
break
|
||||||
|
if i > 0:
|
||||||
|
fenString &= " "
|
||||||
|
fenString &= arg
|
||||||
|
inc(stop)
|
||||||
|
args = args[stop..^1]
|
||||||
|
try:
|
||||||
|
tempBoard = newChessboardFromFEN(fenString)
|
||||||
|
except ValueError:
|
||||||
|
echo &"Error: position: {getCurrentExceptionMsg()}"
|
||||||
|
return
|
||||||
|
if args.len() > 0:
|
||||||
|
var i = 0
|
||||||
|
while i < args.len():
|
||||||
|
case args[i]:
|
||||||
|
of "moves":
|
||||||
|
var j = i + 1
|
||||||
|
while j < args.len():
|
||||||
|
if handleMoveCommand(tempBoard, @["move", args[j]]) == emptyMove():
|
||||||
|
return
|
||||||
|
inc(j)
|
||||||
|
inc(i)
|
||||||
|
board = tempBoard
|
||||||
|
of "print":
|
||||||
|
echo board
|
||||||
|
of "pretty":
|
||||||
|
echo board.pretty()
|
||||||
|
|
||||||
|
|
||||||
|
const HELP_TEXT = """Nimfish help menu:
|
||||||
|
- go: Begin a search
|
||||||
|
Subcommands:
|
||||||
|
- perft <depth> [options]: Run the performance test at the given depth (in ply) and
|
||||||
|
print the results
|
||||||
|
Options:
|
||||||
|
- bulk: Enable bulk-counting (significantly faster, gives less statistics)
|
||||||
|
- verbose: Enable move debugging (for each and every move, not recommended on large searches)
|
||||||
|
Example: go perft 5 bulk
|
||||||
|
- position: Get/set board position
|
||||||
|
Subcommands:
|
||||||
|
- fen [string]: Set the board to the given fen string if one is provided, or print
|
||||||
|
the current position as a FEN string if no arguments are given
|
||||||
|
- startpos: Set the board to the starting position
|
||||||
|
- pretty: Pretty-print the current position
|
||||||
|
- print: Print the current position using ASCII characters only
|
||||||
|
Options:
|
||||||
|
- moves {moveList}: Perform the given moves (space-separated, all-lowercase)
|
||||||
|
in algebraic notation after the position is loaded. This option only applies
|
||||||
|
to the "startpos" and "fen" subcommands: it is ignored otherwise
|
||||||
|
Examples:
|
||||||
|
- position startpos
|
||||||
|
- position fen "..." moves a2a3 a7a6
|
||||||
|
- clear: Clear the screen
|
||||||
|
- move <move>: Perform the given move in algebraic notation
|
||||||
|
- castle: Print castling rights for each side
|
||||||
|
- check: Print if the current side to move is in check
|
||||||
|
- undo: Undoes the last move that was performed. Can be used in succession
|
||||||
|
- turn: Print which side is to move
|
||||||
|
- ep: Print the current en passant target
|
||||||
|
- pretty: Shorthand for "position pretty"
|
||||||
|
- print: Shorthand for "position print"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
proc main: int =
|
proc main: int =
|
||||||
## Nimfish's control interface
|
## Nimfish's control interface
|
||||||
|
@ -2018,17 +2139,30 @@ proc main: int =
|
||||||
of "clear":
|
of "clear":
|
||||||
echo "\x1Bc"
|
echo "\x1Bc"
|
||||||
of "help":
|
of "help":
|
||||||
echo "TODO"
|
echo HELP_TEXT
|
||||||
of "go":
|
of "go":
|
||||||
handleGoCommand(board, cmd)
|
handleGoCommand(board, cmd)
|
||||||
of "position":
|
of "position":
|
||||||
handlePositionCommand(board, cmd)
|
handlePositionCommand(board, cmd)
|
||||||
of "move":
|
of "move":
|
||||||
handleMoveCommand(board, cmd)
|
handleMoveCommand(board, cmd)
|
||||||
of "pretty":
|
of "pretty", "print":
|
||||||
echo board.pretty()
|
handlePositionCommand(board, @["position", cmd[0]])
|
||||||
of "undo":
|
of "undo":
|
||||||
board.undoLastMove()
|
board.undoLastMove()
|
||||||
|
of "turn":
|
||||||
|
echo &"Active color: {board.getActiveColor()}"
|
||||||
|
of "ep":
|
||||||
|
let target = board.getEnPassantTarget()
|
||||||
|
if target != emptyLocation():
|
||||||
|
echo &"En passant target: {target.locationToAlgebraic()}"
|
||||||
|
else:
|
||||||
|
echo "En passant target: None"
|
||||||
|
of "castle":
|
||||||
|
let canCastle = board.canCastle()
|
||||||
|
echo &"Castling rights for {($board.getActiveColor()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||||
|
of "check":
|
||||||
|
echo &"{board.getActiveColor()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||||
else:
|
else:
|
||||||
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
||||||
except IOError:
|
except IOError:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -25,14 +26,19 @@ def main(args: Namespace) -> int:
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
encoding="u8"
|
encoding="u8",
|
||||||
|
text=True,
|
||||||
|
bufsize=1
|
||||||
)
|
)
|
||||||
print(f"Starting Nimfish engine at {NIMFISH.as_posix()!r}")
|
print(f"Starting Nimfish engine at {NIMFISH.as_posix()!r}")
|
||||||
nimfish_process = subprocess.Popen(NIMFISH,
|
nimfish_process = subprocess.Popen(NIMFISH,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
encoding="u8")
|
encoding="u8",
|
||||||
|
text=True,
|
||||||
|
bufsize=1
|
||||||
|
)
|
||||||
print(f"Setting position to {(args.fen if args.fen else 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1')!r}")
|
print(f"Setting position to {(args.fen if args.fen else 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1')!r}")
|
||||||
if args.fen:
|
if args.fen:
|
||||||
nimfish_process.stdin.write(f"position fen {args.fen}\n")
|
nimfish_process.stdin.write(f"position fen {args.fen}\n")
|
||||||
|
@ -43,13 +49,8 @@ def main(args: Namespace) -> int:
|
||||||
print(f"Engines started, beginning search to depth {args.ply}")
|
print(f"Engines started, beginning search to depth {args.ply}")
|
||||||
nimfish_process.stdin.write(f"go perft {args.ply} {'bulk' if args.bulk else ''}\n")
|
nimfish_process.stdin.write(f"go perft {args.ply} {'bulk' if args.bulk else ''}\n")
|
||||||
stockfish_process.stdin.write(f"go perft {args.ply}\n")
|
stockfish_process.stdin.write(f"go perft {args.ply}\n")
|
||||||
print("Search started, waiting for engine completion")
|
|
||||||
start_time = time.time()
|
|
||||||
stockfish_output = stockfish_process.communicate()[0]
|
stockfish_output = stockfish_process.communicate()[0]
|
||||||
stockfish_time = time.time() - start_time
|
|
||||||
start_time = time.time()
|
|
||||||
nimfish_output = nimfish_process.communicate()[0]
|
nimfish_output = nimfish_process.communicate()[0]
|
||||||
nimfish_time = time.time() - start_time
|
|
||||||
positions = {
|
positions = {
|
||||||
"all": {},
|
"all": {},
|
||||||
"stockfish": {},
|
"stockfish": {},
|
||||||
|
@ -89,8 +90,8 @@ def main(args: Namespace) -> int:
|
||||||
total_nodes = {"stockfish": sum(positions["stockfish"][move] for move in positions["stockfish"]),
|
total_nodes = {"stockfish": sum(positions["stockfish"][move] for move in positions["stockfish"]),
|
||||||
"nimfish": sum(positions["nimfish"][move] for move in positions["nimfish"])}
|
"nimfish": sum(positions["nimfish"][move] for move in positions["nimfish"])}
|
||||||
total_difference = total_nodes["stockfish"] - total_nodes["nimfish"]
|
total_difference = total_nodes["stockfish"] - total_nodes["nimfish"]
|
||||||
print(f"Stockfish searched {total_nodes['stockfish']} node{'' if total_nodes['stockfish'] == 1 else 's'} in {stockfish_time:.2f} seconds")
|
print(f"Stockfish searched {total_nodes['stockfish']} node{'' if total_nodes['stockfish'] == 1 else 's'}")
|
||||||
print(f"Nimfish searched {total_nodes['nimfish']} node{'' if total_nodes['nimfish'] == 1 else 's'} in {nimfish_time:.2f} seconds")
|
print(f"Nimfish searched {total_nodes['nimfish']} node{'' if total_nodes['nimfish'] == 1 else 's'}")
|
||||||
|
|
||||||
if total_difference > 0:
|
if total_difference > 0:
|
||||||
print(f"Nimfish searched {total_difference} less node{'' if total_difference == 1 else 's'} than Stockfish")
|
print(f"Nimfish searched {total_difference} less node{'' if total_difference == 1 else 's'} than Stockfish")
|
||||||
|
@ -117,13 +118,12 @@ def main(args: Namespace) -> int:
|
||||||
"To fix this, re-run the program without the --bulk option")
|
"To fix this, re-run the program without the --bulk option")
|
||||||
if extra:
|
if extra:
|
||||||
print(f" Breakdown by move type:")
|
print(f" Breakdown by move type:")
|
||||||
print(f" - Captures: {extra.group('captures')}")
|
print(f" - Captures: {extra.group('captures')}")
|
||||||
print(f" - Checks: {extra.group('checks')}")
|
print(f" - Checks: {extra.group('checks')}")
|
||||||
print(f" - En Passant: {extra.group('enPassant')}")
|
print(f" - En Passant: {extra.group('enPassant')}")
|
||||||
print(f" - Checkmates: {extra.group('checkmates')}")
|
print(f" - Checkmates: {extra.group('checkmates')}")
|
||||||
print(f" - Castles: {extra.group('castles')}")
|
print(f" - Castles: {extra.group('castles')}")
|
||||||
print(f" - Promotions: {extra.group('promotions')}")
|
print(f" - Promotions: {extra.group('promotions')}")
|
||||||
print(f" - Total: {total_nodes['nimfish']}")
|
|
||||||
|
|
||||||
elif not args.bulk:
|
elif not args.bulk:
|
||||||
print("Unable to locate move breakdown in Nimfish output")
|
print("Unable to locate move breakdown in Nimfish output")
|
||||||
|
@ -134,11 +134,11 @@ def main(args: Namespace) -> int:
|
||||||
for move in missing["stockfish"]:
|
for move in missing["stockfish"]:
|
||||||
print(f" - {move}: {positions['stockfish'][move]}")
|
print(f" - {move}: {positions['stockfish'][move]}")
|
||||||
if missing["nimfish"]:
|
if missing["nimfish"]:
|
||||||
print(" Illegal moves generated: ")
|
print("\n Illegal moves generated: ")
|
||||||
for move in missing["nimfish"]:
|
for move in missing["nimfish"]:
|
||||||
print(f" - {move}: {positions['nimfish'][move]}")
|
print(f" - {move}: {positions['nimfish'][move]}")
|
||||||
if mistakes:
|
if mistakes:
|
||||||
print(" Counting mistakes made:")
|
print("\n Counting mistakes made:")
|
||||||
for move in mistakes:
|
for move in mistakes:
|
||||||
missed = positions["stockfish"][move] - positions["nimfish"][move]
|
missed = positions["stockfish"][move] - positions["nimfish"][move]
|
||||||
print(f" - {move}: expected {positions['stockfish'][move]}, got {positions['nimfish'][move]} ({'-' if missed > 0 else '+'}{abs(missed)})")
|
print(f" - {move}: expected {positions['stockfish'][move]}, got {positions['nimfish'][move]} ({'-' if missed > 0 else '+'}{abs(missed)})")
|
||||||
|
@ -155,4 +155,5 @@ if __name__ == "__main__":
|
||||||
parser.add_argument("--bulk", action="store_true", help="Enable bulk-counting for Nimfish (faster, less debuggable)", default=False)
|
parser.add_argument("--bulk", action="store_true", help="Enable bulk-counting for Nimfish (faster, less debuggable)", default=False)
|
||||||
parser.add_argument("--stockfish", type=Path, help="Path to the stockfish executable. Defaults to '' (detected automatically)", default=None)
|
parser.add_argument("--stockfish", type=Path, help="Path to the stockfish executable. Defaults to '' (detected automatically)", default=None)
|
||||||
parser.add_argument("--nimfish", type=Path, help="Path to the nimfish executable. Defaults to '' (detected automatically)", default=None)
|
parser.add_argument("--nimfish", type=Path, help="Path to the nimfish executable. Defaults to '' (detected automatically)", default=None)
|
||||||
|
parser.add_argument("--auto-mode", action="store_true", help="Automatically attempt to detect which moves Nimfish got wrong")
|
||||||
sys.exit(main(parser.parse_args()))
|
sys.exit(main(parser.parse_args()))
|
Loading…
Reference in New Issue