Switch to bitwise flags for moves and fix perft counting mistakes
This commit is contained in:
parent
6e10cbe925
commit
9047e3a53d
|
@ -11,9 +11,6 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import ../util/matrix
|
|
||||||
|
|
||||||
export matrix
|
|
||||||
|
|
||||||
import std/strutils
|
import std/strutils
|
||||||
import std/strformat
|
import std/strformat
|
||||||
|
@ -45,18 +42,18 @@ type
|
||||||
|
|
||||||
MoveFlag* = enum
|
MoveFlag* = enum
|
||||||
## An enumeration of move flags
|
## An enumeration of move flags
|
||||||
Default = 0'i8, # No flag
|
Default = 0'u16, # No flag
|
||||||
EnPassant, # Move is a capture with en passant
|
EnPassant = 1, # Move is a capture with en passant
|
||||||
Capture, # Move is a capture
|
Capture = 2, # Move is a capture
|
||||||
DoublePush, # Move is a double pawn push
|
DoublePush = 4, # Move is a double pawn push
|
||||||
# Castling metadata
|
# Castling metadata
|
||||||
CastleLong,
|
CastleLong = 8,
|
||||||
CastleShort,
|
CastleShort = 16,
|
||||||
# Pawn promotion metadata
|
# Pawn promotion metadata
|
||||||
PromoteToQueen,
|
PromoteToQueen = 32,
|
||||||
PromoteToRook,
|
PromoteToRook = 64,
|
||||||
PromoteToBishop,
|
PromoteToBishop = 128,
|
||||||
PromoteToKnight
|
PromoteToKnight = 256
|
||||||
|
|
||||||
# Useful type aliases
|
# Useful type aliases
|
||||||
Location* = tuple[row, col: int8]
|
Location* = tuple[row, col: int8]
|
||||||
|
@ -73,7 +70,8 @@ type
|
||||||
## A chess move
|
## A chess move
|
||||||
startSquare*: Location
|
startSquare*: Location
|
||||||
targetSquare*: Location
|
targetSquare*: Location
|
||||||
flag*: MoveFlag
|
flag*: uint16
|
||||||
|
|
||||||
|
|
||||||
Position* = ref object
|
Position* = ref object
|
||||||
## A chess position
|
## A chess position
|
||||||
|
@ -107,19 +105,13 @@ type
|
||||||
|
|
||||||
# An 8x8 matrix we use for constant
|
# An 8x8 matrix we use for constant
|
||||||
# time lookup of pieces by their location
|
# time lookup of pieces by their location
|
||||||
grid: Matrix[Piece]
|
grid: seq[Piece]
|
||||||
# The current position
|
# The current position
|
||||||
position: Position
|
position: Position
|
||||||
# List of all reached positions
|
# List of all reached positions
|
||||||
positions: seq[Position]
|
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
|
# A bunch of simple utility functions
|
||||||
|
|
||||||
func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
|
func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
|
||||||
|
@ -284,7 +276,7 @@ proc newChessboard: ChessBoard =
|
||||||
## Returns a new, empty chessboard
|
## Returns a new, empty chessboard
|
||||||
new(result)
|
new(result)
|
||||||
# Turns our flat sequence into an 8x8 grid
|
# Turns our flat sequence into an 8x8 grid
|
||||||
result.grid = newMatrixFromSeq[Piece](empty, (8, 8))
|
result.grid = newSeqOfCap[Piece](64)
|
||||||
result.position = Position(attacked: (@[], @[]),
|
result.position = Position(attacked: (@[], @[]),
|
||||||
enPassantSquare: emptyLocation(),
|
enPassantSquare: emptyLocation(),
|
||||||
move: emptyMove(),
|
move: emptyMove(),
|
||||||
|
@ -304,6 +296,10 @@ proc newChessboard: ChessBoard =
|
||||||
pawns: @[])))
|
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 =
|
proc newChessboardFromFEN*(fen: string): ChessBoard =
|
||||||
## Initializes a chessboard with the
|
## Initializes a chessboard with the
|
||||||
|
@ -567,9 +563,55 @@ func getPiece*(self: ChessBoard, square: string): Piece {.inline.} =
|
||||||
|
|
||||||
|
|
||||||
func isPromotion*(move: Move): bool {.inline.} =
|
func isPromotion*(move: Move): bool {.inline.} =
|
||||||
## Returns whrther the given move is a
|
## Returns whether the given move is a
|
||||||
## pawn promotion or not
|
## pawn promotion
|
||||||
return move.flag in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]
|
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 =
|
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
||||||
|
@ -772,7 +814,7 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
break
|
break
|
||||||
if p.color == None:
|
if p.color == None:
|
||||||
continue
|
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]:
|
if p.color == piece.color.opposite() and p.kind in [Queen, Rook]:
|
||||||
ok = false
|
ok = false
|
||||||
if ok:
|
if ok:
|
||||||
|
@ -807,9 +849,9 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
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, flag: promotionType))
|
result.add(Move(startSquare: location, targetSquare: target, flag: promotionType.uint16 and flag.uint16))
|
||||||
continue
|
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] =
|
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
|
# it and stop going any further
|
||||||
if otherPiece.kind != King:
|
if otherPiece.kind != King:
|
||||||
# Can't capture the 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
|
break
|
||||||
# Target square is empty
|
# Target square is empty
|
||||||
result.add(Move(startSquare: location, targetSquare: square))
|
result.add(Move(startSquare: location, targetSquare: square))
|
||||||
|
@ -903,7 +945,7 @@ proc generateKingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
continue
|
continue
|
||||||
# Target square is empty or contains an enemy piece:
|
# Target square is empty or contains an enemy piece:
|
||||||
# All good for us!
|
# 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] =
|
proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
|
@ -941,7 +983,7 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
if otherPiece.color != None:
|
if otherPiece.color != None:
|
||||||
# Target square contains an enemy piece: capture
|
# Target square contains an enemy piece: capture
|
||||||
# it
|
# it
|
||||||
result.add(Move(startSquare: location, targetSquare: square, flag: Capture))
|
result.add(Move(startSquare: location, targetSquare: square, flag: Capture.uint16))
|
||||||
else:
|
else:
|
||||||
# Target square is empty
|
# Target square is empty
|
||||||
result.add(Move(startSquare: location, targetSquare: square))
|
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] =
|
proc generateAllMoves*(self: ChessBoard): seq[Move] =
|
||||||
## Returns the list of all possible legal moves
|
## Returns the list of all possible legal moves
|
||||||
## in the current position
|
## in the current position
|
||||||
for i, row in self.grid:
|
for i in 0..7:
|
||||||
for j, piece in row:
|
for j in 0..7:
|
||||||
if self.grid[i, j].color == self.getActiveColor():
|
if self.grid[i, j].color == self.getActiveColor():
|
||||||
for move in self.generateMoves((int8(i), int8(j))):
|
for move in self.generateMoves((int8(i), int8(j))):
|
||||||
result.add(move)
|
result.add(move)
|
||||||
|
@ -1350,9 +1392,6 @@ proc movePiece(self: ChessBoard, startSquare, targetSquare: Location, attack: bo
|
||||||
## 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
|
||||||
|
@ -1371,7 +1410,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
castlingAvailable = self.position.castlingAvailable
|
castlingAvailable = self.position.castlingAvailable
|
||||||
enPassantTarget = self.getEnPassantTarget()
|
enPassantTarget = self.getEnPassantTarget()
|
||||||
# Needed to detect draw by the 50 move rule
|
# 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
|
halfMoveClock = 0
|
||||||
else:
|
else:
|
||||||
inc(halfMoveClock)
|
inc(halfMoveClock)
|
||||||
|
@ -1384,7 +1423,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
if self.grid[enPassantPawn.row, enPassantPawn.col].color == piece.color.opposite():
|
if self.grid[enPassantPawn.row, enPassantPawn.col].color == piece.color.opposite():
|
||||||
enPassantTarget = emptyLocation()
|
enPassantTarget = emptyLocation()
|
||||||
|
|
||||||
if move.flag == DoublePush:
|
if move.isDoublePush():
|
||||||
enPassantTarget = move.targetSquare + piece.color.bottomSide()
|
enPassantTarget = move.targetSquare + piece.color.bottomSide()
|
||||||
|
|
||||||
# Castling check: have the rooks moved?
|
# Castling check: have the rooks moved?
|
||||||
|
@ -1409,7 +1448,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
# Has a rook been captured?
|
# Has a rook been captured?
|
||||||
if move.flag == Capture:
|
if move.isCapture():
|
||||||
let captured = self.grid[move.targetSquare.row, move.targetSquare.col]
|
let captured = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||||
if captured.kind == Rook:
|
if captured.kind == Rook:
|
||||||
case piece.color:
|
case piece.color:
|
||||||
|
@ -1431,7 +1470,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
# Unreachable
|
# Unreachable
|
||||||
discard
|
discard
|
||||||
# Has the king moved?
|
# 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
|
# Revoke all castling rights for the moving king
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of White:
|
of White:
|
||||||
|
@ -1454,13 +1493,13 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
)
|
)
|
||||||
# Update position metadata
|
# Update position metadata
|
||||||
|
|
||||||
if move.flag in [CastleShort, CastleLong]:
|
if move.isCastling():
|
||||||
# Move the rook onto the
|
# Move the rook onto the
|
||||||
# correct file when castling
|
# correct file when castling
|
||||||
var
|
var
|
||||||
location: Location
|
location: Location
|
||||||
target: Location
|
target: Location
|
||||||
if move.flag == CastleShort:
|
if move.getCastlingType() == CastleShort:
|
||||||
location = piece.color.kingSideRook()
|
location = piece.color.kingSideRook()
|
||||||
target = shortCastleRook()
|
target = shortCastleRook()
|
||||||
else:
|
else:
|
||||||
|
@ -1470,11 +1509,11 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
let move = Move(startSquare: location, targetSquare: location + target, flag: move.flag)
|
let move = Move(startSquare: location, targetSquare: location + target, flag: move.flag)
|
||||||
self.movePiece(move, attack=false)
|
self.movePiece(move, attack=false)
|
||||||
|
|
||||||
if move.flag == Capture:
|
if move.isCapture():
|
||||||
# Get rid of captured pieces
|
# Get rid of captured pieces
|
||||||
self.removePiece(move.targetSquare, attack=false)
|
self.removePiece(move.targetSquare, attack=false)
|
||||||
|
|
||||||
if move.flag == EnPassant:
|
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)
|
||||||
|
|
||||||
|
@ -1484,7 +1523,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
# 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
|
||||||
self.removePiece(move.targetSquare, attack=false)
|
self.removePiece(move.targetSquare, attack=false)
|
||||||
case move.flag:
|
case move.getPromotionType():
|
||||||
of PromoteToBishop:
|
of PromoteToBishop:
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color))
|
self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color))
|
||||||
of PromoteToKnight:
|
of PromoteToKnight:
|
||||||
|
@ -1551,7 +1590,8 @@ proc resetBoard*(self: ChessBoard) =
|
||||||
## in the chessboard. Warning: this can be
|
## in the chessboard. Warning: this can be
|
||||||
## expensive, especially in critical paths
|
## expensive, especially in critical paths
|
||||||
## or tight loops
|
## 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:
|
for loc in self.position.pieces.white.pawns:
|
||||||
self.grid[loc.row, loc.col] = Piece(color: White, kind: Pawn)
|
self.grid[loc.row, loc.col] = Piece(color: White, kind: Pawn)
|
||||||
for loc in self.position.pieces.black.pawns:
|
for loc in self.position.pieces.black.pawns:
|
||||||
|
@ -1585,6 +1625,8 @@ proc undoLastMove*(self: ChessBoard) =
|
||||||
|
|
||||||
proc isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
proc isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
||||||
## Returns whether the given move is legal
|
## 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)
|
return move in self.generateMoves(move.startSquare)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1596,53 +1638,83 @@ proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||||
self.doMove(move)
|
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 =
|
proc toChar*(piece: Piece): char =
|
||||||
if piece.color == White:
|
if piece.color == White:
|
||||||
return char(piece.kind).toUpperAscii()
|
return char(piece.kind).toUpperAscii()
|
||||||
return char(piece.kind)
|
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 =
|
proc pretty*(self: ChessBoard): string =
|
||||||
## Returns a colorized version of the
|
## Returns a colorized version of the
|
||||||
## board for easier visualization
|
## board for easier visualization
|
||||||
result &= "- - - - - - - -"
|
for i in 0..7:
|
||||||
|
if i > 0:
|
||||||
for i, row in self.grid:
|
result &= "\n"
|
||||||
result &= "\n"
|
for j in 0..7:
|
||||||
for j, piece in row:
|
if ((i + j) mod 2) == 0:
|
||||||
if piece.kind == Empty:
|
result &= "\x1b[39;44;1m"
|
||||||
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:
|
else:
|
||||||
result &= &"\x1b[30;1m{char(piece.kind)} "
|
result &= "\x1b[39;40;1m"
|
||||||
result &= &"\x1b[33;1m{rankToColumn(i + 1) + 1}\x1b[0m"
|
let piece = self.grid[i, j]
|
||||||
|
if piece.kind == Empty:
|
||||||
|
result &= " \x1b[0m"
|
||||||
|
else:
|
||||||
|
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 &= "\n\x1b[31;1ma b c d e f g h"
|
||||||
result &= "\x1b[0m"
|
result &= "\x1b[0m"
|
||||||
|
|
||||||
|
@ -1653,9 +1725,10 @@ proc toFEN*(self: ChessBoard): string =
|
||||||
## position in the chessboard
|
## position in the chessboard
|
||||||
var skip: int
|
var skip: int
|
||||||
# Piece placement data
|
# Piece placement data
|
||||||
for i, row in self.grid:
|
for i in 0..7:
|
||||||
skip = 0
|
skip = 0
|
||||||
for j, piece in row:
|
for j in 0..7:
|
||||||
|
let piece = self.grid[i, j]
|
||||||
if piece.kind == Empty:
|
if piece.kind == Empty:
|
||||||
inc(skip)
|
inc(skip)
|
||||||
elif skip > 0:
|
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 =
|
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
|
## Counts (and debugs) the number of legal positions reached after
|
||||||
## the given number of ply
|
## the given number of ply
|
||||||
if ply == 0:
|
|
||||||
return (1, 0, 0, 0, 0, 0, 0)
|
|
||||||
|
|
||||||
let moves = self.generateAllMoves()
|
let moves = self.generateAllMoves()
|
||||||
|
if len(moves) == 0:
|
||||||
|
result.checkmates = 1
|
||||||
|
if ply == 0:
|
||||||
|
result.nodes = 1
|
||||||
|
return
|
||||||
if ply == 1 and bulk:
|
if ply == 1 and bulk:
|
||||||
if divide:
|
if divide:
|
||||||
var postfix = ""
|
var postfix = ""
|
||||||
for move in moves:
|
for move in moves:
|
||||||
case move.flag:
|
case move.getPromotionType():
|
||||||
of PromoteToBishop:
|
of PromoteToBishop:
|
||||||
postfix = "b"
|
postfix = "b"
|
||||||
of PromoteToKnight:
|
of PromoteToKnight:
|
||||||
|
@ -1726,9 +1802,6 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
echo ""
|
echo ""
|
||||||
return (uint64(len(moves)), 0, 0, 0, 0, 0, 0)
|
return (uint64(len(moves)), 0, 0, 0, 0, 0, 0)
|
||||||
|
|
||||||
if len(moves) == 0:
|
|
||||||
inc(result.checkmates)
|
|
||||||
|
|
||||||
for move in moves:
|
for move in moves:
|
||||||
if verbose:
|
if verbose:
|
||||||
let canCastle = self.canCastle(self.getActiveColor())
|
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 "None"
|
||||||
echo "\n", self.pretty()
|
echo "\n", self.pretty()
|
||||||
self.doMove(move)
|
self.doMove(move)
|
||||||
case move.flag:
|
if move.isCapture():
|
||||||
of Capture:
|
inc(result.captures)
|
||||||
inc(result.captures)
|
if move.isCastling():
|
||||||
of CastleShort, CastleLong:
|
inc(result.castles)
|
||||||
inc(result.castles)
|
if move.isPromotion():
|
||||||
of PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook:
|
inc(result.promotions)
|
||||||
inc(result.promotions)
|
if move.isEnPassant():
|
||||||
of EnPassant:
|
inc(result.enPassant)
|
||||||
inc(result.enPassant)
|
|
||||||
else:
|
|
||||||
discard
|
|
||||||
if self.inCheck():
|
if self.inCheck():
|
||||||
# Opponent king is in check
|
# Opponent king is in check
|
||||||
inc(result.checks)
|
inc(result.checks)
|
||||||
|
@ -1779,17 +1849,18 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
self.undoLastMove()
|
self.undoLastMove()
|
||||||
if divide and (not bulk or ply > 1):
|
if divide and (not bulk or ply > 1):
|
||||||
var postfix = ""
|
var postfix = ""
|
||||||
case move.flag:
|
if move.isPromotion():
|
||||||
of PromoteToBishop:
|
case move.getPromotionType():
|
||||||
postfix = "b"
|
of PromoteToBishop:
|
||||||
of PromoteToKnight:
|
postfix = "b"
|
||||||
postfix = "n"
|
of PromoteToKnight:
|
||||||
of PromoteToRook:
|
postfix = "n"
|
||||||
postfix = "r"
|
of PromoteToRook:
|
||||||
of PromoteToQueen:
|
postfix = "r"
|
||||||
postfix = "q"
|
of PromoteToQueen:
|
||||||
else:
|
postfix = "q"
|
||||||
discard
|
else:
|
||||||
|
discard
|
||||||
echo &"{move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}{postfix}: {next.nodes}"
|
echo &"{move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}{postfix}: {next.nodes}"
|
||||||
if verbose:
|
if verbose:
|
||||||
echo ""
|
echo ""
|
||||||
|
@ -1802,6 +1873,130 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
||||||
result.checkmates += next.checkmates
|
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 =
|
proc main: int =
|
||||||
## Nimfish's control interface
|
## Nimfish's control interface
|
||||||
echo "Nimfish by nocturn9x (see LICENSE)"
|
echo "Nimfish by nocturn9x (see LICENSE)"
|
||||||
|
@ -1824,79 +2019,15 @@ proc main: int =
|
||||||
of "help":
|
of "help":
|
||||||
echo "TODO"
|
echo "TODO"
|
||||||
of "go":
|
of "go":
|
||||||
if len(cmd) < 2:
|
handleGoCommand(board, cmd)
|
||||||
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
|
|
||||||
of "position":
|
of "position":
|
||||||
case len(cmd):
|
handlePositionCommand(board, cmd)
|
||||||
of 2:
|
of "move":
|
||||||
case cmd[1]:
|
handleMoveCommand(board, cmd)
|
||||||
of "startpos":
|
of "pretty":
|
||||||
board = newDefaultChessboard()
|
echo board.pretty()
|
||||||
of "current", "cur":
|
of "undo":
|
||||||
echo &"Current position: {board.toFEN()}"
|
board.undoLastMove()
|
||||||
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
|
|
||||||
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:
|
||||||
|
|
|
@ -16,7 +16,7 @@ def main(args: Namespace) -> int:
|
||||||
print(f"Could not locate stockfish executable -> {type(e).__name__}: {e}")
|
print(f"Could not locate stockfish executable -> {type(e).__name__}: {e}")
|
||||||
return -1
|
return -1
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
print(f"Could not locate nimfish executable -> {type(e).__name__}: {e}")
|
print(f"Could not locate nimfish executable -> {type(e).__name__}: {e}")
|
||||||
return -1
|
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("--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("--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 '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()))
|
sys.exit(main(parser.parse_args()))
|
Loading…
Reference in New Issue