Switch to bitwise flags for moves and fix perft counting mistakes
This commit is contained in:
parent
5f1eb1afc0
commit
dc77c053f2
|
@ -11,9 +11,6 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import ../util/matrix
|
||||
|
||||
export matrix
|
||||
|
||||
import std/strutils
|
||||
import std/strformat
|
||||
|
@ -45,18 +42,18 @@ type
|
|||
|
||||
MoveFlag* = enum
|
||||
## An enumeration of move flags
|
||||
Default = 0'i8, # No flag
|
||||
EnPassant, # Move is a capture with en passant
|
||||
Capture, # Move is a capture
|
||||
DoublePush, # Move is a double pawn push
|
||||
Default = 0'u16, # No flag
|
||||
EnPassant = 1, # Move is a capture with en passant
|
||||
Capture = 2, # Move is a capture
|
||||
DoublePush = 4, # Move is a double pawn push
|
||||
# Castling metadata
|
||||
CastleLong,
|
||||
CastleShort,
|
||||
CastleLong = 8,
|
||||
CastleShort = 16,
|
||||
# Pawn promotion metadata
|
||||
PromoteToQueen,
|
||||
PromoteToRook,
|
||||
PromoteToBishop,
|
||||
PromoteToKnight
|
||||
PromoteToQueen = 32,
|
||||
PromoteToRook = 64,
|
||||
PromoteToBishop = 128,
|
||||
PromoteToKnight = 256
|
||||
|
||||
# Useful type aliases
|
||||
Location* = tuple[row, col: int8]
|
||||
|
@ -73,7 +70,8 @@ type
|
|||
## A chess move
|
||||
startSquare*: Location
|
||||
targetSquare*: Location
|
||||
flag*: MoveFlag
|
||||
flag*: uint16
|
||||
|
||||
|
||||
Position* = ref object
|
||||
## A chess position
|
||||
|
@ -107,19 +105,13 @@ type
|
|||
|
||||
# An 8x8 matrix we use for constant
|
||||
# time lookup of pieces by their location
|
||||
grid: Matrix[Piece]
|
||||
grid: seq[Piece]
|
||||
# The current position
|
||||
position: Position
|
||||
# List of all reached positions
|
||||
positions: seq[Position]
|
||||
|
||||
|
||||
# Initialized only once, copied every time
|
||||
var empty: seq[Piece] = @[]
|
||||
for _ in countup(0, 63):
|
||||
empty.add(Piece(kind: Empty, color: None))
|
||||
|
||||
|
||||
# A bunch of simple utility functions
|
||||
|
||||
func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
|
||||
|
@ -284,7 +276,7 @@ proc newChessboard: ChessBoard =
|
|||
## Returns a new, empty chessboard
|
||||
new(result)
|
||||
# Turns our flat sequence into an 8x8 grid
|
||||
result.grid = newMatrixFromSeq[Piece](empty, (8, 8))
|
||||
result.grid = newSeqOfCap[Piece](64)
|
||||
result.position = Position(attacked: (@[], @[]),
|
||||
enPassantSquare: emptyLocation(),
|
||||
move: emptyMove(),
|
||||
|
@ -304,6 +296,10 @@ proc newChessboard: ChessBoard =
|
|||
pawns: @[])))
|
||||
|
||||
|
||||
func coordToIndex(row, col: int): int {.inline.} = (row * 8) + col
|
||||
func `[]`(self: seq[Piece], row, column: Natural): Piece {.inline.} = self[coordToIndex(row, column)]
|
||||
proc `[]=`(self: var seq[Piece], row, column: Natural, piece: Piece) {.inline.} = self[coordToIndex(row, column)] = piece
|
||||
|
||||
|
||||
proc newChessboardFromFEN*(fen: string): ChessBoard =
|
||||
## Initializes a chessboard with the
|
||||
|
@ -567,9 +563,55 @@ func getPiece*(self: ChessBoard, square: string): Piece {.inline.} =
|
|||
|
||||
|
||||
func isPromotion*(move: Move): bool {.inline.} =
|
||||
## Returns whrther the given move is a
|
||||
## pawn promotion or not
|
||||
return move.flag in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]
|
||||
## Returns whether the given move is a
|
||||
## pawn promotion
|
||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
||||
if (move.flag and promotion.uint16) != 0:
|
||||
return true
|
||||
|
||||
|
||||
func getPromotionType*(move: Move): MoveFlag {.inline.} =
|
||||
## Returns the promotion type of the given move.
|
||||
## The return value of this function is only valid
|
||||
## if isPromotion() returns true
|
||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]:
|
||||
if (move.flag and promotion.uint16) != 0:
|
||||
return promotion
|
||||
|
||||
|
||||
func isCapture*(move: Move): bool {.inline.} =
|
||||
## Returns whether the given move is a
|
||||
## cature
|
||||
result = (move.flag and Capture.uint16) != 0
|
||||
|
||||
|
||||
func isCastling*(move: Move): bool {.inline.} =
|
||||
## Returns whether the given move is a
|
||||
## castle
|
||||
for flag in [CastleLong, CastleShort]:
|
||||
if (move.flag and flag.uint16) != 0:
|
||||
return true
|
||||
|
||||
|
||||
func getCastlingType*(move: Move): MoveFlag {.inline.} =
|
||||
## Returns the castling type of the given move.
|
||||
## The return value of this function is only valid
|
||||
## if isCastling() returns true
|
||||
for flag in [CastleLong, CastleShort]:
|
||||
if (move.flag and flag.uint16) != 0:
|
||||
return flag
|
||||
|
||||
|
||||
func isEnPassant*(move: Move): bool {.inline.} =
|
||||
## Returns whether the given move is an
|
||||
## en passant capture
|
||||
result = (move.flag and EnPassant.uint16) != 0
|
||||
|
||||
|
||||
func isDoublePush*(move: Move): bool {.inline.} =
|
||||
## Returns whether the given move is a
|
||||
## double pawn push
|
||||
result = (move.flag and DoublePush.uint16) != 0
|
||||
|
||||
|
||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
||||
|
@ -772,7 +814,7 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
break
|
||||
if p.color == None:
|
||||
continue
|
||||
# Bishops can't create checks through en passant (I'm pretty sure at least)
|
||||
# Bishops can't create checks through en passant
|
||||
if p.color == piece.color.opposite() and p.kind in [Queen, Rook]:
|
||||
ok = false
|
||||
if ok:
|
||||
|
@ -807,9 +849,9 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
if target.row == piece.color.getLastRow():
|
||||
# Pawn reached the other side of the board: generate all potential piece promotions
|
||||
for promotionType in [PromoteToKnight, PromoteToBishop, PromoteToRook, PromoteToQueen]:
|
||||
result.add(Move(startSquare: location, targetSquare: target, flag: promotionType))
|
||||
result.add(Move(startSquare: location, targetSquare: target, flag: promotionType.uint16 and flag.uint16))
|
||||
continue
|
||||
result.add(Move(startSquare: location, targetSquare: target, flag: flag))
|
||||
result.add(Move(startSquare: location, targetSquare: target, flag: flag.uint16))
|
||||
|
||||
|
||||
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
|
@ -855,7 +897,7 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
# it and stop going any further
|
||||
if otherPiece.kind != King:
|
||||
# Can't capture the king
|
||||
result.add(Move(startSquare: location, targetSquare: square, flag: Capture))
|
||||
result.add(Move(startSquare: location, targetSquare: square, flag: Capture.uint16))
|
||||
break
|
||||
# Target square is empty
|
||||
result.add(Move(startSquare: location, targetSquare: square))
|
||||
|
@ -903,7 +945,7 @@ proc generateKingMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
continue
|
||||
# Target square is empty or contains an enemy piece:
|
||||
# All good for us!
|
||||
result.add(Move(startSquare: location, targetSquare: square, flag: flag))
|
||||
result.add(Move(startSquare: location, targetSquare: square, flag: flag.uint16))
|
||||
|
||||
|
||||
proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
|
@ -941,7 +983,7 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
if otherPiece.color != None:
|
||||
# Target square contains an enemy piece: capture
|
||||
# it
|
||||
result.add(Move(startSquare: location, targetSquare: square, flag: Capture))
|
||||
result.add(Move(startSquare: location, targetSquare: square, flag: Capture.uint16))
|
||||
else:
|
||||
# Target square is empty
|
||||
result.add(Move(startSquare: location, targetSquare: square))
|
||||
|
@ -967,8 +1009,8 @@ proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
proc generateAllMoves*(self: ChessBoard): seq[Move] =
|
||||
## Returns the list of all possible legal moves
|
||||
## in the current position
|
||||
for i, row in self.grid:
|
||||
for j, piece in row:
|
||||
for i in 0..7:
|
||||
for j in 0..7:
|
||||
if self.grid[i, j].color == self.getActiveColor():
|
||||
for move in self.generateMoves((int8(i), int8(j))):
|
||||
result.add(move)
|
||||
|
@ -1350,9 +1392,6 @@ proc movePiece(self: ChessBoard, startSquare, targetSquare: Location, attack: bo
|
|||
## Like the other movePiece(), but with two locations
|
||||
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare), attack)
|
||||
|
||||
|
||||
|
||||
|
||||
proc doMove(self: ChessBoard, move: Move) =
|
||||
## Internal function called by makeMove after
|
||||
## performing legality checks. Can be used in
|
||||
|
@ -1371,7 +1410,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
castlingAvailable = self.position.castlingAvailable
|
||||
enPassantTarget = self.getEnPassantTarget()
|
||||
# Needed to detect draw by the 50 move rule
|
||||
if piece.kind == Pawn or move.flag == Capture:
|
||||
if piece.kind == Pawn or move.isCapture():
|
||||
halfMoveClock = 0
|
||||
else:
|
||||
inc(halfMoveClock)
|
||||
|
@ -1384,7 +1423,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
if self.grid[enPassantPawn.row, enPassantPawn.col].color == piece.color.opposite():
|
||||
enPassantTarget = emptyLocation()
|
||||
|
||||
if move.flag == DoublePush:
|
||||
if move.isDoublePush():
|
||||
enPassantTarget = move.targetSquare + piece.color.bottomSide()
|
||||
|
||||
# Castling check: have the rooks moved?
|
||||
|
@ -1409,7 +1448,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
else:
|
||||
discard
|
||||
# Has a rook been captured?
|
||||
if move.flag == Capture:
|
||||
if move.isCapture():
|
||||
let captured = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||
if captured.kind == Rook:
|
||||
case piece.color:
|
||||
|
@ -1431,7 +1470,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
# Unreachable
|
||||
discard
|
||||
# Has the king moved?
|
||||
if piece.kind == King or move.flag in [CastleLong, CastleShort]:
|
||||
if piece.kind == King or move.isCastling():
|
||||
# Revoke all castling rights for the moving king
|
||||
case piece.color:
|
||||
of White:
|
||||
|
@ -1454,13 +1493,13 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
)
|
||||
# Update position metadata
|
||||
|
||||
if move.flag in [CastleShort, CastleLong]:
|
||||
if move.isCastling():
|
||||
# Move the rook onto the
|
||||
# correct file when castling
|
||||
var
|
||||
location: Location
|
||||
target: Location
|
||||
if move.flag == CastleShort:
|
||||
if move.getCastlingType() == CastleShort:
|
||||
location = piece.color.kingSideRook()
|
||||
target = shortCastleRook()
|
||||
else:
|
||||
|
@ -1470,11 +1509,11 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
let move = Move(startSquare: location, targetSquare: location + target, flag: move.flag)
|
||||
self.movePiece(move, attack=false)
|
||||
|
||||
if move.flag == Capture:
|
||||
if move.isCapture():
|
||||
# Get rid of captured pieces
|
||||
self.removePiece(move.targetSquare, attack=false)
|
||||
|
||||
if move.flag == EnPassant:
|
||||
if move.isEnPassant():
|
||||
# Make the en passant pawn disappear
|
||||
self.removePiece(move.targetSquare + piece.color.bottomSide(), attack=false)
|
||||
|
||||
|
@ -1484,7 +1523,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
# Move is a pawn promotion: get rid of the pawn
|
||||
# and spawn a new piece
|
||||
self.removePiece(move.targetSquare, attack=false)
|
||||
case move.flag:
|
||||
case move.getPromotionType():
|
||||
of PromoteToBishop:
|
||||
self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color))
|
||||
of PromoteToKnight:
|
||||
|
@ -1551,7 +1590,8 @@ proc resetBoard*(self: ChessBoard) =
|
|||
## in the chessboard. Warning: this can be
|
||||
## expensive, especially in critical paths
|
||||
## or tight loops
|
||||
self.grid = newMatrixFromSeq[Piece](empty, (8, 8))
|
||||
for i in 0..63:
|
||||
self.grid[i] = emptyPiece()
|
||||
for loc in self.position.pieces.white.pawns:
|
||||
self.grid[loc.row, loc.col] = Piece(color: White, kind: Pawn)
|
||||
for loc in self.position.pieces.black.pawns:
|
||||
|
@ -1585,6 +1625,8 @@ proc undoLastMove*(self: ChessBoard) =
|
|||
|
||||
proc isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
||||
## Returns whether the given move is legal
|
||||
if self.grid[move.startSquare.row, move.startSquare.col].color != self.getActiveColor():
|
||||
return false
|
||||
return move in self.generateMoves(move.startSquare)
|
||||
|
||||
|
||||
|
@ -1596,53 +1638,83 @@ proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
|||
self.doMove(move)
|
||||
|
||||
|
||||
proc `$`*(self: ChessBoard): string =
|
||||
result &= "- - - - - - - -"
|
||||
for i, row in self.grid:
|
||||
result &= "\n"
|
||||
for piece in row:
|
||||
if piece.kind == Empty:
|
||||
result &= "x "
|
||||
continue
|
||||
if piece.color == White:
|
||||
result &= &"{char(piece.kind).toUpperAscii()} "
|
||||
else:
|
||||
result &= &"{char(piece.kind)} "
|
||||
result &= &"{rankToColumn(i + 1) + 1}"
|
||||
result &= "\n- - - - - - - -"
|
||||
result &= "\na b c d e f g h"
|
||||
|
||||
|
||||
proc toChar*(piece: Piece): char =
|
||||
if piece.color == White:
|
||||
return char(piece.kind).toUpperAscii()
|
||||
return char(piece.kind)
|
||||
|
||||
|
||||
proc `$`*(self: ChessBoard): string =
|
||||
result &= "- - - - - - - -"
|
||||
for i in 0..7:
|
||||
result &= "\n"
|
||||
for j in 0..7:
|
||||
let piece = self.grid[i, j]
|
||||
if piece.kind == Empty:
|
||||
result &= "x "
|
||||
continue
|
||||
result &= &"{piece.toChar()} "
|
||||
result &= &"{rankToColumn(i + 1) + 1}"
|
||||
result &= "\n- - - - - - - -"
|
||||
result &= "\na b c d e f g h"
|
||||
|
||||
|
||||
proc toPretty*(piece: Piece): string =
|
||||
case piece.color:
|
||||
of Black:
|
||||
case piece.kind:
|
||||
of King:
|
||||
return "\U2654"
|
||||
of Queen:
|
||||
return "\U2655"
|
||||
of Rook:
|
||||
return "\U2656"
|
||||
of Bishop:
|
||||
return "\U2657"
|
||||
of Knight:
|
||||
return "\U2658"
|
||||
of Pawn:
|
||||
return "\U2659"
|
||||
else:
|
||||
discard
|
||||
of White:
|
||||
case piece.kind:
|
||||
of King:
|
||||
return "\U265A"
|
||||
of Queen:
|
||||
return "\U265B"
|
||||
of Rook:
|
||||
return "\U265C"
|
||||
of Bishop:
|
||||
return "\U265D"
|
||||
of Knight:
|
||||
return "\U265E"
|
||||
of Pawn:
|
||||
return "\U265F"
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc pretty*(self: ChessBoard): string =
|
||||
## Returns a colorized version of the
|
||||
## board for easier visualization
|
||||
result &= "- - - - - - - -"
|
||||
|
||||
for i, row in self.grid:
|
||||
for i in 0..7:
|
||||
if i > 0:
|
||||
result &= "\n"
|
||||
for j, piece in row:
|
||||
for j in 0..7:
|
||||
if ((i + j) mod 2) == 0:
|
||||
result &= "\x1b[39;44;1m"
|
||||
else:
|
||||
result &= "\x1b[39;40;1m"
|
||||
let piece = self.grid[i, j]
|
||||
if piece.kind == Empty:
|
||||
result &= "\x1b[36;1mx"
|
||||
# Avoids the color overflowing
|
||||
# onto the numbers
|
||||
if j < 7:
|
||||
result &= " \x1b[0m"
|
||||
else:
|
||||
result &= "\x1b[0m "
|
||||
continue
|
||||
if piece.color == White:
|
||||
result &= &"\x1b[37;1m{char(piece.kind).toUpperAscii()}\x1b[0m "
|
||||
else:
|
||||
result &= &"\x1b[30;1m{char(piece.kind)} "
|
||||
result &= &"{piece.toPretty()} \x1b[0m"
|
||||
result &= &" \x1b[33;1m{rankToColumn(i + 1) + 1}\x1b[0m"
|
||||
|
||||
result &= "\n- - - - - - - -"
|
||||
result &= "\n\x1b[31;1ma b c d e f g h"
|
||||
result &= "\x1b[0m"
|
||||
|
||||
|
@ -1653,9 +1725,10 @@ proc toFEN*(self: ChessBoard): string =
|
|||
## position in the chessboard
|
||||
var skip: int
|
||||
# Piece placement data
|
||||
for i, row in self.grid:
|
||||
for i in 0..7:
|
||||
skip = 0
|
||||
for j, piece in row:
|
||||
for j in 0..7:
|
||||
let piece = self.grid[i, j]
|
||||
if piece.kind == Empty:
|
||||
inc(skip)
|
||||
elif skip > 0:
|
||||
|
@ -1702,15 +1775,18 @@ proc toFEN*(self: ChessBoard): string =
|
|||
proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = false, bulk: bool = false): CountData =
|
||||
## Counts (and debugs) the number of legal positions reached after
|
||||
## the given number of ply
|
||||
if ply == 0:
|
||||
return (1, 0, 0, 0, 0, 0, 0)
|
||||
|
||||
let moves = self.generateAllMoves()
|
||||
if len(moves) == 0:
|
||||
result.checkmates = 1
|
||||
if ply == 0:
|
||||
result.nodes = 1
|
||||
return
|
||||
if ply == 1 and bulk:
|
||||
if divide:
|
||||
var postfix = ""
|
||||
for move in moves:
|
||||
case move.flag:
|
||||
case move.getPromotionType():
|
||||
of PromoteToBishop:
|
||||
postfix = "b"
|
||||
of PromoteToKnight:
|
||||
|
@ -1726,9 +1802,6 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
echo ""
|
||||
return (uint64(len(moves)), 0, 0, 0, 0, 0, 0)
|
||||
|
||||
if len(moves) == 0:
|
||||
inc(result.checkmates)
|
||||
|
||||
for move in moves:
|
||||
if verbose:
|
||||
let canCastle = self.canCastle(self.getActiveColor())
|
||||
|
@ -1747,17 +1820,14 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
echo "None"
|
||||
echo "\n", self.pretty()
|
||||
self.doMove(move)
|
||||
case move.flag:
|
||||
of Capture:
|
||||
if move.isCapture():
|
||||
inc(result.captures)
|
||||
of CastleShort, CastleLong:
|
||||
if move.isCastling():
|
||||
inc(result.castles)
|
||||
of PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook:
|
||||
if move.isPromotion():
|
||||
inc(result.promotions)
|
||||
of EnPassant:
|
||||
if move.isEnPassant():
|
||||
inc(result.enPassant)
|
||||
else:
|
||||
discard
|
||||
if self.inCheck():
|
||||
# Opponent king is in check
|
||||
inc(result.checks)
|
||||
|
@ -1779,7 +1849,8 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
self.undoLastMove()
|
||||
if divide and (not bulk or ply > 1):
|
||||
var postfix = ""
|
||||
case move.flag:
|
||||
if move.isPromotion():
|
||||
case move.getPromotionType():
|
||||
of PromoteToBishop:
|
||||
postfix = "b"
|
||||
of PromoteToKnight:
|
||||
|
@ -1802,6 +1873,130 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
result.checkmates += next.checkmates
|
||||
|
||||
|
||||
proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
||||
if len(command) < 2:
|
||||
echo &"Error: go: invalid number of arguments"
|
||||
return
|
||||
case command[1]:
|
||||
of "perft":
|
||||
if len(command) == 2:
|
||||
echo &"Error: go: perft: invalid number of arguments"
|
||||
return
|
||||
var
|
||||
args = command[2].splitWhitespace()
|
||||
bulk = false
|
||||
verbose = false
|
||||
if args.len() > 1:
|
||||
var ok = true
|
||||
for arg in args[1..^1]:
|
||||
case arg:
|
||||
of "bulk-count", "bulk":
|
||||
bulk = true
|
||||
of "verbose":
|
||||
verbose = true
|
||||
else:
|
||||
echo &"Error: go: perft: invalid argument '{args[1]}'"
|
||||
ok = false
|
||||
break
|
||||
if not ok:
|
||||
return
|
||||
try:
|
||||
let ply = parseInt(args[0])
|
||||
if bulk:
|
||||
echo &"\nNodes searched (bulk-counting: on): {board.perft(ply, divide=true, bulk=true, verbose=verbose).nodes}\n"
|
||||
else:
|
||||
let data = board.perft(ply, divide=true, verbose=verbose)
|
||||
echo &"\nNodes searched (bulk-counting: off): {data.nodes}"
|
||||
echo &" - Captures: {data.captures}"
|
||||
echo &" - Checks: {data.checks}"
|
||||
echo &" - E.P: {data.enPassant}"
|
||||
echo &" - Checkmates: {data.checkmates}"
|
||||
echo &" - Castles: {data.castles}"
|
||||
echo &" - Promotions: {data.promotions}"
|
||||
echo ""
|
||||
except ValueError:
|
||||
echo "Error: go: perft: invalid depth"
|
||||
else:
|
||||
echo &"Error: go: unknown subcommand '{command[1]}'"
|
||||
|
||||
|
||||
proc handlePositionCommand(board: var ChessBoard, command: seq[string]) =
|
||||
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:
|
||||
echo &"Error: move: invalid number of arguments"
|
||||
return
|
||||
let moveString = command[1]
|
||||
if len(moveString) notin 4..5:
|
||||
echo &"Error: move: invalid move syntax"
|
||||
return
|
||||
var
|
||||
startSquare: Location
|
||||
targetSquare: Location
|
||||
flag: uint16
|
||||
|
||||
try:
|
||||
startSquare = moveString[0..1].algebraicToLocation()
|
||||
except ValueError:
|
||||
echo &"Error: move: invalid start square"
|
||||
return
|
||||
try:
|
||||
targetSquare = moveString[2..3].algebraicToLocation()
|
||||
except ValueError:
|
||||
echo &"Error: move: invalid target square"
|
||||
return
|
||||
|
||||
if board.grid[targetSquare.row, targetSquare.col].kind != Empty:
|
||||
flag = flag or Capture.uint16
|
||||
|
||||
if board.grid[startSquare.row, startSquare.col].kind == Pawn and abs(startSquare.row - targetSquare.row) == 2:
|
||||
flag = flag or DoublePush.uint16
|
||||
|
||||
if len(moveString) == 5:
|
||||
# Promotion
|
||||
case moveString[4]:
|
||||
of 'b':
|
||||
flag = flag and PromoteToBishop.uint16
|
||||
of 'n':
|
||||
flag = flag and PromoteToKnight.uint16
|
||||
of 'q':
|
||||
flag = flag and PromoteToQueen.uint16
|
||||
of 'r':
|
||||
flag = flag and PromoteToRook.uint16
|
||||
else:
|
||||
echo &"Error: move: invalid promotion type"
|
||||
return
|
||||
|
||||
let move = Move(startSquare: startSquare, targetSquare: targetSquare, flag: flag)
|
||||
if board.makeMove(move) == emptyMove():
|
||||
echo "Error: move: illegal move"
|
||||
|
||||
|
||||
proc main: int =
|
||||
## Nimfish's control interface
|
||||
echo "Nimfish by nocturn9x (see LICENSE)"
|
||||
|
@ -1824,79 +2019,15 @@ proc main: int =
|
|||
of "help":
|
||||
echo "TODO"
|
||||
of "go":
|
||||
if len(cmd) < 2:
|
||||
echo &"Error: go: invalid number of arguments"
|
||||
continue
|
||||
case cmd[1]:
|
||||
of "perft":
|
||||
if len(cmd) == 2:
|
||||
echo &"Error: go: perft: invalid number of arguments"
|
||||
continue
|
||||
var
|
||||
args = cmd[2].splitWhitespace()
|
||||
bulk = false
|
||||
verbose = false
|
||||
if args.len() > 1:
|
||||
var ok = true
|
||||
for arg in args[1..^1]:
|
||||
case arg:
|
||||
of "bulk-count", "bulk":
|
||||
bulk = true
|
||||
of "verbose":
|
||||
verbose = true
|
||||
else:
|
||||
echo &"Error: go: perft: invalid argument '{args[1]}'"
|
||||
ok = false
|
||||
break
|
||||
if not ok:
|
||||
continue
|
||||
try:
|
||||
let ply = parseInt(args[0])
|
||||
if bulk:
|
||||
echo &"\nNodes searched (bulk-counting: on): {board.perft(ply, divide=true, bulk=true, verbose=verbose).nodes}\n"
|
||||
else:
|
||||
let data = board.perft(ply, divide=true, verbose=verbose)
|
||||
echo &"\nNodes searched (bulk-counting: off): {data.nodes}"
|
||||
echo &" - Captures: {data.captures}"
|
||||
echo &" - Checks: {data.checks}"
|
||||
echo &" - E.P: {data.enPassant}"
|
||||
echo &" - Checkmates: {data.checkmates}"
|
||||
echo &" - Castles: {data.castles}"
|
||||
echo &" - Promotions: {data.promotions}"
|
||||
echo ""
|
||||
except ValueError:
|
||||
echo "Error: go: perft: invalid depth"
|
||||
continue
|
||||
else:
|
||||
echo &"Error: go: unknown subcommand '{cmd[1]}'"
|
||||
continue
|
||||
handleGoCommand(board, cmd)
|
||||
of "position":
|
||||
case len(cmd):
|
||||
of 2:
|
||||
case cmd[1]:
|
||||
of "startpos":
|
||||
board = newDefaultChessboard()
|
||||
of "current", "cur":
|
||||
echo &"Current position: {board.toFEN()}"
|
||||
handlePositionCommand(board, cmd)
|
||||
of "move":
|
||||
handleMoveCommand(board, cmd)
|
||||
of "pretty":
|
||||
echo board.pretty()
|
||||
of "print", "show":
|
||||
echo board
|
||||
else:
|
||||
echo &"Error: position: invalid argument '{cmd[1]}'"
|
||||
continue
|
||||
of 3:
|
||||
case cmd[1]:
|
||||
of "fen":
|
||||
try:
|
||||
board = newChessboardFromFEN(cmd[2])
|
||||
except ValueError:
|
||||
echo &"Error: position: invalid FEN string '{cmd[2]}': {getCurrentExceptionMsg()}"
|
||||
else:
|
||||
echo &"Error: position: unknown subcommand '{cmd[1]}'"
|
||||
else:
|
||||
echo &"Error: position: invalid number of arguments"
|
||||
continue
|
||||
of "undo":
|
||||
board.undoLastMove()
|
||||
else:
|
||||
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
||||
except IOError:
|
||||
|
|
|
@ -16,7 +16,7 @@ def main(args: Namespace) -> int:
|
|||
print(f"Could not locate stockfish executable -> {type(e).__name__}: {e}")
|
||||
return -1
|
||||
try:
|
||||
NIMFISH = (args.nimfish or (Path.cwd() / "nimfish")).resolve(strict=True)
|
||||
NIMFISH = (args.nimfish or (Path.cwd() / "bin" / "nimfish")).resolve(strict=True)
|
||||
except Exception as e:
|
||||
print(f"Could not locate nimfish executable -> {type(e).__name__}: {e}")
|
||||
return -1
|
||||
|
@ -154,5 +154,5 @@ if __name__ == "__main__":
|
|||
parser.add_argument("--ply", "-d", type=int, required=True, help="The depth to stop at, expressed in plys (half-moves)")
|
||||
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("--nimfish", type=Path, help="Path to the nimfish executable. Defaults to 'nimfish'", default=Path("nimfish"))
|
||||
parser.add_argument("--nimfish", type=Path, help="Path to the nimfish executable. Defaults to '' (detected automatically)", default=None)
|
||||
sys.exit(main(parser.parse_args()))
|
Loading…
Reference in New Issue