Switch to static move list and print nps after perft completion
This commit is contained in:
parent
aeaa57aba6
commit
48e2adddc6
|
@ -16,6 +16,7 @@ import std/strutils
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import std/times
|
import std/times
|
||||||
import std/math
|
import std/math
|
||||||
|
from std/lenientops import `/` # Only needed for perft
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -73,6 +74,11 @@ type
|
||||||
targetSquare*: Location
|
targetSquare*: Location
|
||||||
flags*: uint16
|
flags*: uint16
|
||||||
|
|
||||||
|
MoveList* = object
|
||||||
|
## A list of moves
|
||||||
|
data: array[218, Move]
|
||||||
|
len: int8
|
||||||
|
|
||||||
|
|
||||||
Position* = ref object
|
Position* = ref object
|
||||||
## A chess position
|
## A chess position
|
||||||
|
@ -112,10 +118,31 @@ type
|
||||||
positions: seq[Position]
|
positions: seq[Position]
|
||||||
|
|
||||||
|
|
||||||
|
iterator items(self: MoveList): Move =
|
||||||
|
var i = 0
|
||||||
|
while self.len > i:
|
||||||
|
yield self.data[i]
|
||||||
|
inc(i)
|
||||||
|
|
||||||
|
|
||||||
|
func add(self: var MoveList, move: Move) {.inline.} =
|
||||||
|
self.data[self.len] = move
|
||||||
|
inc(self.len)
|
||||||
|
|
||||||
|
func contains(self: MoveList, move: Move): bool {.inline.} =
|
||||||
|
for item in self:
|
||||||
|
if move == item:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func len(self: MoveList): int {.inline.} = self.len
|
||||||
|
|
||||||
|
|
||||||
# A bunch of simple utility functions and forward declarations
|
# A bunch of simple utility functions and forward declarations
|
||||||
|
|
||||||
func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
|
func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
|
||||||
func emptyLocation*: Location {.inline.} = (-1 , -1)
|
func emptyLocation*: Location {.inline.} = (-1, -1)
|
||||||
func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White)
|
func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White)
|
||||||
proc algebraicToLocation*(s: string): Location {.inline.}
|
proc algebraicToLocation*(s: string): Location {.inline.}
|
||||||
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.}
|
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.}
|
||||||
|
@ -124,7 +151,7 @@ func `+`*(a, b: Location): Location = (a.row + b.row, a.col + b.col)
|
||||||
func `-`*(a: Location): Location = (-a.row, -a.col)
|
func `-`*(a: Location): Location = (-a.row, -a.col)
|
||||||
func `-`*(a, b: Location): Location = (a.row - b.row, a.col - b.col)
|
func `-`*(a, b: Location): Location = (a.row - b.row, a.col - b.col)
|
||||||
func isValid*(a: Location): bool {.inline.} = a.row in 0..7 and a.col in 0..7
|
func isValid*(a: Location): bool {.inline.} = a.row in 0..7 and a.col in 0..7
|
||||||
proc generateMoves(self: ChessBoard, location: Location): seq[Move]
|
proc generateMoves(self: ChessBoard, location: Location, moves: var MoveList)
|
||||||
proc getAttackers*(self: ChessBoard, loc: Location, color: PieceColor): seq[Location]
|
proc getAttackers*(self: ChessBoard, loc: Location, color: PieceColor): seq[Location]
|
||||||
proc getAttackFor*(self: ChessBoard, source, target: Location): tuple[source, target, direction: Location]
|
proc getAttackFor*(self: ChessBoard, source, target: Location): tuple[source, target, direction: Location]
|
||||||
proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): bool
|
proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): bool
|
||||||
|
@ -829,7 +856,7 @@ proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
||||||
result.add(location)
|
result.add(location)
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generatePawnMoves(self: ChessBoard, location: Location, moveList: var MoveList) =
|
||||||
## Generates the possible moves for the pawn in the given
|
## Generates the possible moves for the pawn in the given
|
||||||
## location
|
## location
|
||||||
var
|
var
|
||||||
|
@ -911,12 +938,12 @@ 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, flags: promotionType.uint16 or flags))
|
moveList.add(Move(startSquare: location, targetSquare: target, flags: promotionType.uint16 or flags))
|
||||||
continue
|
continue
|
||||||
result.add(Move(startSquare: location, targetSquare: target, flags: flags))
|
moveList.add(Move(startSquare: location, targetSquare: target, flags: flags))
|
||||||
|
|
||||||
|
|
||||||
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generateSlidingMoves(self: ChessBoard, location: Location, moves: var MoveList) =
|
||||||
## Generates moves for the sliding piece in the given location
|
## Generates moves for the sliding piece in the given location
|
||||||
let piece = self.grid[location.row, location.col]
|
let piece = self.grid[location.row, location.col]
|
||||||
assert piece.kind in [Bishop, Rook, Queen], &"generateSlidingMoves called on a {piece.kind}"
|
assert piece.kind in [Bishop, Rook, Queen], &"generateSlidingMoves called on a {piece.kind}"
|
||||||
|
@ -972,13 +999,13 @@ 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, flags: Capture.uint16))
|
moves.add(Move(startSquare: location, targetSquare: square, flags: Capture.uint16))
|
||||||
break
|
break
|
||||||
# Target square is empty, keep going
|
# Target square is empty, keep going
|
||||||
result.add(Move(startSquare: location, targetSquare: square))
|
moves.add(Move(startSquare: location, targetSquare: square))
|
||||||
|
|
||||||
|
|
||||||
proc generateKingMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generateKingMoves(self: ChessBoard, location: Location, moves: var MoveList) =
|
||||||
## Generates moves for the king in the given location
|
## Generates moves for the king in the given location
|
||||||
var
|
var
|
||||||
piece = self.grid[location.row, location.col]
|
piece = self.grid[location.row, location.col]
|
||||||
|
@ -1020,10 +1047,10 @@ 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, flags: flag.uint16))
|
moves.add(Move(startSquare: location, targetSquare: square, flags: flag.uint16))
|
||||||
|
|
||||||
|
|
||||||
proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generateKnightMoves(self: ChessBoard, location: Location, moves: var MoveList) =
|
||||||
## Generates moves for the knight in the given location
|
## Generates moves for the knight in the given location
|
||||||
var
|
var
|
||||||
piece = self.grid[location.row, location.col]
|
piece = self.grid[location.row, location.col]
|
||||||
|
@ -1039,7 +1066,7 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
let pinned = self.getPinnedDirections(location)
|
let pinned = self.getPinnedDirections(location)
|
||||||
if pinned.len() > 0:
|
if pinned.len() > 0:
|
||||||
# Knight is pinned: can't move!
|
# Knight is pinned: can't move!
|
||||||
return @[]
|
return
|
||||||
let checked = self.inCheck()
|
let checked = self.inCheck()
|
||||||
let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color)
|
let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color)
|
||||||
for direction in directions:
|
for direction in directions:
|
||||||
|
@ -1057,37 +1084,38 @@ 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, flags: Capture.uint16))
|
moves.add(Move(startSquare: location, targetSquare: square, flags: Capture.uint16))
|
||||||
else:
|
else:
|
||||||
# Target square is empty
|
# Target square is empty
|
||||||
result.add(Move(startSquare: location, targetSquare: square))
|
moves.add(Move(startSquare: location, targetSquare: square))
|
||||||
|
|
||||||
|
|
||||||
proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generateMoves(self: ChessBoard, location: Location, moves: var MoveList) =
|
||||||
## Returns the list of possible legal chess moves for the
|
## Returns the list of possible legal chess moves for the
|
||||||
## piece in the given location
|
## piece in the given location
|
||||||
let piece = self.grid[location.row, location.col]
|
let piece = self.grid[location.row, location.col]
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
of Queen, Bishop, Rook:
|
of Queen, Bishop, Rook:
|
||||||
return self.generateSlidingMoves(location)
|
self.generateSlidingMoves(location, moves)
|
||||||
of Pawn:
|
of Pawn:
|
||||||
return self.generatePawnMoves(location)
|
self.generatePawnMoves(location, moves)
|
||||||
of King:
|
of King:
|
||||||
return self.generateKingMoves(location)
|
self.generateKingMoves(location, moves)
|
||||||
of Knight:
|
of Knight:
|
||||||
return self.generateKnightMoves(location)
|
self.generateKnightMoves(location, moves)
|
||||||
else:
|
else:
|
||||||
return @[]
|
discard
|
||||||
|
|
||||||
|
|
||||||
proc generateAllMoves*(self: ChessBoard): seq[Move] =
|
proc generateAllMoves*(self: ChessBoard): MoveList =
|
||||||
## Returns the list of all possible legal moves
|
## Returns the list of all possible legal moves
|
||||||
## in the current position
|
## in the current position
|
||||||
|
var data: array[218, Move]
|
||||||
|
result = MoveList(len: 0, data: data)
|
||||||
for i in 0..7:
|
for i in 0..7:
|
||||||
for j in 0..7:
|
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))):
|
self.generateMoves((int8(i), int8(j)), result)
|
||||||
result.add(move)
|
|
||||||
|
|
||||||
|
|
||||||
proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): bool =
|
proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): bool =
|
||||||
|
@ -1716,7 +1744,9 @@ 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
|
||||||
return move in self.generateMoves(move.startSquare)
|
var moves = MoveList()
|
||||||
|
self.generateMoves(move.startSquare, moves)
|
||||||
|
return move in moves
|
||||||
|
|
||||||
|
|
||||||
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||||
|
@ -1999,11 +2029,13 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
||||||
if bulk:
|
if bulk:
|
||||||
let t = cpuTime()
|
let t = cpuTime()
|
||||||
let nodes = board.perft(ply, divide=true, bulk=true, verbose=verbose).nodes
|
let nodes = board.perft(ply, divide=true, bulk=true, verbose=verbose).nodes
|
||||||
|
let tot = cpuTime() - t
|
||||||
echo &"\nNodes searched (bulk-counting: on): {nodes}"
|
echo &"\nNodes searched (bulk-counting: on): {nodes}"
|
||||||
echo &"Time taken: {round(cpuTime() - t, 3)} seconds\n"
|
echo &"Time taken: {round(tot, 3)} seconds\nNodes per second: {round(nodes / tot).uint64}"
|
||||||
else:
|
else:
|
||||||
let t = cpuTime()
|
let t = cpuTime()
|
||||||
let data = board.perft(ply, divide=true, verbose=verbose)
|
let data = board.perft(ply, divide=true, verbose=verbose)
|
||||||
|
let tot = cpuTime() - t
|
||||||
echo &"\nNodes searched (bulk-counting: off): {data.nodes}"
|
echo &"\nNodes searched (bulk-counting: off): {data.nodes}"
|
||||||
echo &" - Captures: {data.captures}"
|
echo &" - Captures: {data.captures}"
|
||||||
echo &" - Checks: {data.checks}"
|
echo &" - Checks: {data.checks}"
|
||||||
|
@ -2012,7 +2044,7 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
||||||
echo &" - Castles: {data.castles}"
|
echo &" - Castles: {data.castles}"
|
||||||
echo &" - Promotions: {data.promotions}"
|
echo &" - Promotions: {data.promotions}"
|
||||||
echo ""
|
echo ""
|
||||||
echo &"Time taken: {round(cpuTime() - t, 3)} seconds"
|
echo &"Time taken: {tot} seconds\nNodes per second: {round(data.nodes / tot).uint64}"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo "Error: go: perft: invalid depth"
|
echo "Error: go: perft: invalid depth"
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -26,7 +26,7 @@ def main(args: Namespace) -> int:
|
||||||
stop = timeit.default_timer()
|
stop = timeit.default_timer()
|
||||||
print(f"\r[S] Ran {len(positions)} tests at depth {args.ply} in {stop - start:.2f} seconds ({len(successful)} successful, {len(failed)} failed)\033[K")
|
print(f"\r[S] Ran {len(positions)} tests at depth {args.ply} in {stop - start:.2f} seconds ({len(successful)} successful, {len(failed)} failed)\033[K")
|
||||||
if failed and args.show_failures:
|
if failed and args.show_failures:
|
||||||
print("[S] The following FENs failed to pass the test:", end="")
|
print("[S] The following FENs failed to pass the test:", end="\n\t")
|
||||||
print("\n\t".join(failed))
|
print("\n\t".join(failed))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue