Fixes to performance & improve legality checks
This commit is contained in:
parent
c6cc98a296
commit
b9dcde1563
|
@ -23,6 +23,8 @@ import std/sequtils
|
||||||
type
|
type
|
||||||
# Useful type aliases
|
# Useful type aliases
|
||||||
Location* = tuple[row, col: int8]
|
Location* = tuple[row, col: int8]
|
||||||
|
|
||||||
|
Attacked = seq[tuple[source, dest: Location]]
|
||||||
|
|
||||||
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
|
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
|
||||||
bishops: seq[Location], knights: seq[Location],
|
bishops: seq[Location], knights: seq[Location],
|
||||||
|
@ -52,9 +54,8 @@ type
|
||||||
MoveFlag* = enum
|
MoveFlag* = enum
|
||||||
## An enumeration of move flags
|
## An enumeration of move flags
|
||||||
Default = 0'i8, # No flag
|
Default = 0'i8, # No flag
|
||||||
|
EnPassant, # Move is a capture with en passant
|
||||||
Capture, # Move is a capture
|
Capture, # Move is a capture
|
||||||
EnPassant, # Move is an en passant
|
|
||||||
Backwards, # Move is a backward move generated by undoMove
|
|
||||||
DoublePush, # Move is a double pawn push
|
DoublePush, # Move is a double pawn push
|
||||||
# Castling metadata
|
# Castling metadata
|
||||||
CastleLong,
|
CastleLong,
|
||||||
|
@ -96,7 +97,7 @@ type
|
||||||
# Locations of all pieces
|
# Locations of all pieces
|
||||||
pieces: tuple[white: Pieces, black: Pieces]
|
pieces: tuple[white: Pieces, black: Pieces]
|
||||||
# Potential attacking moves for black and white
|
# Potential attacking moves for black and white
|
||||||
attacked: tuple[white: seq[Move], black: seq[Move]]
|
attacked: tuple[white: Attacked, black: Attacked]
|
||||||
# Has any piece been captured to reach this position?
|
# Has any piece been captured to reach this position?
|
||||||
captured: Piece
|
captured: Piece
|
||||||
# Active color
|
# Active color
|
||||||
|
@ -122,20 +123,21 @@ 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.}
|
||||||
func getCapture*(self: ChessBoard, move: Move): Location
|
func getCapture*(self: ChessBoard, move: Move): Location
|
||||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move
|
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.discardable.}
|
||||||
proc makeMove*(self: ChessBoard, move: Move): Move
|
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.}
|
||||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: Location): Move
|
proc makeMove*(self: ChessBoard, startSquare, targetSquare: Location): Move {.discardable.}
|
||||||
func emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
|
func emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
|
||||||
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): seq[Move]
|
||||||
proc isAttacked*(self: ChessBoard, loc: Location): bool
|
proc isAttacked*(self: ChessBoard, loc: Location): bool
|
||||||
proc undoMove*(self: ChessBoard, move: Move): Move {.discardable.}
|
proc undoMove*(self: ChessBoard, move: Move)
|
||||||
proc isLegal(self: ChessBoard, move: Move, keep: bool = false): bool
|
proc isLegal(self: ChessBoard, move: Move, keep: bool = false): bool
|
||||||
proc doMove(self: ChessBoard, move: Move)
|
proc doMove(self: ChessBoard, move: Move)
|
||||||
proc isLegalFast(self: ChessBoard, move: Move, keep: bool = false): bool
|
proc isLegalFast(self: ChessBoard, move: Move, keep: bool = false): bool
|
||||||
proc makeMoveFast*(self: ChessBoard, move: Move): Move {.discardable.}
|
|
||||||
proc pretty*(self: ChessBoard): string
|
proc pretty*(self: ChessBoard): string
|
||||||
|
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece)
|
||||||
|
proc updateAttackedSquares(self: ChessBoard)
|
||||||
|
|
||||||
# Due to our board layout, directions of movement are reversed for white/black so
|
# Due to our board layout, directions of movement are reversed for white/black so
|
||||||
# we need these helpers to avoid going mad with integer tuples and minus signs
|
# we need these helpers to avoid going mad with integer tuples and minus signs
|
||||||
|
@ -146,7 +148,7 @@ func bottomLeftDiagonal(color: PieceColor): Location {.inline.} = (if color == W
|
||||||
func bottomRightDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (1, 1) else: (-1, -1))
|
func bottomRightDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (1, 1) else: (-1, -1))
|
||||||
func leftSide(color: PieceColor): Location {.inline.} = (if color == White: (0, -1) else: (0, 1))
|
func leftSide(color: PieceColor): Location {.inline.} = (if color == White: (0, -1) else: (0, 1))
|
||||||
func rightSide(color: PieceColor): Location {.inline.} = (if color == White: (0, 1) else: (0, -1))
|
func rightSide(color: PieceColor): Location {.inline.} = (if color == White: (0, 1) else: (0, -1))
|
||||||
func topSide(color: PieceColor): Location {.inline.} = (if color == White: (-1, 0) else: (1, 0))
|
func topSide(color: PieceColor): Location {.inline.} = (-1, 0)
|
||||||
func bottomSide(color: PieceColor): Location {.inline.} = (if color == White: (1, 0) else: (-1, 0))
|
func bottomSide(color: PieceColor): Location {.inline.} = (if color == White: (1, 0) else: (-1, 0))
|
||||||
func forward(color: PieceColor): Location {.inline.} = (if color == White: (-1, 0) else: (1, 0))
|
func forward(color: PieceColor): Location {.inline.} = (if color == White: (-1, 0) else: (1, 0))
|
||||||
func doublePush(color: PieceColor): Location {.inline.} = (if color == White: (-2, 0) else: (2, 0))
|
func doublePush(color: PieceColor): Location {.inline.} = (if color == White: (-2, 0) else: (2, 0))
|
||||||
|
@ -166,9 +168,9 @@ func bottomLeftKnightMove(color: PieceColor, long: bool = true): Location {.inli
|
||||||
return (1, -2)
|
return (1, -2)
|
||||||
elif color == Black:
|
elif color == Black:
|
||||||
if long:
|
if long:
|
||||||
return (2, 1)
|
return (-2, 1)
|
||||||
else:
|
else:
|
||||||
return (2, -1)
|
return (1, -2)
|
||||||
|
|
||||||
|
|
||||||
func bottomRightKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
|
func bottomRightKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
|
||||||
|
@ -207,7 +209,7 @@ func topRightKnightMove(color: PieceColor, long: bool = true): Location {.inline
|
||||||
if long:
|
if long:
|
||||||
return (2, -1)
|
return (2, -1)
|
||||||
else:
|
else:
|
||||||
return (1, -2)
|
return (-1, 2)
|
||||||
|
|
||||||
|
|
||||||
func getActiveColor*(self: ChessBoard): PieceColor {.inline.} =
|
func getActiveColor*(self: ChessBoard): PieceColor {.inline.} =
|
||||||
|
@ -423,6 +425,8 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "too many fields in FEN string")
|
raise newException(ValueError, "too many fields in FEN string")
|
||||||
inc(index)
|
inc(index)
|
||||||
|
result.updateAttackedSquares()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc newDefaultChessboard*: ChessBoard =
|
proc newDefaultChessboard*: ChessBoard =
|
||||||
|
@ -519,34 +523,30 @@ func locationToAlgebraic*(loc: Location): string {.inline.} =
|
||||||
return &"{char(uint8(loc.col) + uint8('a'))}{rowToRank(loc.row)}"
|
return &"{char(uint8(loc.col) + uint8('a'))}{rowToRank(loc.row)}"
|
||||||
|
|
||||||
|
|
||||||
func getPiece*(self: ChessBoard, square: string): Piece =
|
func getPiece*(self: ChessBoard, loc: Location): Piece =
|
||||||
## Gets the piece on the given square
|
## Gets the piece at the given location
|
||||||
## in algebraic notation
|
|
||||||
let loc = square.algebraicToLocation()
|
|
||||||
return self.grid[loc.row, loc.col]
|
return self.grid[loc.row, loc.col]
|
||||||
|
|
||||||
|
|
||||||
func getCapture*(self: ChessBoard, move: Move): Location =
|
func getPiece*(self: ChessBoard, square: string): Piece =
|
||||||
## Returns the location that would be captured if this
|
## Gets the piece on the given square
|
||||||
## move were played on the board, taking en passant and
|
## in algebraic notation
|
||||||
## other things into account (the move is assumed to be
|
return self.getPiece(square.algebraicToLocation())
|
||||||
## already valid). An empty location is returned if no
|
|
||||||
## piece is captured by the given move
|
|
||||||
result = emptyLocation()
|
|
||||||
let target = self.grid[move.targetSquare.row, move.targetSquare.col]
|
|
||||||
if target.color == None:
|
|
||||||
if move.targetSquare != self.position.enPassantSquare.targetSquare:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
return ((if move.piece.color == White: move.targetSquare.row + 1 else: move.targetSquare.row - 1), move.targetSquare.col)
|
|
||||||
elif target.color == move.piece.color.opposite():
|
|
||||||
return move.targetSquare
|
|
||||||
|
|
||||||
|
|
||||||
func isCapture*(self: ChessBoard, move: Move): bool {.inline.} =
|
func isCapture*(self: ChessBoard, move: Move): bool {.inline.} =
|
||||||
## Returns whether the given move is a capture
|
## Returns whether the given move is a capture
|
||||||
## or not
|
## or not
|
||||||
return self.getCapture(move) != emptyLocation()
|
return move.flag in [Capture, EnPassant]
|
||||||
|
|
||||||
|
|
||||||
|
func getCapture*(self: ChessBoard, move: Move): Location =
|
||||||
|
## Returns the location that would be captured if this
|
||||||
|
## move were played on the board
|
||||||
|
if not self.isCapture(move):
|
||||||
|
return emptyLocation()
|
||||||
|
return move.targetSquare
|
||||||
|
|
||||||
|
|
||||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
||||||
|
@ -838,20 +838,19 @@ proc generateAllMoves*(self: ChessBoard): seq[Move] =
|
||||||
## in the current position
|
## in the current position
|
||||||
for i, row in self.grid:
|
for i, row in self.grid:
|
||||||
for j, piece in row:
|
for j, piece in row:
|
||||||
if self.grid[i, j].color != self.getActiveColor():
|
if self.grid[i, j].color == self.getActiveColor():
|
||||||
continue
|
for move in self.generateMoves((int8(i), int8(j))):
|
||||||
for move in self.generateMoves((int8(i), int8(j))):
|
result.add(move)
|
||||||
result.add(move)
|
|
||||||
|
|
||||||
|
|
||||||
proc getAttackers*(self: ChessBoard, square: Location): seq[Piece] =
|
proc getAttackers*(self: ChessBoard, square: Location): seq[Location] =
|
||||||
## Returns all the attackers of the given square
|
## Returns all the attackers of the given square
|
||||||
for move in self.position.attacked.black:
|
for attack in self.position.attacked.black:
|
||||||
if move.targetSquare == square:
|
if attack.dest == square:
|
||||||
result.add(move.piece)
|
result.add(attack.source)
|
||||||
for move in self.position.attacked.white:
|
for attack in self.position.attacked.white:
|
||||||
if move.targetSquare == square:
|
if attack.dest == square:
|
||||||
result.add(move.piece)
|
result.add(attack.source)
|
||||||
|
|
||||||
# We don't use getAttackers because this one only cares about whether
|
# We don't use getAttackers because this one only cares about whether
|
||||||
# the square is attacked or not (and can therefore exit earlier than
|
# the square is attacked or not (and can therefore exit earlier than
|
||||||
|
@ -862,91 +861,144 @@ proc isAttacked*(self: ChessBoard, loc: Location): bool =
|
||||||
let piece = self.grid[loc.row, loc.col]
|
let piece = self.grid[loc.row, loc.col]
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of White:
|
of White:
|
||||||
for move in self.position.attacked.black:
|
for attack in self.position.attacked.black:
|
||||||
if move.targetSquare == loc:
|
if attack.dest == loc:
|
||||||
return true
|
return true
|
||||||
of Black:
|
of Black:
|
||||||
for move in self.position.attacked.white:
|
for attack in self.position.attacked.black:
|
||||||
if move.targetSquare == loc:
|
if attack.dest == loc:
|
||||||
return true
|
return true
|
||||||
of None:
|
of None:
|
||||||
case self.getActiveColor():
|
case self.getActiveColor():
|
||||||
of White:
|
of White:
|
||||||
for move in self.position.attacked.black:
|
for attack in self.position.attacked.black:
|
||||||
if move.targetSquare == loc:
|
if attack.dest == loc:
|
||||||
return true
|
return true
|
||||||
of Black:
|
of Black:
|
||||||
for move in self.position.attacked.white:
|
for attack in self.position.attacked.black:
|
||||||
if move.targetSquare == loc:
|
if attack.dest == loc:
|
||||||
return true
|
return true
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc isAttacked*(self: ChessBoard, square: string): bool =
|
proc isAttacked*(self: ChessBoard, square: string): bool =
|
||||||
## Returns whether the given square is attacked
|
## Returns whether the given square is attacked
|
||||||
## by its opponent
|
## by its opponent
|
||||||
return self.isAttacked(square.algebraicToLocation())
|
return self.isAttacked(square.algebraicToLocation())
|
||||||
|
|
||||||
|
|
||||||
|
proc updatePawnAttacks(self: ChessBoard) =
|
||||||
|
## Internal helper of updateAttackedSquares
|
||||||
|
for loc in self.position.pieces.white.pawns:
|
||||||
|
# Pawns are special in how they capture (i.e. the
|
||||||
|
# squares they can move to do not match the squares
|
||||||
|
# they can capture on. Sneaky fucks)
|
||||||
|
let piece = self.grid[loc.row, loc.col]
|
||||||
|
self.position.attacked.white.add((loc, loc + piece.color.topRightDiagonal()))
|
||||||
|
self.position.attacked.white.add((loc, loc + piece.color.topLeftDiagonal()))
|
||||||
|
# We do the same thing for black
|
||||||
|
for loc in self.position.pieces.black.pawns:
|
||||||
|
let piece = self.grid[loc.row, loc.col]
|
||||||
|
self.position.attacked.black.add((loc, loc + piece.color.topRightDiagonal()))
|
||||||
|
self.position.attacked.black.add((loc, loc + piece.color.topLeftDiagonal()))
|
||||||
|
|
||||||
|
|
||||||
|
proc getSlidingAttacks(self: ChessBoard, loc: Location): Attacked =
|
||||||
|
## Internal helper of updateSlidingAttacks
|
||||||
|
var
|
||||||
|
directions: seq[Location] = @[]
|
||||||
|
square: Location = loc
|
||||||
|
otherPiece: Piece
|
||||||
|
let piece = self.grid[loc.row, loc.col]
|
||||||
|
if piece.kind in [Bishop, Queen]:
|
||||||
|
directions.add(piece.color.topLeftDiagonal())
|
||||||
|
directions.add(piece.color.topRightDiagonal())
|
||||||
|
directions.add(piece.color.bottomLeftDiagonal())
|
||||||
|
directions.add(piece.color.bottomRightDiagonal())
|
||||||
|
if piece.kind in [Queen, Rook]:
|
||||||
|
directions.add(piece.color.topSide())
|
||||||
|
directions.add(piece.color.bottomSide())
|
||||||
|
directions.add(piece.color.rightSide())
|
||||||
|
directions.add(piece.color.leftSide())
|
||||||
|
for direction in directions:
|
||||||
|
square = loc
|
||||||
|
# Slide in this direction as long as it's possible
|
||||||
|
while true:
|
||||||
|
square = square + direction
|
||||||
|
# End of board reached
|
||||||
|
if not square.isValid():
|
||||||
|
break
|
||||||
|
otherPiece = self.grid[square.row, square.col]
|
||||||
|
# A piece is in the way: we cannot proceed
|
||||||
|
# any further
|
||||||
|
if otherPiece.color notin [piece.color.opposite(), None]:
|
||||||
|
break
|
||||||
|
# Target square is attacked
|
||||||
|
result.add((loc, square))
|
||||||
|
|
||||||
|
|
||||||
|
proc updateSlidingAttacks(self: ChessBoard) =
|
||||||
|
## Internal helper of updateAttackedSquares
|
||||||
|
|
||||||
|
var
|
||||||
|
directions: seq[Location]
|
||||||
|
piece: Piece
|
||||||
|
# Bishops
|
||||||
|
for loc in self.position.pieces.white.bishops:
|
||||||
|
for attack in self.getSlidingAttacks(loc):
|
||||||
|
self.position.attacked.white.add(attack)
|
||||||
|
for loc in self.position.pieces.black.bishops:
|
||||||
|
for attack in self.getSlidingAttacks(loc):
|
||||||
|
self.position.attacked.black.add(attack)
|
||||||
|
# Rooks
|
||||||
|
for loc in self.position.pieces.white.rooks:
|
||||||
|
for attack in self.getSlidingAttacks(loc):
|
||||||
|
self.position.attacked.white.add(attack)
|
||||||
|
for loc in self.position.pieces.black.rooks:
|
||||||
|
for attack in self.getSlidingAttacks(loc):
|
||||||
|
self.position.attacked.black.add(attack)
|
||||||
|
# Queens
|
||||||
|
for loc in self.position.pieces.white.queens:
|
||||||
|
for attack in self.getSlidingAttacks(loc):
|
||||||
|
self.position.attacked.white.add(attack)
|
||||||
|
for loc in self.position.pieces.black.queens:
|
||||||
|
for attack in self.getSlidingAttacks(loc):
|
||||||
|
self.position.attacked.black.add(attack)
|
||||||
|
|
||||||
|
|
||||||
proc updateAttackedSquares(self: ChessBoard) =
|
proc updateAttackedSquares(self: ChessBoard) =
|
||||||
## Updates internal metadata about which squares
|
## Updates internal metadata about which squares
|
||||||
## are attacked. Called internally by doMove
|
## are attacked. Called internally by doMove
|
||||||
|
|
||||||
|
self.position.attacked.white.setLen(0)
|
||||||
# Go over each piece one by one and see which squares
|
self.position.attacked.black.setLen(0)
|
||||||
# it currently attacks
|
|
||||||
|
|
||||||
# Pawns
|
# Pawns
|
||||||
for loc in self.position.pieces.white.pawns:
|
self.updatePawnAttacks()
|
||||||
for move in self.generateMoves(loc):
|
# Sliding pieces
|
||||||
self.position.attacked.white.add(move)
|
self.updateSlidingAttacks()
|
||||||
# Bishops
|
|
||||||
for loc in self.position.pieces.white.bishops:
|
|
||||||
for move in self.generateMoves(loc):
|
|
||||||
self.position.attacked.white.add(move)
|
|
||||||
# Knights
|
# Knights
|
||||||
for loc in self.position.pieces.white.knights:
|
for loc in self.position.pieces.white.knights:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.position.attacked.white.add(move)
|
self.position.attacked.white.add((move.startSquare, move.targetSquare))
|
||||||
# Rooks
|
|
||||||
for loc in self.position.pieces.white.rooks:
|
|
||||||
for move in self.generateMoves(loc):
|
|
||||||
self.position.attacked.white.add(move)
|
|
||||||
# Queens
|
|
||||||
for loc in self.position.pieces.white.queens:
|
|
||||||
for move in self.generateMoves(loc):
|
|
||||||
self.position.attacked.white.add(move)
|
|
||||||
# King
|
# King
|
||||||
for move in self.generateMoves(self.position.pieces.white.king):
|
for move in self.generateMoves(self.position.pieces.white.king):
|
||||||
self.position.attacked.white.add(move)
|
self.position.attacked.white.add((move.startSquare, move.targetSquare))
|
||||||
|
# Knights
|
||||||
# Same for black
|
|
||||||
for loc in self.position.pieces.black.pawns:
|
|
||||||
for move in self.generateMoves(loc):
|
|
||||||
self.position.attacked.black.add(move)
|
|
||||||
for loc in self.position.pieces.black.bishops:
|
|
||||||
for move in self.generateMoves(loc):
|
|
||||||
self.position.attacked.black.add(move)
|
|
||||||
for loc in self.position.pieces.black.knights:
|
for loc in self.position.pieces.black.knights:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.position.attacked.white.add(move)
|
self.position.attacked.black.add((move.startSquare, move.targetSquare))
|
||||||
for loc in self.position.pieces.black.rooks:
|
# King
|
||||||
for move in self.generateMoves(loc):
|
|
||||||
self.position.attacked.black.add(move)
|
|
||||||
for loc in self.position.pieces.black.queens:
|
|
||||||
for move in self.generateMoves(loc):
|
|
||||||
self.position.attacked.black.add(move)
|
|
||||||
for move in self.generateMoves(self.position.pieces.black.king):
|
for move in self.generateMoves(self.position.pieces.black.king):
|
||||||
self.position.attacked.black.add(move)
|
self.position.attacked.black.add((move.startSquare, move.targetSquare))
|
||||||
|
|
||||||
|
|
||||||
proc removePiece(self: ChessBoard, location: Location) =
|
proc removePiece(self: ChessBoard, location: Location, emptyGrid: bool = true) =
|
||||||
## Removes a piece from the board, updating necessary
|
## Removes a piece from the board, updating necessary
|
||||||
## metadata
|
## metadata
|
||||||
var piece = self.grid[location.row, location.col]
|
var piece = self.grid[location.row, location.col]
|
||||||
self.grid[location.row, location.col] = emptyPiece()
|
if emptyGrid:
|
||||||
|
self.grid[location.row, location.col] = emptyPiece()
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of White:
|
of White:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
|
@ -1036,7 +1088,7 @@ proc movePiece(self: ChessBoard, move: Move, attack: bool = true) =
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
# Empty out the starting square
|
# Empty out the starting square
|
||||||
self.grid[move.startSquare.row, move.startSquare.col] = emptyPiece()
|
self.grid[move.startSquare.row, move.startSquare.col] = emptyPiece()
|
||||||
# Actually move the piece
|
# Actually move the piece
|
||||||
self.grid[move.targetSquare.row, move.targetSquare.col] = move.piece
|
self.grid[move.targetSquare.row, move.targetSquare.col] = move.piece
|
||||||
|
@ -1062,7 +1114,7 @@ proc updateLocations(self: ChessBoard, move: Move) =
|
||||||
self.removePiece(capture)
|
self.removePiece(capture)
|
||||||
# Update the positional metadata of the moving piece
|
# Update the positional metadata of the moving piece
|
||||||
self.movePiece(move)
|
self.movePiece(move)
|
||||||
|
|
||||||
|
|
||||||
proc doMove(self: ChessBoard, move: Move) =
|
proc doMove(self: ChessBoard, move: Move) =
|
||||||
## Internal function called by makeMove after
|
## Internal function called by makeMove after
|
||||||
|
@ -1080,108 +1132,103 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
halfMoveClock = self.position.halfMoveClock
|
halfMoveClock = self.position.halfMoveClock
|
||||||
fullMoveCount = self.position.fullMoveCount
|
fullMoveCount = self.position.fullMoveCount
|
||||||
castlingAvailable = self.position.castlingAvailable
|
castlingAvailable = self.position.castlingAvailable
|
||||||
if move.flag != Backwards:
|
let capture = self.getCapture(move)
|
||||||
if move.piece.kind == Pawn or self.isCapture(move):
|
if move.piece.kind == Pawn or self.isCapture(move):
|
||||||
halfMoveClock = 0
|
halfMoveClock = 0
|
||||||
else:
|
else:
|
||||||
inc(halfMoveClock)
|
inc(halfMoveClock)
|
||||||
if move.piece.color == Black:
|
if move.piece.color == Black:
|
||||||
inc(fullMoveCount)
|
inc(fullMoveCount)
|
||||||
# Castling check: have the rooks moved?
|
# Castling check: have the rooks moved?
|
||||||
if move.piece.kind == Rook:
|
if move.piece.kind == Rook:
|
||||||
case move.piece.color:
|
case move.piece.color:
|
||||||
of White:
|
of White:
|
||||||
if move.startSquare.row == move.piece.getStartRow():
|
if move.startSquare.row == move.piece.getStartRow():
|
||||||
if move.startSquare.col == 0:
|
if move.startSquare.col == 0:
|
||||||
# Queen side
|
# Queen side
|
||||||
castlingAvailable.white.queen = false
|
castlingAvailable.white.queen = false
|
||||||
elif move.startSquare.col == 7:
|
elif move.startSquare.col == 7:
|
||||||
# King side
|
# King side
|
||||||
castlingAvailable.white.king = false
|
castlingAvailable.white.king = false
|
||||||
of Black:
|
of Black:
|
||||||
if move.startSquare.row == move.piece.getStartRow():
|
if move.startSquare.row == move.piece.getStartRow():
|
||||||
if move.startSquare.col == 0:
|
if move.startSquare.col == 0:
|
||||||
# Queen side
|
# Queen side
|
||||||
castlingAvailable.black.queen = false
|
castlingAvailable.black.queen = false
|
||||||
elif move.startSquare.col == 7:
|
elif move.startSquare.col == 7:
|
||||||
# King side
|
# King side
|
||||||
castlingAvailable.black.king = false
|
castlingAvailable.black.king = false
|
||||||
else:
|
|
||||||
discard
|
|
||||||
# Has a rook been captured?
|
|
||||||
let capture = self.getCapture(move)
|
|
||||||
if capture != emptyLocation():
|
|
||||||
let piece = self.grid[capture.row, capture.col]
|
|
||||||
if piece.kind == Rook:
|
|
||||||
case piece.color:
|
|
||||||
of White:
|
|
||||||
if capture == piece.color.queenSideRook():
|
|
||||||
# Queen side
|
|
||||||
castlingAvailable.white.queen = false
|
|
||||||
elif capture == piece.color.kingSideRook():
|
|
||||||
# King side
|
|
||||||
castlingAvailable.white.king = false
|
|
||||||
of Black:
|
|
||||||
if capture == piece.color.queenSideRook():
|
|
||||||
# Queen side
|
|
||||||
castlingAvailable.black.queen = false
|
|
||||||
elif capture == piece.color.kingSideRook():
|
|
||||||
# King side
|
|
||||||
castlingAvailable.black.king = false
|
|
||||||
else:
|
|
||||||
# Unreachable
|
|
||||||
discard
|
|
||||||
# Has the king moved?
|
|
||||||
if move.piece.kind == King:
|
|
||||||
case move.piece.color:
|
|
||||||
of White:
|
|
||||||
castlingAvailable.white.king = false
|
|
||||||
castlingAvailable.white.queen = false
|
|
||||||
of Black:
|
|
||||||
castlingAvailable.black.king = false
|
|
||||||
castlingAvailable.black.queen = false
|
|
||||||
else:
|
|
||||||
discard
|
|
||||||
|
|
||||||
let previous = self.position
|
|
||||||
if move.flag != Backwards:
|
|
||||||
# Record final position for future reference
|
|
||||||
self.positions.add(previous)
|
|
||||||
# Create new position
|
|
||||||
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
|
||||||
halfMoveClock: halfMoveClock,
|
|
||||||
fullMoveCount: fullMoveCount,
|
|
||||||
captured: emptyPiece(),
|
|
||||||
turn: self.getActiveColor().opposite,
|
|
||||||
castlingAvailable: castlingAvailable,
|
|
||||||
# Updated at the next call to doMove()
|
|
||||||
move: emptyMove(),
|
|
||||||
pieces: previous.pieces,
|
|
||||||
)
|
|
||||||
|
|
||||||
var location: Location
|
|
||||||
if move.flag in [CastleShort, CastleLong]:
|
|
||||||
# Move the rook onto the
|
|
||||||
# correct file
|
|
||||||
var
|
|
||||||
location: Location
|
|
||||||
target: Location
|
|
||||||
if move.flag == CastleShort:
|
|
||||||
location = move.piece.color.kingSideRook()
|
|
||||||
target = shortCastleRook()
|
|
||||||
else:
|
else:
|
||||||
location = move.piece.color.queenSideRook()
|
discard
|
||||||
target = longCastleRook()
|
# Has a rook been captured?
|
||||||
let rook = self.grid[location.row, location.col]
|
if capture != emptyLocation():
|
||||||
let move = Move(startSquare: location, targetSquare: location + target, piece: rook, flag: move.flag)
|
let piece = self.grid[capture.row, capture.col]
|
||||||
self.movePiece(move, attack=false)
|
if piece.kind == Rook:
|
||||||
|
case piece.color:
|
||||||
|
of White:
|
||||||
|
if capture == piece.color.queenSideRook():
|
||||||
|
# Queen side
|
||||||
|
castlingAvailable.white.queen = false
|
||||||
|
elif capture == piece.color.kingSideRook():
|
||||||
|
# King side
|
||||||
|
castlingAvailable.white.king = false
|
||||||
|
of Black:
|
||||||
|
if capture == piece.color.queenSideRook():
|
||||||
|
# Queen side
|
||||||
|
castlingAvailable.black.queen = false
|
||||||
|
elif capture == piece.color.kingSideRook():
|
||||||
|
# King side
|
||||||
|
castlingAvailable.black.king = false
|
||||||
|
else:
|
||||||
|
# Unreachable
|
||||||
|
discard
|
||||||
|
# Has the king moved?
|
||||||
|
if move.piece.kind == King:
|
||||||
|
case move.piece.color:
|
||||||
|
of White:
|
||||||
|
castlingAvailable.white.king = false
|
||||||
|
castlingAvailable.white.queen = false
|
||||||
|
of Black:
|
||||||
|
castlingAvailable.black.king = false
|
||||||
|
castlingAvailable.black.queen = false
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
let previous = self.position
|
||||||
|
# Record final position for future reference
|
||||||
|
self.positions.add(previous)
|
||||||
|
# Create new position
|
||||||
|
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||||
|
halfMoveClock: halfMoveClock,
|
||||||
|
fullMoveCount: fullMoveCount,
|
||||||
|
captured: emptyPiece(),
|
||||||
|
turn: self.getActiveColor().opposite,
|
||||||
|
castlingAvailable: castlingAvailable,
|
||||||
|
# Updated at the next call to doMove()
|
||||||
|
move: emptyMove(),
|
||||||
|
pieces: previous.pieces,
|
||||||
|
)
|
||||||
|
|
||||||
|
var location: Location
|
||||||
|
if move.flag in [CastleShort, CastleLong]:
|
||||||
|
# Move the rook onto the
|
||||||
|
# correct file
|
||||||
|
var
|
||||||
|
location: Location
|
||||||
|
target: Location
|
||||||
|
if move.flag == CastleShort:
|
||||||
|
location = move.piece.color.kingSideRook()
|
||||||
|
target = shortCastleRook()
|
||||||
|
else:
|
||||||
|
location = move.piece.color.queenSideRook()
|
||||||
|
target = longCastleRook()
|
||||||
|
let rook = self.grid[location.row, location.col]
|
||||||
|
let move = Move(startSquare: location, targetSquare: location + target, piece: rook, flag: move.flag)
|
||||||
|
self.movePiece(move, attack=false)
|
||||||
|
|
||||||
# Update position and attack metadata
|
# Update position and attack metadata
|
||||||
if move.flag == Backwards:
|
self.updateLocations(move)
|
||||||
self.movePiece(move.targetSquare, move.startSquare)
|
|
||||||
else:
|
|
||||||
self.movePiece(move)
|
|
||||||
|
|
||||||
# Check for double pawn push
|
# Check for double pawn push
|
||||||
if move.flag == DoublePush:
|
if move.flag == DoublePush:
|
||||||
self.position.enPassantSquare = Move(piece: move.piece,
|
self.position.enPassantSquare = Move(piece: move.piece,
|
||||||
|
@ -1236,59 +1283,48 @@ proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||||
self.grid[location.row, location.col] = piece
|
self.grid[location.row, location.col] = piece
|
||||||
|
|
||||||
|
|
||||||
proc undoMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
proc undoMove*(self: ChessBoard, move: Move) =
|
||||||
## Undoes the given move if possible
|
## Undoes the given move
|
||||||
result = emptyMove()
|
|
||||||
if self.positions.len() == 0:
|
if self.positions.len() == 0:
|
||||||
return
|
return
|
||||||
var move: Move = move
|
var position = self.positions[^1]
|
||||||
let previous = self.positions.pop()
|
while true:
|
||||||
move.flag = Backwards
|
if position.move == move:
|
||||||
self.doMove(move)
|
break
|
||||||
self.position = previous
|
discard self.positions.pop()
|
||||||
self.position.move = move
|
position = self.positions[^1]
|
||||||
#self.updateLocations(move)
|
|
||||||
return move
|
|
||||||
|
|
||||||
|
self.grid[move.startSquare.row, move.startSquare.col] = move.piece
|
||||||
|
if self.isCapture(move):
|
||||||
|
self.grid[move.targetSquare.row, move.targetSquare.col] = self.position.captured
|
||||||
|
else:
|
||||||
|
self.grid[move.targetSquare.row, move.targetSquare.col] = emptyPiece()
|
||||||
|
self.position = position
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc isLegal(self: ChessBoard, move: Move, keep: bool = false): bool =
|
proc isLegal(self: ChessBoard, move: Move, keep: bool = false): bool =
|
||||||
## Returns whether the given move is legal
|
## Returns whether the given move is legal
|
||||||
var move = move
|
self.doMove(move)
|
||||||
if move.piece.kind == King and longCastleKing() + move.startSquare == move.targetSquare:
|
|
||||||
move.flag = CastleLong
|
|
||||||
elif move.piece.kind == King and shortCastleKing() + move.startSquare == move.targetSquare:
|
|
||||||
move.flag = CastleShort
|
|
||||||
if move.piece.kind == Pawn and move.piece.color.doublePush() + move.startSquare == move.targetSquare:
|
|
||||||
move.flag = DoublePush
|
|
||||||
if move notin self.generateMoves(move.startSquare):
|
if move notin self.generateMoves(move.startSquare):
|
||||||
# Piece cannot arrive to destination (blocked
|
# Piece cannot arrive to destination (blocked
|
||||||
# or otherwise invalid move)
|
# or otherwise invalid move)
|
||||||
return false
|
return false
|
||||||
self.doMove(move)
|
|
||||||
if not keep:
|
if not keep:
|
||||||
defer: self.undoMove(move)
|
defer: self.undoMove(move)
|
||||||
# Move would reveal an attack
|
# Move would reveal an attack
|
||||||
# on our king: not allowed
|
# on our king: not allowed
|
||||||
if self.inCheck(move.piece.color):
|
return self.inCheck(move.piece.color)
|
||||||
return false
|
|
||||||
# All checks have passed: move is legal
|
|
||||||
result = true
|
|
||||||
|
|
||||||
|
|
||||||
proc isLegalFast(self: ChessBoard, move: Move, keep: bool = false): bool =
|
proc isLegalFast(self: ChessBoard, move: Move, keep: bool = false): bool =
|
||||||
## Returns whether the given move is legal
|
## Returns whether the given move is legal
|
||||||
## assuming that the input move is pseudo legal
|
## assuming that the input move is pseudo legal
|
||||||
var move = move
|
|
||||||
if move.piece.kind == King and longCastleKing() + move.startSquare == move.targetSquare:
|
|
||||||
move.flag = CastleLong
|
|
||||||
elif move.piece.kind == King and shortCastleKing() + move.startSquare == move.targetSquare:
|
|
||||||
move.flag = CastleShort
|
|
||||||
if move.piece.kind == Pawn and move.piece.color.doublePush() + move.startSquare == move.targetSquare:
|
|
||||||
move.flag = DoublePush
|
|
||||||
self.position.move = move
|
self.position.move = move
|
||||||
self.doMove(move)
|
self.doMove(move)
|
||||||
if not keep:
|
if not keep:
|
||||||
defer: self.undoMove(move)
|
defer: self.undoMove(move)
|
||||||
|
|
||||||
# Move would reveal an attack
|
# Move would reveal an attack
|
||||||
# on our king: not allowed
|
# on our king: not allowed
|
||||||
if self.inCheck(move.piece.color):
|
if self.inCheck(move.piece.color):
|
||||||
|
@ -1301,26 +1337,12 @@ proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||||
## Like the other makeMove(), but with a Move object
|
## Like the other makeMove(), but with a Move object
|
||||||
result = move
|
result = move
|
||||||
self.position.move = move
|
self.position.move = move
|
||||||
if move.flag == Backwards:
|
|
||||||
self.doMove(result)
|
|
||||||
let legal = self.isLegal(move, keep=true)
|
let legal = self.isLegal(move, keep=true)
|
||||||
if not legal:
|
if not legal:
|
||||||
return emptyMove()
|
return emptyMove()
|
||||||
result = self.position.move
|
result = self.position.move
|
||||||
|
|
||||||
|
|
||||||
proc makeMoveFast*(self: ChessBoard, move: Move): Move {.discardable.} =
|
|
||||||
## Like the other makeMove(), but uses isLegalFast
|
|
||||||
result = move
|
|
||||||
self.position.move = move
|
|
||||||
if move.flag == Backwards:
|
|
||||||
self.doMove(result)
|
|
||||||
let legal = self.isLegalFast(move, keep=true)
|
|
||||||
if not legal:
|
|
||||||
return emptyMove()
|
|
||||||
result = self.position.move
|
|
||||||
|
|
||||||
|
|
||||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.discardable.} =
|
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.discardable.} =
|
||||||
## Makes a move on the board from the chosen start square to
|
## Makes a move on the board from the chosen start square to
|
||||||
## the chosen target square, ensuring it is legal (turns are
|
## the chosen target square, ensuring it is legal (turns are
|
||||||
|
@ -1387,18 +1409,17 @@ proc pretty*(self: ChessBoard): string =
|
||||||
result &= "\x1b[0m"
|
result &= "\x1b[0m"
|
||||||
|
|
||||||
|
|
||||||
|
proc countLegalMoves*(self: ChessBoard, ply: int, verbose: bool = false, current: int = 0): int =
|
||||||
|
|
||||||
proc countLegalMoves*(self: ChessBoard, ply: int, verbose: bool = false, verboseIllegal: bool = false, current: int = 0): int =
|
|
||||||
## Counts (and debugs) the number of legal positions reached after
|
## Counts (and debugs) the number of legal positions reached after
|
||||||
## the given number of half moves
|
## the given number of half moves
|
||||||
if ply == 0:
|
if ply == 0:
|
||||||
result = 1
|
result = 1
|
||||||
else:
|
else:
|
||||||
var now: string
|
var before: string
|
||||||
for move in self.generateAllMoves():
|
for move in self.generateAllMoves():
|
||||||
now = self.pretty()
|
before = self.pretty()
|
||||||
if self.isLegalFast(move, keep=true):
|
self.doMove(move)
|
||||||
|
if not self.inCheck(move.piece.color):
|
||||||
if verbose:
|
if verbose:
|
||||||
let canCastle = self.canCastle()
|
let canCastle = self.canCastle()
|
||||||
echo "\x1Bc"
|
echo "\x1Bc"
|
||||||
|
@ -1406,12 +1427,10 @@ proc countLegalMoves*(self: ChessBoard, ply: int, verbose: bool = false, verbose
|
||||||
echo &"Move: {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()} (({move.startSquare.row}, {move.startSquare.col}) -> ({move.targetSquare.row}, {move.targetSquare.col}))"
|
echo &"Move: {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()} (({move.startSquare.row}, {move.startSquare.col}) -> ({move.targetSquare.row}, {move.targetSquare.col}))"
|
||||||
echo &"Turn: {move.piece.color}"
|
echo &"Turn: {move.piece.color}"
|
||||||
echo &"Piece: {move.piece.kind}"
|
echo &"Piece: {move.piece.kind}"
|
||||||
echo &"In check: {(if self.inCheck(move.piece.color): \"yes\" else: \"no\")}"
|
|
||||||
echo "Legal: yes"
|
|
||||||
echo &"Flag: {move.flag}"
|
echo &"Flag: {move.flag}"
|
||||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||||
echo "\nBefore:"
|
echo "\nBefore:"
|
||||||
echo now
|
echo before
|
||||||
echo "\nNow: "
|
echo "\nNow: "
|
||||||
echo self.pretty()
|
echo self.pretty()
|
||||||
try:
|
try:
|
||||||
|
@ -1420,30 +1439,11 @@ proc countLegalMoves*(self: ChessBoard, ply: int, verbose: bool = false, verbose
|
||||||
discard
|
discard
|
||||||
except EOFError:
|
except EOFError:
|
||||||
discard
|
discard
|
||||||
result += self.countLegalMoves(ply - 1, verbose, verboseIllegal, result)
|
result += self.countLegalMoves(ply - 1, verbose, result + 1)
|
||||||
elif verboseIllegal:
|
|
||||||
let canCastle = self.canCastle()
|
|
||||||
echo "\x1Bc"
|
|
||||||
echo &"Ply: {self.position.plyFromRoot} (move {current + result + 1})"
|
|
||||||
echo &"Move: {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()} (({move.startSquare.row}, {move.startSquare.col}) -> ({move.targetSquare.row}, {move.targetSquare.col}))"
|
|
||||||
echo &"Turn: {move.piece.color}"
|
|
||||||
echo &"Piece: {move.piece.kind}"
|
|
||||||
echo &"In check: {(if self.inCheck(move.piece.color): \"yes\" else: \"no\")}"
|
|
||||||
echo "Legal: no"
|
|
||||||
echo &"Flag: {move.flag}"
|
|
||||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
|
||||||
echo "\nBefore:"
|
|
||||||
echo now
|
|
||||||
echo "\nNow: "
|
|
||||||
echo self.pretty()
|
|
||||||
try:
|
|
||||||
discard readLine(stdin)
|
|
||||||
except IOError:
|
|
||||||
discard
|
|
||||||
except EOFError:
|
|
||||||
discard
|
|
||||||
self.undoMove(move)
|
self.undoMove(move)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
|
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
|
||||||
doAssert piece.kind == kind and piece.color == color, &"expected piece of kind {kind} and color {color}, got {piece.kind} / {piece.color} instead"
|
doAssert piece.kind == kind and piece.color == color, &"expected piece of kind {kind} and color {color}, got {piece.kind} / {piece.color} instead"
|
||||||
|
@ -1501,6 +1501,6 @@ when isMainModule:
|
||||||
when compileOption("profiler"):
|
when compileOption("profiler"):
|
||||||
import nimprof
|
import nimprof
|
||||||
|
|
||||||
b = newChessboardFromFEN("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -")
|
|
||||||
echo b.countLegalMoves(2, verbose=true, verboseIllegal=true)
|
echo b.countLegalMoves(4, verbose=false)
|
||||||
echo "All tests were successful"
|
echo "All tests were successful"
|
Loading…
Reference in New Issue