More bug fixes. Still borked. Improve modularity
This commit is contained in:
parent
0496047164
commit
64c30b8a90
|
@ -50,8 +50,8 @@ type
|
|||
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
||||
enPassantSquare*: Square
|
||||
|
||||
# Active color
|
||||
turn: PieceColor
|
||||
# The side to move
|
||||
sideToMove: PieceColor
|
||||
# Positional bitboards for all pieces
|
||||
pieces: tuple[white, black: tuple[king, queens, rooks, bishops, knights, pawns: Bitboard]]
|
||||
# Pinned pieces for each side
|
||||
|
@ -69,14 +69,12 @@ type
|
|||
position: Position
|
||||
# List of all previously reached positions
|
||||
positions: seq[Position]
|
||||
# Index of the current position
|
||||
currPos: int
|
||||
|
||||
|
||||
# A bunch of simple utility functions and forward declarations
|
||||
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.}
|
||||
proc isLegal(self: ChessBoard, move: Move): bool {.inline.}
|
||||
proc doMove(self: ChessBoard, move: Move)
|
||||
proc doMove*(self: ChessBoard, move: Move)
|
||||
proc pretty*(self: ChessBoard): string
|
||||
proc spawnPiece(self: ChessBoard, square: Square, piece: Piece)
|
||||
proc toFEN*(self: ChessBoard): string
|
||||
|
@ -89,13 +87,16 @@ proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} =
|
|||
for x in other:
|
||||
self.add(x)
|
||||
|
||||
proc updateBoard*(self: ChessBoard)
|
||||
proc update*(self: ChessBoard)
|
||||
|
||||
|
||||
func setSideToMove*(self: ChessBoard, side: PieceColor) {.inline.} =
|
||||
self.position.sideToMove = side
|
||||
|
||||
# A bunch of getters
|
||||
func getSideToMove*(self: ChessBoard): PieceColor {.inline.} =
|
||||
## Returns the currently side to move
|
||||
return self.position.turn
|
||||
return self.position.sideToMove
|
||||
|
||||
|
||||
func getEnPassantTarget*(self: ChessBoard): Square {.inline.} =
|
||||
|
@ -103,6 +104,11 @@ func getEnPassantTarget*(self: ChessBoard): Square {.inline.} =
|
|||
return self.position.enPassantSquare
|
||||
|
||||
|
||||
func getPlyFromRoot*(self: ChessBoard): int8 {.inline.} =
|
||||
## Returns the current distance from the root in plys
|
||||
return self.position.plyFromRoot
|
||||
|
||||
|
||||
func getMoveCount*(self: ChessBoard): int {.inline.} =
|
||||
## Returns the number of full moves that
|
||||
## have been played
|
||||
|
@ -136,7 +142,7 @@ func getStartRank(piece: Piece): int {.inline.} =
|
|||
return 0
|
||||
|
||||
|
||||
func getKingStartingSquare(color: PieceColor): Square {.inline.} =
|
||||
func getKingStartingSquare*(color: PieceColor): Square {.inline.} =
|
||||
## Retrieves the starting square of the king
|
||||
## for the given color
|
||||
case color:
|
||||
|
@ -148,14 +154,16 @@ func getKingStartingSquare(color: PieceColor): Square {.inline.} =
|
|||
discard
|
||||
|
||||
|
||||
func kingSideRook(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
||||
func queenSideRook(color: PieceColor): Square {.inline.} = (if color == White: "a8".toSquare() else: "a1".toSquare())
|
||||
func longCastleKing(color: PieceColor): Square {.inline.} = (if color == White: "c1".toSquare() else: "c8".toSquare())
|
||||
func shortCastleKing(color: PieceColor): Square {.inline.} = (if color == White: "g1".toSquare() else: "g8".toSquare())
|
||||
func longCastleRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 3) else: makeSquare(7, 5))
|
||||
func shortCastleRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(0, 0) else: makeSquare(0, 2))
|
||||
# FIXME: Check this shit.
|
||||
func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
||||
func queenSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "a8".toSquare() else: "a1".toSquare())
|
||||
func longCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "c1".toSquare() else: "c8".toSquare())
|
||||
func shortCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "g1".toSquare() else: "g8".toSquare())
|
||||
func longCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "d1".toSquare() else: "d8".toSquare())
|
||||
func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "f1".toSquare() else: "f8".toSquare())
|
||||
|
||||
proc inCheck(self: ChessBoard, side: PieceColor): bool
|
||||
|
||||
proc inCheck*(self: ChessBoard, side: PieceColor): bool
|
||||
|
||||
|
||||
proc newChessboard: ChessBoard =
|
||||
|
@ -163,8 +171,7 @@ proc newChessboard: ChessBoard =
|
|||
new(result)
|
||||
for i in 0..63:
|
||||
result.grid[i] = nullPiece()
|
||||
result.currPos = -1
|
||||
result.position = Position(enPassantSquare: nullSquare(), turn: White)
|
||||
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White)
|
||||
|
||||
# Indexing operations
|
||||
func `[]`(self: array[64, Piece], square: Square): Piece {.inline.} = self[square.int8]
|
||||
|
@ -314,9 +321,9 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
|
|||
# Active color
|
||||
case c:
|
||||
of 'w':
|
||||
result.position.turn = White
|
||||
result.position.sideToMove = White
|
||||
of 'b':
|
||||
result.position.turn = Black
|
||||
result.position.sideToMove = Black
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: invalid active color identifier '{c}'")
|
||||
of 2:
|
||||
|
@ -504,12 +511,9 @@ func getFlags*(move: Move): seq[MoveFlag] =
|
|||
result.add(Default)
|
||||
|
||||
|
||||
func getKingSquare(self: ChessBoard, color: PieceColor = None): Square {.inline.} =
|
||||
func getKingSquare*(self: ChessBoard, color: PieceColor): Square {.inline.} =
|
||||
## Returns the square of the king for the given
|
||||
## side (if it is None, the side to move is used)
|
||||
var color = color
|
||||
if color == None:
|
||||
color = self.getSideToMove()
|
||||
## side
|
||||
case color:
|
||||
of White:
|
||||
return self.position.pieces.white.king.toSquare()
|
||||
|
@ -539,7 +543,6 @@ proc getOccupancy(self: ChessBoard): Bitboard =
|
|||
result = self.getOccupancyFor(Black) or self.getOccupancyFor(White)
|
||||
|
||||
|
||||
|
||||
proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the attack bitboard for the given square from
|
||||
## the pawns of the given side
|
||||
|
@ -601,7 +604,7 @@ proc getSlidingAttacks(self: ChessBoard, square: Square, attacker: PieceColor):
|
|||
result = result or queen.toBitboard()
|
||||
|
||||
|
||||
proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
||||
proc getAttacksTo*(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Computes the attack bitboard for the given square from
|
||||
## the given side
|
||||
result = Bitboard(0)
|
||||
|
@ -613,12 +616,30 @@ proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitbo
|
|||
result = result or self.getSlidingAttacks(square, attacker)
|
||||
|
||||
|
||||
proc updateCheckers(self: ChessBoard) =
|
||||
let side = self.getSideToMove()
|
||||
let checkers = self.getAttacksTo(self.getKingSquare(side), side.opposite())
|
||||
case side:
|
||||
of White:
|
||||
self.position.checkers.white = checkers
|
||||
of Black:
|
||||
self.position.checkers.black = checkers
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc inCheck(self: ChessBoard, side: PieceColor): bool =
|
||||
## Returns if the current side to move is in check
|
||||
return self.getAttacksTo(self.getKingSquare(side), side.opposite()) != 0
|
||||
case self.getSideToMove():
|
||||
of White:
|
||||
return self.position.checkers.white != 0
|
||||
of Black:
|
||||
return self.position.checkers.black != 0
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc canCastle(self: ChessBoard, side: PieceColor): tuple[king, queen: bool] =
|
||||
proc canCastle*(self: ChessBoard, side: PieceColor): tuple[king, queen: bool] =
|
||||
## Returns if the current side to move can castle
|
||||
return (false, false) # TODO
|
||||
|
||||
|
@ -659,10 +680,12 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
|
|||
pawns = self.getBitboard(Pawn, sideToMove)
|
||||
# We can only capture enemy pieces (except the king)
|
||||
enemyPieces = self.getCapturablePieces(nonSideToMove)
|
||||
enemyPawns = self.getBitboard(Pawn, nonSideToMove)
|
||||
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
||||
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
||||
epTarget = self.getEnPassantTarget()
|
||||
let epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
|
||||
var epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
|
||||
epBitboard = epBitboard and enemyPawns
|
||||
# Top right attacks
|
||||
for square in rightMovement and enemyPieces:
|
||||
moves.add(createMove(square.toBitboard().backwardLeftRelativeTo(sideToMove), square, Capture))
|
||||
|
@ -704,13 +727,11 @@ proc generateRookMovements(self: ChessBoard, moves: var MoveList) =
|
|||
let
|
||||
sideToMove = self.getSideToMove()
|
||||
occupancy = self.getOccupancy()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
rooks = self.getBitboard(Rook, sideToMove)
|
||||
for square in rooks:
|
||||
let blockers = occupancy and Rook.getRelevantBlockers(square)
|
||||
var moveset = getRookMoves(square, blockers)
|
||||
# Can't move over our own pieces
|
||||
moveset = moveset and not friendlyPieces
|
||||
# Can't move over other pieces (captures are handled elsewhere)
|
||||
let moveset = getRookMoves(square, blockers) and not occupancy
|
||||
for target in moveset:
|
||||
moves.add(createMove(square, target))
|
||||
|
||||
|
@ -744,13 +765,12 @@ proc generateBishopMovements(self: ChessBoard, moves: var MoveList) =
|
|||
let
|
||||
sideToMove = self.getSideToMove()
|
||||
occupancy = self.getOccupancy()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
bishops = self.getBitboard(Bishop, sideToMove)
|
||||
for square in bishops:
|
||||
let blockers = occupancy and Bishop.getRelevantBlockers(square)
|
||||
var moveset = getBishopMoves(square, blockers)
|
||||
# Can't move over our own pieces
|
||||
moveset = moveset and not friendlyPieces
|
||||
# Can't move over other pieces (captures are handled elsewhere)
|
||||
moveset = moveset and not occupancy
|
||||
for target in moveset:
|
||||
moves.add(createMove(square, target))
|
||||
|
||||
|
@ -795,8 +815,8 @@ proc generateQueenMovements(self: ChessBoard, moves: var MoveList) =
|
|||
rookMoves = getRookMoves(square, rookBlockers)
|
||||
bishopMoves = getBishopMoves(square, bishopBlockers)
|
||||
var moveset = rookMoves or bishopMoves
|
||||
# Can't move over our own pieces
|
||||
moveset = moveset and not friendlyPieces
|
||||
# Can't move over other pieces (captures are handled elsewhere)
|
||||
moveset = moveset and not occupancy
|
||||
for target in moveset:
|
||||
moves.add(createMove(square, target))
|
||||
|
||||
|
@ -871,77 +891,17 @@ proc generateKnightMoves(self: ChessBoard, moves: var MoveList)=
|
|||
moves.add(createMove(square, target, Capture))
|
||||
|
||||
|
||||
proc checkInsufficientMaterialPieceCount(self: ChessBoard, color: PieceColor): bool =
|
||||
## Helper function for checkInsufficientMaterial
|
||||
let
|
||||
friendlyPawns = self.countPieces(Piece(kind: Pawn, color: color))
|
||||
friendlyRooks = self.countPieces(Piece(kind: Rook, color: color))
|
||||
friendlyQueens = self.countPieces(Piece(kind: Queen, color: color))
|
||||
friendlyKnights = self.countPieces(Piece(kind: Knight, color: color))
|
||||
friendlyBishops = self.countPieces(Piece(kind: Bishop, color: color))
|
||||
enemyPawns = self.countPieces(Piece(kind: Pawn, color: color.opposite()))
|
||||
enemyRooks = self.countPieces(Piece(kind: Rook, color: color.opposite()))
|
||||
enemyQueens = self.countPieces(Piece(kind: Queen, color: color.opposite()))
|
||||
enemyKnights = self.countPieces(Piece(kind: Knight, color: color.opposite()))
|
||||
enemyBishops = self.countPieces(Piece(kind: Bishop, color: color.opposite()))
|
||||
if friendlyPawns > 0 or friendlyRooks > 0 or friendlyQueens > 0:
|
||||
return false
|
||||
if friendlyKnights >= 2:
|
||||
return false
|
||||
if friendlyKnights + friendlyBishops >= 2:
|
||||
return false
|
||||
if friendlyKnights >= 1 and (enemyPawns > 0 or enemyRooks > 0 or enemyBishops > 0 or enemyKnights > 0 or enemyQueens > 0):
|
||||
return false
|
||||
if friendlyBishops >= 1 and (enemyKnights > 0 or enemyPawns > 0):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
proc checkInsufficientMaterial(self: ChessBoard): bool =
|
||||
## Checks if the given position has not enough material for either side to
|
||||
## checkmate the enemy king. Note that the criteria as implemented here are
|
||||
## not fully compliant with FIDE rules (they just define a draw by insufficient
|
||||
## material as "[...] the position is such that the opponent cannot checkmate
|
||||
## the player’s king by any possible series of legal moves.", which is really
|
||||
## tricky to implement efficiently). For more info see https://www.reddit.com/r/chess/comments/se89db/a_writeup_on_definitions_of_insufficient_material/
|
||||
if not (self.checkInsufficientMaterialPieceCount(White) and self.checkInsufficientMaterialPieceCount(Black)):
|
||||
return false
|
||||
let
|
||||
whiteBishops = self.countPieces(Piece(kind: Bishop, color: White))
|
||||
blackBishops = self.countPieces(Piece(kind: Bishop, color: Black))
|
||||
if blackBishops + whiteBishops >= 2:
|
||||
var
|
||||
darkSquare = 0
|
||||
lightSquare = 0
|
||||
for bishop in self.position.pieces.black.bishops:
|
||||
if bishop.isLightSquare():
|
||||
lightSquare += 1
|
||||
else:
|
||||
darkSquare += 1
|
||||
for bishop in self.position.pieces.white.bishops:
|
||||
if bishop.isLightSquare():
|
||||
lightSquare += 1
|
||||
else:
|
||||
darkSquare += 1
|
||||
if darkSquare >= 1 and lightSquare >= 1:
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
proc generateMoves*(self: ChessBoard, moves: var MoveList) =
|
||||
## Generates the list of all possible legal moves
|
||||
## in the current position
|
||||
moves.clear()
|
||||
if self.position.halfMoveClock >= 100:
|
||||
# Draw by 50-move rule
|
||||
return
|
||||
# TODO: Check for draw by insufficient material
|
||||
#[
|
||||
if self.checkInsufficientMaterial():
|
||||
return @[]
|
||||
]#
|
||||
# TODO: Check for repetitions (requires zobrist hashing + table)
|
||||
self.generatePawnMoves(moves)
|
||||
self.generateKingMoves(moves)
|
||||
self.generatePawnMoves(moves)
|
||||
self.generateKnightMoves(moves)
|
||||
self.generateSlidingMoves(moves)
|
||||
|
||||
|
@ -1031,7 +991,8 @@ proc removePiece(self: ChessBoard, square: Square) =
|
|||
## Removes a piece from the board, updating necessary
|
||||
## metadata
|
||||
var piece = self.grid[square]
|
||||
doAssert piece.kind != Empty and piece.color != None
|
||||
when not defined(danger):
|
||||
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
||||
self.removePieceFromBitboard(square)
|
||||
self.grid[square] = nullPiece()
|
||||
|
||||
|
@ -1042,9 +1003,10 @@ proc movePiece(self: ChessBoard, move: Move) =
|
|||
## not update attacked squares metadata, just
|
||||
## positional info and the grid itself
|
||||
let piece = self.grid[move.startSquare]
|
||||
let targetSquare = self.getPiece(move.targetSquare)
|
||||
if targetSquare.color != None:
|
||||
raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}")
|
||||
when not defined(danger):
|
||||
let targetSquare = self.getPiece(move.targetSquare)
|
||||
if targetSquare.color != None:
|
||||
raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}: {move}")
|
||||
# Update positional metadata
|
||||
self.removePiece(move.startSquare)
|
||||
self.spawnPiece(move.targetSquare, piece)
|
||||
|
@ -1055,7 +1017,7 @@ proc movePiece(self: ChessBoard, startSquare, targetSquare: Square) =
|
|||
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare))
|
||||
|
||||
|
||||
proc doMove(self: ChessBoard, move: Move) =
|
||||
proc doMove*(self: ChessBoard, move: Move) =
|
||||
## Internal function called by makeMove after
|
||||
## performing legality checks. Can be used in
|
||||
## performance-critical paths where a move is
|
||||
|
@ -1063,11 +1025,11 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
|
||||
# Record final position for future reference
|
||||
self.positions.add(self.position)
|
||||
inc(self.currPos)
|
||||
|
||||
# Final checks
|
||||
let piece = self.grid[move.startSquare]
|
||||
doAssert piece.kind != Empty and piece.color != None
|
||||
when not defined(danger):
|
||||
doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}"
|
||||
|
||||
var
|
||||
halfMoveClock = self.position.halfMoveClock
|
||||
|
@ -1089,7 +1051,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||
halfMoveClock: halfMoveClock,
|
||||
fullMoveCount: fullMoveCount,
|
||||
turn: self.getSideToMove().opposite,
|
||||
sideToMove: self.getSideToMove().opposite,
|
||||
castlingRights: castlingRights,
|
||||
enPassantSquare: enPassantTarget,
|
||||
pieces: self.position.pieces
|
||||
|
@ -1103,6 +1065,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
if move.isCapture():
|
||||
# Get rid of captured pieces
|
||||
self.removePiece(move.targetSquare)
|
||||
|
||||
# Move the piece to its target square
|
||||
self.movePiece(move)
|
||||
if move.isPromotion():
|
||||
|
@ -1121,19 +1084,19 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
self.updateBoard()
|
||||
self.updateCheckers()
|
||||
|
||||
|
||||
proc spawnPiece(self: ChessBoard, square: Square, piece: Piece) =
|
||||
## Internal helper to "spawn" a given piece at the given
|
||||
## square. Note that this will overwrite whatever piece
|
||||
## was previously located there: use with caution. Does
|
||||
## not automatically update the attacked square metadata
|
||||
## square
|
||||
when not defined(danger):
|
||||
doAssert self.grid[square].kind == Empty
|
||||
self.addPieceToBitboard(square, piece)
|
||||
self.grid[square] = piece
|
||||
|
||||
|
||||
proc updateBoard*(self: ChessBoard) =
|
||||
proc update*(self: ChessBoard) =
|
||||
## Updates the internal grid representation
|
||||
## according to the positional data stored
|
||||
## in the chessboard
|
||||
|
@ -1168,10 +1131,8 @@ proc updateBoard*(self: ChessBoard) =
|
|||
proc unmakeMove*(self: ChessBoard) =
|
||||
## Reverts to the previous board position,
|
||||
## if one exists
|
||||
if self.currPos >= 0:
|
||||
self.position = self.positions[self.currPos]
|
||||
dec(self.currPos)
|
||||
self.updateBoard()
|
||||
self.position = self.positions.pop()
|
||||
self.update()
|
||||
|
||||
|
||||
proc isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
||||
|
@ -1329,418 +1290,6 @@ proc toFEN*(self: ChessBoard): string =
|
|||
result &= $self.getMoveCount()
|
||||
|
||||
|
||||
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
|
||||
|
||||
var moves = MoveList()
|
||||
self.generateMoves(moves)
|
||||
if not bulk:
|
||||
if len(moves) == 0 and self.inCheck(self.getSideToMove()):
|
||||
result.checkmates = 1
|
||||
# TODO: Should we count stalemates/draws?
|
||||
if ply == 0:
|
||||
result.nodes = 1
|
||||
return
|
||||
elif ply == 1 and bulk:
|
||||
if divide:
|
||||
var postfix = ""
|
||||
for move in moves:
|
||||
case move.getPromotionType():
|
||||
of PromoteToBishop:
|
||||
postfix = "b"
|
||||
of PromoteToKnight:
|
||||
postfix = "n"
|
||||
of PromoteToRook:
|
||||
postfix = "r"
|
||||
of PromoteToQueen:
|
||||
postfix = "q"
|
||||
else:
|
||||
postfix = ""
|
||||
echo &"{move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}{postfix}: 1"
|
||||
if verbose:
|
||||
echo ""
|
||||
return (uint64(len(moves)), 0, 0, 0, 0, 0, 0)
|
||||
|
||||
for move in moves:
|
||||
if verbose:
|
||||
let canCastle = self.canCastle(self.getSideToMove())
|
||||
echo &"Ply (from root): {self.position.plyFromRoot}"
|
||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
||||
echo &"Turn: {self.getSideToMove()}"
|
||||
echo &"Piece: {self.grid[move.startSquare].kind}"
|
||||
echo &"Flags: {move.getFlags()}"
|
||||
echo &"In check: {(if self.inCheck(self.getSideToMove()): \"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()}"
|
||||
stdout.write("En Passant target: ")
|
||||
if self.getEnPassantTarget() != nullSquare():
|
||||
echo self.getEnPassantTarget().toAlgebraic()
|
||||
else:
|
||||
echo "None"
|
||||
echo "\n", self.pretty()
|
||||
self.doMove(move)
|
||||
if ply == 1:
|
||||
if move.isCapture():
|
||||
inc(result.captures)
|
||||
if move.isCastling():
|
||||
inc(result.castles)
|
||||
if move.isPromotion():
|
||||
inc(result.promotions)
|
||||
if move.isEnPassant():
|
||||
inc(result.enPassant)
|
||||
if self.inCheck(self.getSideToMove()):
|
||||
# Opponent king is in check
|
||||
inc(result.checks)
|
||||
if verbose:
|
||||
let canCastle = self.canCastle(self.getSideToMove())
|
||||
echo "\n"
|
||||
echo &"Opponent in check: {(if self.inCheck(self.getSideToMove()): \"yes\" else: \"no\")}"
|
||||
echo &"Opponent can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
echo &"Position after move: {self.toFEN()}"
|
||||
echo "\n", self.pretty()
|
||||
stdout.write("nextpos>> ")
|
||||
try:
|
||||
discard readLine(stdin)
|
||||
except IOError:
|
||||
discard
|
||||
except EOFError:
|
||||
discard
|
||||
let next = self.perft(ply - 1, verbose, bulk=bulk)
|
||||
self.unmakeMove()
|
||||
if divide and (not bulk or ply > 1):
|
||||
var postfix = ""
|
||||
if move.isPromotion():
|
||||
case move.getPromotionType():
|
||||
of PromoteToBishop:
|
||||
postfix = "b"
|
||||
of PromoteToKnight:
|
||||
postfix = "n"
|
||||
of PromoteToRook:
|
||||
postfix = "r"
|
||||
of PromoteToQueen:
|
||||
postfix = "q"
|
||||
else:
|
||||
discard
|
||||
echo &"{move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}{postfix}: {next.nodes}"
|
||||
if verbose:
|
||||
echo ""
|
||||
result.nodes += next.nodes
|
||||
result.captures += next.captures
|
||||
result.checks += next.checks
|
||||
result.promotions += next.promotions
|
||||
result.castles += next.castles
|
||||
result.enPassant += next.enPassant
|
||||
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":
|
||||
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:
|
||||
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:
|
||||
let t = cpuTime()
|
||||
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 ""
|
||||
echo &"Time taken: {round(cpuTime() - t, 3)} seconds"
|
||||
except ValueError:
|
||||
echo "Error: go: perft: invalid depth"
|
||||
else:
|
||||
echo &"Error: go: unknown subcommand '{command[1]}'"
|
||||
|
||||
|
||||
proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discardable.} =
|
||||
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: Square
|
||||
targetSquare: Square
|
||||
flags: seq[MoveFlag]
|
||||
|
||||
try:
|
||||
startSquare = moveString[0..1].toSquare()
|
||||
except ValueError:
|
||||
echo &"Error: move: invalid start square ({moveString[0..1]})"
|
||||
return
|
||||
try:
|
||||
targetSquare = moveString[2..3].toSquare()
|
||||
except ValueError:
|
||||
echo &"Error: move: invalid target square ({moveString[2..3]})"
|
||||
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, etc.)
|
||||
|
||||
if board.grid[targetSquare].kind != Empty:
|
||||
flags.add(Capture)
|
||||
|
||||
elif board.grid[startSquare].kind == Pawn and abs(rankFromSquare(startSquare) - rankFromSquare(targetSquare)) == 2:
|
||||
flags.add(DoublePush)
|
||||
|
||||
if len(moveString) == 5:
|
||||
# Promotion
|
||||
case moveString[4]:
|
||||
of 'b':
|
||||
flags.add(PromoteToBishop)
|
||||
of 'n':
|
||||
flags.add(PromoteToKnight)
|
||||
of 'q':
|
||||
flags.add(PromoteToQueen)
|
||||
of 'r':
|
||||
flags.add(PromoteToRook)
|
||||
else:
|
||||
echo &"Error: move: invalid promotion type"
|
||||
return
|
||||
|
||||
|
||||
var move = createMove(startSquare, targetSquare, flags)
|
||||
let piece = board.getPiece(move.startSquare)
|
||||
if piece.kind == King and move.startSquare == board.getSideToMove().getKingStartingSquare():
|
||||
if move.targetSquare == longCastleKing(piece.color):
|
||||
move.flags = move.flags or CastleLong.uint16
|
||||
elif move.targetSquare == shortCastleKing(piece.color):
|
||||
move.flags = move.flags or CastleShort.uint16
|
||||
if move.targetSquare == board.getEnPassantTarget():
|
||||
move.flags = move.flags or EnPassant.uint16
|
||||
result = board.makeMove(move)
|
||||
if result == nullMove():
|
||||
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: ChessBoard
|
||||
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]]) == nullMove():
|
||||
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]]) == nullMove():
|
||||
return
|
||||
inc(j)
|
||||
inc(i)
|
||||
board = tempBoard
|
||||
of "print":
|
||||
echo board
|
||||
of "pretty":
|
||||
echo board.pretty()
|
||||
else:
|
||||
echo &"error: position: unknown subcommand '{command[1]}'"
|
||||
return
|
||||
|
||||
|
||||
proc handleUCICommand(board: var ChessBoard, command: seq[string]) =
|
||||
echo "id name Nimfish 0.1"
|
||||
echo "id author Nocturn9x & Contributors (see LICENSE)"
|
||||
# TODO
|
||||
echo "uciok"
|
||||
|
||||
|
||||
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 castlingRights rights for each side
|
||||
- check: Print if the current side to move is in check
|
||||
- undo, u: Undoes the last move. 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"
|
||||
- fen: Shorthand for "position fen"
|
||||
- pos <args>: Shorthand for "position <args>"
|
||||
- get <square>: Get the piece on the given square
|
||||
- atk: Print the attack bitboard of the given square for the side to move
|
||||
- uci: enter UCI mode (WIP)
|
||||
"""
|
||||
|
||||
|
||||
proc main: int =
|
||||
## Nimfish's control interface
|
||||
echo "Nimfish by nocturn9x (see LICENSE)"
|
||||
var
|
||||
board = newDefaultChessboard()
|
||||
uciMode = false
|
||||
while true:
|
||||
var
|
||||
cmd: seq[string]
|
||||
cmdStr: string
|
||||
try:
|
||||
if not uciMode:
|
||||
stdout.write(">>> ")
|
||||
stdout.flushFile()
|
||||
cmdStr = readLine(stdin).strip(leading=true, trailing=true, chars={'\t', ' '})
|
||||
if cmdStr.len() == 0:
|
||||
continue
|
||||
cmd = cmdStr.splitWhitespace(maxsplit=2)
|
||||
|
||||
case cmd[0]:
|
||||
of "uci":
|
||||
handleUCICommand(board, cmd)
|
||||
uciMode = true
|
||||
of "clear":
|
||||
echo "\x1Bc"
|
||||
of "help":
|
||||
echo HELP_TEXT
|
||||
of "go":
|
||||
handleGoCommand(board, cmd)
|
||||
of "position", "pos":
|
||||
handlePositionCommand(board, cmd)
|
||||
of "move":
|
||||
handleMoveCommand(board, cmd)
|
||||
of "pretty", "print", "fen":
|
||||
handlePositionCommand(board, @["position", cmd[0]])
|
||||
of "undo", "u":
|
||||
board.unmakeMove()
|
||||
of "turn":
|
||||
echo &"Active color: {board.getSideToMove()}"
|
||||
of "atk":
|
||||
if len(cmd) != 2:
|
||||
echo "error: atk: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.getAttacksTo(cmd[1].toSquare(), board.getSideToMove())
|
||||
except ValueError:
|
||||
echo "error: atk: invalid square"
|
||||
continue
|
||||
of "ep":
|
||||
let target = board.getEnPassantTarget()
|
||||
if target != nullSquare():
|
||||
echo &"En passant target: {target.toAlgebraic()}"
|
||||
else:
|
||||
echo "En passant target: None"
|
||||
of "get":
|
||||
if len(cmd) != 2:
|
||||
echo "error: get: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.getPiece(cmd[1])
|
||||
except ValueError:
|
||||
echo "error: get: invalid square"
|
||||
continue
|
||||
of "castle":
|
||||
let canCastle = board.canCastle(board.getSideToMove())
|
||||
echo &"Castling rights for {($board.getSideToMove()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
of "check":
|
||||
echo &"{board.getSideToMove()} king in check: {(if board.inCheck(board.getSideToMove()): \"yes\" else: \"no\")}"
|
||||
else:
|
||||
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
||||
except IOError:
|
||||
echo ""
|
||||
return 0
|
||||
except EOFError:
|
||||
echo ""
|
||||
return 0
|
||||
|
||||
|
||||
when isMainModule:
|
||||
|
||||
|
||||
|
@ -1846,5 +1395,9 @@ when isMainModule:
|
|||
testPieceBitboard(blackQueens, blackQueenSquares)
|
||||
testPieceBitboard(blackKing, blackKingSquares)
|
||||
|
||||
|
||||
setControlCHook(proc () {.noconv.} = quit(0))
|
||||
quit(main())
|
||||
|
||||
import tui
|
||||
|
||||
quit(tui.commandLoop())
|
||||
|
|
|
@ -0,0 +1,427 @@
|
|||
import nimfish
|
||||
|
||||
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import std/times
|
||||
import std/math
|
||||
|
||||
|
||||
type
|
||||
CountData = tuple[nodes: uint64, captures: uint64, castles: uint64, checks: uint64, promotions: uint64, enPassant: uint64, checkmates: uint64]
|
||||
|
||||
|
||||
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
|
||||
|
||||
var moves = MoveList()
|
||||
self.generateMoves(moves)
|
||||
if not bulk:
|
||||
if len(moves) == 0 and self.inCheck(self.getSideToMove()):
|
||||
result.checkmates = 1
|
||||
# TODO: Should we count stalemates/draws?
|
||||
if ply == 0:
|
||||
result.nodes = 1
|
||||
return
|
||||
elif ply == 1 and bulk:
|
||||
if divide:
|
||||
var postfix = ""
|
||||
for move in moves:
|
||||
case move.getPromotionType():
|
||||
of PromoteToBishop:
|
||||
postfix = "b"
|
||||
of PromoteToKnight:
|
||||
postfix = "n"
|
||||
of PromoteToRook:
|
||||
postfix = "r"
|
||||
of PromoteToQueen:
|
||||
postfix = "q"
|
||||
else:
|
||||
postfix = ""
|
||||
echo &"{move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}{postfix}: 1"
|
||||
if verbose:
|
||||
echo ""
|
||||
return (uint64(len(moves)), 0, 0, 0, 0, 0, 0)
|
||||
|
||||
for move in moves:
|
||||
if verbose:
|
||||
let canCastle = self.canCastle(self.getSideToMove())
|
||||
echo &"Ply (from root): {self.getPlyFromRoot()}"
|
||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
||||
echo &"Turn: {self.getSideToMove()}"
|
||||
echo &"Piece: {self.getPiece(move.startSquare).kind}"
|
||||
echo &"Flags: {move.getFlags()}"
|
||||
echo &"In check: {(if self.inCheck(self.getSideToMove()): \"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()}"
|
||||
stdout.write("En Passant target: ")
|
||||
if self.getEnPassantTarget() != nullSquare():
|
||||
echo self.getEnPassantTarget().toAlgebraic()
|
||||
else:
|
||||
echo "None"
|
||||
echo "\n", self.pretty()
|
||||
self.doMove(move)
|
||||
if ply == 1:
|
||||
if move.isCapture():
|
||||
inc(result.captures)
|
||||
if move.isCastling():
|
||||
inc(result.castles)
|
||||
if move.isPromotion():
|
||||
inc(result.promotions)
|
||||
if move.isEnPassant():
|
||||
inc(result.enPassant)
|
||||
if self.inCheck(self.getSideToMove()):
|
||||
# Opponent king is in check
|
||||
inc(result.checks)
|
||||
if verbose:
|
||||
let canCastle = self.canCastle(self.getSideToMove())
|
||||
echo "\n"
|
||||
echo &"Opponent in check: {(if self.inCheck(self.getSideToMove()): \"yes\" else: \"no\")}"
|
||||
echo &"Opponent can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
echo &"Position after move: {self.toFEN()}"
|
||||
echo "\n", self.pretty()
|
||||
stdout.write("nextpos>> ")
|
||||
try:
|
||||
discard readLine(stdin)
|
||||
except IOError:
|
||||
discard
|
||||
except EOFError:
|
||||
discard
|
||||
let next = self.perft(ply - 1, verbose, bulk=bulk)
|
||||
self.unmakeMove()
|
||||
if divide and (not bulk or ply > 1):
|
||||
var postfix = ""
|
||||
if move.isPromotion():
|
||||
case move.getPromotionType():
|
||||
of PromoteToBishop:
|
||||
postfix = "b"
|
||||
of PromoteToKnight:
|
||||
postfix = "n"
|
||||
of PromoteToRook:
|
||||
postfix = "r"
|
||||
of PromoteToQueen:
|
||||
postfix = "q"
|
||||
else:
|
||||
discard
|
||||
echo &"{move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}{postfix}: {next.nodes}"
|
||||
if verbose:
|
||||
echo ""
|
||||
result.nodes += next.nodes
|
||||
result.captures += next.captures
|
||||
result.checks += next.checks
|
||||
result.promotions += next.promotions
|
||||
result.castles += next.castles
|
||||
result.enPassant += next.enPassant
|
||||
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":
|
||||
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:
|
||||
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:
|
||||
let t = cpuTime()
|
||||
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 ""
|
||||
echo &"Time taken: {round(cpuTime() - t, 3)} seconds"
|
||||
except ValueError:
|
||||
echo "Error: go: perft: invalid depth"
|
||||
else:
|
||||
echo &"Error: go: unknown subcommand '{command[1]}'"
|
||||
|
||||
|
||||
proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discardable.} =
|
||||
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: Square
|
||||
targetSquare: Square
|
||||
flags: seq[MoveFlag]
|
||||
|
||||
try:
|
||||
startSquare = moveString[0..1].toSquare()
|
||||
except ValueError:
|
||||
echo &"Error: move: invalid start square ({moveString[0..1]})"
|
||||
return
|
||||
try:
|
||||
targetSquare = moveString[2..3].toSquare()
|
||||
except ValueError:
|
||||
echo &"Error: move: invalid target square ({moveString[2..3]})"
|
||||
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, etc.)
|
||||
|
||||
if board.getPiece(targetSquare).kind != Empty:
|
||||
flags.add(Capture)
|
||||
|
||||
elif board.getPiece(targetSquare).kind == Pawn and abs(rankFromSquare(startSquare) - rankFromSquare(targetSquare)) == 2:
|
||||
flags.add(DoublePush)
|
||||
|
||||
if len(moveString) == 5:
|
||||
# Promotion
|
||||
case moveString[4]:
|
||||
of 'b':
|
||||
flags.add(PromoteToBishop)
|
||||
of 'n':
|
||||
flags.add(PromoteToKnight)
|
||||
of 'q':
|
||||
flags.add(PromoteToQueen)
|
||||
of 'r':
|
||||
flags.add(PromoteToRook)
|
||||
else:
|
||||
echo &"Error: move: invalid promotion type"
|
||||
return
|
||||
|
||||
|
||||
var move = createMove(startSquare, targetSquare, flags)
|
||||
let piece = board.getPiece(move.startSquare)
|
||||
if piece.kind == King and move.startSquare == board.getSideToMove().getKingStartingSquare():
|
||||
if move.targetSquare == longCastleKing(piece.color):
|
||||
move.flags = move.flags or CastleLong.uint16
|
||||
elif move.targetSquare == shortCastleKing(piece.color):
|
||||
move.flags = move.flags or CastleShort.uint16
|
||||
if move.targetSquare == board.getEnPassantTarget():
|
||||
move.flags = move.flags or EnPassant.uint16
|
||||
result = board.makeMove(move)
|
||||
if result == nullMove():
|
||||
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: ChessBoard
|
||||
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]]) == nullMove():
|
||||
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]]) == nullMove():
|
||||
return
|
||||
inc(j)
|
||||
inc(i)
|
||||
board = tempBoard
|
||||
of "print":
|
||||
echo board
|
||||
of "pretty":
|
||||
echo board.pretty()
|
||||
else:
|
||||
echo &"error: position: unknown subcommand '{command[1]}'"
|
||||
return
|
||||
|
||||
|
||||
proc handleUCICommand(board: var ChessBoard, command: seq[string]) =
|
||||
echo "id name Nimfish 0.1"
|
||||
echo "id author Nocturn9x & Contributors (see LICENSE)"
|
||||
# TODO
|
||||
echo "uciok"
|
||||
|
||||
|
||||
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 castlingRights rights for each side
|
||||
- check: Print if the current side to move is in check
|
||||
- unmove, u: Unmakes the last move. Can be used in succession
|
||||
- stm: Print which side is to move
|
||||
- ep: Print the current en passant target
|
||||
- pretty: Shorthand for "position pretty"
|
||||
- print: Shorthand for "position print"
|
||||
- fen: Shorthand for "position fen"
|
||||
- pos <args>: Shorthand for "position <args>"
|
||||
- get <square>: Get the piece on the given square
|
||||
- atk <square>: Print the attack bitboard of the given square for the side to move
|
||||
- skip: Swap the side to move
|
||||
- uci: enter UCI mode (WIP)
|
||||
- quit: exit
|
||||
"""
|
||||
|
||||
|
||||
proc commandLoop*: int =
|
||||
## Nimfish's control interface
|
||||
echo "Nimfish by nocturn9x (see LICENSE)"
|
||||
var
|
||||
board = newDefaultChessboard()
|
||||
uciMode = false
|
||||
while true:
|
||||
var
|
||||
cmd: seq[string]
|
||||
cmdStr: string
|
||||
try:
|
||||
if not uciMode:
|
||||
stdout.write(">>> ")
|
||||
stdout.flushFile()
|
||||
cmdStr = readLine(stdin).strip(leading=true, trailing=true, chars={'\t', ' '})
|
||||
if cmdStr.len() == 0:
|
||||
continue
|
||||
cmd = cmdStr.splitWhitespace(maxsplit=2)
|
||||
|
||||
case cmd[0]:
|
||||
of "uci":
|
||||
handleUCICommand(board, cmd)
|
||||
uciMode = true
|
||||
of "clear":
|
||||
echo "\x1Bc"
|
||||
of "help":
|
||||
echo HELP_TEXT
|
||||
of "skip":
|
||||
board.setSideToMove(board.getSideToMove().opposite())
|
||||
of "go":
|
||||
handleGoCommand(board, cmd)
|
||||
of "position", "pos":
|
||||
handlePositionCommand(board, cmd)
|
||||
of "move":
|
||||
handleMoveCommand(board, cmd)
|
||||
of "pretty", "print", "fen":
|
||||
handlePositionCommand(board, @["position", cmd[0]])
|
||||
of "unmove", "u":
|
||||
board.unmakeMove()
|
||||
of "stm":
|
||||
echo &"Side to move: {board.getSideToMove()}"
|
||||
of "atk":
|
||||
if len(cmd) != 2:
|
||||
echo "error: atk: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.getAttacksTo(cmd[1].toSquare(), board.getSideToMove())
|
||||
except ValueError:
|
||||
echo "error: atk: invalid square"
|
||||
continue
|
||||
of "ep":
|
||||
let target = board.getEnPassantTarget()
|
||||
if target != nullSquare():
|
||||
echo &"En passant target: {target.toAlgebraic()}"
|
||||
else:
|
||||
echo "En passant target: None"
|
||||
of "get":
|
||||
if len(cmd) != 2:
|
||||
echo "error: get: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.getPiece(cmd[1])
|
||||
except ValueError:
|
||||
echo "error: get: invalid square"
|
||||
continue
|
||||
of "castle":
|
||||
let canCastle = board.canCastle(board.getSideToMove())
|
||||
echo &"Castling rights for {($board.getSideToMove()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
of "check":
|
||||
echo &"{board.getSideToMove()} king in check: {(if board.inCheck(board.getSideToMove()): \"yes\" else: \"no\")}"
|
||||
else:
|
||||
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
||||
except IOError:
|
||||
echo ""
|
||||
return 0
|
||||
except EOFError:
|
||||
echo ""
|
||||
return 0
|
Loading…
Reference in New Issue