Bug fixes(?)
This commit is contained in:
parent
3dca208123
commit
6e10cbe925
|
@ -99,30 +99,19 @@ type
|
||||||
attacked: tuple[white: Attacked, black: Attacked]
|
attacked: tuple[white: Attacked, black: Attacked]
|
||||||
# Pieces pinned by both sides
|
# Pieces pinned by both sides
|
||||||
pinned: tuple[white: Attacked, black: Attacked]
|
pinned: tuple[white: Attacked, black: Attacked]
|
||||||
# The original piece captured to reach this position (may be empty)
|
|
||||||
#captured: Piece
|
|
||||||
# The piece that moved to reach this position (needed to undo moves)
|
|
||||||
#moved: Piece
|
|
||||||
# Active color
|
# Active color
|
||||||
turn: PieceColor
|
turn: PieceColor
|
||||||
|
|
||||||
CacheEntry[T] = ref object
|
|
||||||
valid: bool
|
|
||||||
data: T
|
|
||||||
|
|
||||||
Cache = object
|
|
||||||
canCastle: tuple[white, black: CacheEntry[tuple[queen, king: bool]]]
|
|
||||||
inCheck: tuple[white, black: CacheEntry[bool]]
|
|
||||||
|
|
||||||
ChessBoard* = ref object
|
ChessBoard* = ref object
|
||||||
## A chess board object
|
## A chess board object
|
||||||
|
|
||||||
|
# An 8x8 matrix we use for constant
|
||||||
|
# time lookup of pieces by their location
|
||||||
grid: Matrix[Piece]
|
grid: Matrix[Piece]
|
||||||
|
# The current position
|
||||||
position: Position
|
position: Position
|
||||||
# List of reached positions
|
# List of all reached positions
|
||||||
positions: seq[Position]
|
positions: seq[Position]
|
||||||
# Cached results of expensive
|
|
||||||
# functions in the current position
|
|
||||||
#cache: Cache
|
|
||||||
|
|
||||||
|
|
||||||
# Initialized only once, copied every time
|
# Initialized only once, copied every time
|
||||||
|
@ -131,6 +120,8 @@ for _ in countup(0, 63):
|
||||||
empty.add(Piece(kind: Empty, color: None))
|
empty.add(Piece(kind: Empty, color: None))
|
||||||
|
|
||||||
|
|
||||||
|
# A bunch of simple utility functions
|
||||||
|
|
||||||
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)
|
||||||
|
@ -153,7 +144,6 @@ proc updateAttackedSquares(self: ChessBoard)
|
||||||
proc getPinnedDirections(self: ChessBoard, loc: Location): seq[Location]
|
proc getPinnedDirections(self: ChessBoard, loc: Location): seq[Location]
|
||||||
proc getAttacks*(self: ChessBoard, loc: Location): Attacked
|
proc getAttacks*(self: ChessBoard, loc: Location): Attacked
|
||||||
proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked, pins: Attacked]
|
proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked, pins: Attacked]
|
||||||
#[func invalidateCache(self: ChessBoard) {.inline.}]#
|
|
||||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool
|
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool
|
||||||
|
|
||||||
|
|
||||||
|
@ -162,9 +152,8 @@ proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} =
|
||||||
self.add(x)
|
self.add(x)
|
||||||
|
|
||||||
|
|
||||||
# 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 and 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 everywhere
|
||||||
# everywhere
|
|
||||||
func topLeftDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (-1, -1) else: (1, 1))
|
func topLeftDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (-1, -1) else: (1, 1))
|
||||||
func topRightDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (-1, 1) else: (1, -1))
|
func topRightDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (-1, 1) else: (1, -1))
|
||||||
func bottomLeftDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (1, -1) else: (-1, 1))
|
func bottomLeftDiagonal(color: PieceColor): Location {.inline.} = (if color == White: (1, -1) else: (-1, 1))
|
||||||
|
@ -173,16 +162,11 @@ func leftSide(color: PieceColor): Location {.inline.} = (if color == White: (0,
|
||||||
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.} = (if color == White: (-1, 0) else: (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 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))
|
||||||
func longCastleKing: Location {.inline.} = (0, -2)
|
func longCastleKing: Location {.inline.} = (0, -2)
|
||||||
func shortCastleKing: Location {.inline.} = (0, 2)
|
func shortCastleKing: Location {.inline.} = (0, 2)
|
||||||
func longCastleRook: Location {.inline.} = (0, 3)
|
func longCastleRook: Location {.inline.} = (0, 3)
|
||||||
func shortCastleRook: Location {.inline.} = (0, -2)
|
func shortCastleRook: Location {.inline.} = (0, -2)
|
||||||
func kingSideRook(color: PieceColor): Location {.inline.} = (if color == White: (7, 7) else: (0, 7))
|
|
||||||
func queenSideRook(color: PieceColor): Location {.inline.} = (if color == White: (7, 0) else: (0, 0))
|
|
||||||
|
|
||||||
|
|
||||||
func bottomLeftKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
|
func bottomLeftKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
|
||||||
if color == White:
|
if color == White:
|
||||||
if long:
|
if long:
|
||||||
|
@ -234,7 +218,12 @@ func topRightKnightMove(color: PieceColor, long: bool = true): Location {.inline
|
||||||
else:
|
else:
|
||||||
return (-1, 2)
|
return (-1, 2)
|
||||||
|
|
||||||
|
# These return absolute locations rather than relative direction offsets
|
||||||
|
func kingSideRook(color: PieceColor): Location {.inline.} = (if color == White: (7, 7) else: (0, 7))
|
||||||
|
func queenSideRook(color: PieceColor): Location {.inline.} = (if color == White: (7, 0) else: (0, 0))
|
||||||
|
|
||||||
|
|
||||||
|
# A bunch of getters
|
||||||
func getActiveColor*(self: ChessBoard): PieceColor {.inline.} =
|
func getActiveColor*(self: ChessBoard): PieceColor {.inline.} =
|
||||||
## Returns the currently active color
|
## Returns the currently active color
|
||||||
## (turn of who has to move)
|
## (turn of who has to move)
|
||||||
|
@ -296,8 +285,6 @@ proc newChessboard: ChessBoard =
|
||||||
new(result)
|
new(result)
|
||||||
# Turns our flat sequence into an 8x8 grid
|
# Turns our flat sequence into an 8x8 grid
|
||||||
result.grid = newMatrixFromSeq[Piece](empty, (8, 8))
|
result.grid = newMatrixFromSeq[Piece](empty, (8, 8))
|
||||||
#result.cache = Cache(canCastle: (white: CacheEntry[tuple[queen, king: bool]](), black: CacheEntry[tuple[queen, king: bool]]()),
|
|
||||||
# inCheck: (white: CacheEntry[bool](), black: CacheEntry[bool]()))
|
|
||||||
result.position = Position(attacked: (@[], @[]),
|
result.position = Position(attacked: (@[], @[]),
|
||||||
enPassantSquare: emptyLocation(),
|
enPassantSquare: emptyLocation(),
|
||||||
move: emptyMove(),
|
move: emptyMove(),
|
||||||
|
@ -318,9 +305,9 @@ proc newChessboard: ChessBoard =
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc newChessboardFromFEN*(state: string): ChessBoard =
|
proc newChessboardFromFEN*(fen: string): ChessBoard =
|
||||||
## Initializes a chessboard with the
|
## Initializes a chessboard with the
|
||||||
## state encoded by the given FEN string
|
## position encoded by the given FEN string
|
||||||
result = newChessboard()
|
result = newChessboard()
|
||||||
var
|
var
|
||||||
# Current location in the grid
|
# Current location in the grid
|
||||||
|
@ -330,11 +317,11 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
section = 0
|
section = 0
|
||||||
# Current index into the FEN string
|
# Current index into the FEN string
|
||||||
index = 0
|
index = 0
|
||||||
# Temporary variable to store the piece
|
# Temporary variable to store a piece
|
||||||
piece: Piece
|
piece: Piece
|
||||||
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
|
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
|
||||||
while index <= state.high():
|
while index <= fen.high():
|
||||||
var c = state[index]
|
var c = fen[index]
|
||||||
if c == ' ':
|
if c == ' ':
|
||||||
# Next section
|
# Next section
|
||||||
inc(section)
|
inc(section)
|
||||||
|
@ -366,6 +353,8 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
of Queen:
|
of Queen:
|
||||||
result.position.pieces.black.queens.add((row, column))
|
result.position.pieces.black.queens.add((row, column))
|
||||||
of King:
|
of King:
|
||||||
|
if result.position.pieces.black.king != emptyLocation():
|
||||||
|
raise newException(ValueError, "invalid position: exactly one king of each color must be present")
|
||||||
result.position.pieces.black.king = (row, column)
|
result.position.pieces.black.king = (row, column)
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
@ -382,6 +371,8 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
of Queen:
|
of Queen:
|
||||||
result.position.pieces.white.queens.add((row, column))
|
result.position.pieces.white.queens.add((row, column))
|
||||||
of King:
|
of King:
|
||||||
|
if result.position.pieces.white.king != emptyLocation():
|
||||||
|
raise newException(ValueError, "invalid position: exactly one king of each color must be present")
|
||||||
result.position.pieces.white.king = (row, column)
|
result.position.pieces.white.king = (row, column)
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
@ -397,10 +388,10 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
# Skip x columns
|
# Skip x columns
|
||||||
let x = int(uint8(c) - uint8('0'))
|
let x = int(uint8(c) - uint8('0'))
|
||||||
if x > 8:
|
if x > 8:
|
||||||
raise newException(ValueError, "invalid skip value (> 8) in FEN string")
|
raise newException(ValueError, &"invalid FEN: invalid column skip size ({x} > 8)")
|
||||||
column += int8(x)
|
column += int8(x)
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "invalid piece identifier in FEN string")
|
raise newException(ValueError, &"invalid FEN: unknown piece identifier '{c}'")
|
||||||
of 1:
|
of 1:
|
||||||
# Active color
|
# Active color
|
||||||
case c:
|
case c:
|
||||||
|
@ -409,7 +400,7 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
of 'b':
|
of 'b':
|
||||||
result.position.turn = Black
|
result.position.turn = Black
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "invalid active color identifier in FEN string")
|
raise newException(ValueError, &"invalid FEN: invalid active color identifier '{c}'")
|
||||||
of 2:
|
of 2:
|
||||||
# Castling availability
|
# Castling availability
|
||||||
case c:
|
case c:
|
||||||
|
@ -427,7 +418,7 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
of 'q':
|
of 'q':
|
||||||
result.position.castlingAvailable.black.queen = true
|
result.position.castlingAvailable.black.queen = true
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "invalid castling availability in FEN string")
|
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castling availability section")
|
||||||
of 3:
|
of 3:
|
||||||
# En passant target square
|
# En passant target square
|
||||||
case c:
|
case c:
|
||||||
|
@ -435,14 +426,14 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
# Field is already uninitialized to the correct state
|
# Field is already uninitialized to the correct state
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
result.position.enPassantSquare = state[index..index+1].algebraicToLocation()
|
result.position.enPassantSquare = fen[index..index+1].algebraicToLocation()
|
||||||
# Square metadata is 2 bytes long
|
# Square metadata is 2 bytes long
|
||||||
inc(index)
|
inc(index)
|
||||||
of 4:
|
of 4:
|
||||||
# Halfmove clock
|
# Halfmove clock
|
||||||
var s = ""
|
var s = ""
|
||||||
while not state[index].isSpaceAscii():
|
while not fen[index].isSpaceAscii():
|
||||||
s.add(state[index])
|
s.add(fen[index])
|
||||||
inc(index)
|
inc(index)
|
||||||
# Backtrack so the space is seen by the
|
# Backtrack so the space is seen by the
|
||||||
# next iteration of the loop
|
# next iteration of the loop
|
||||||
|
@ -451,17 +442,20 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
of 5:
|
of 5:
|
||||||
# Fullmove number
|
# Fullmove number
|
||||||
var s = ""
|
var s = ""
|
||||||
while index <= state.high():
|
while index <= fen.high():
|
||||||
s.add(state[index])
|
s.add(fen[index])
|
||||||
inc(index)
|
inc(index)
|
||||||
result.position.fullMoveCount = parseInt(s).int8
|
result.position.fullMoveCount = parseInt(s).int8
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "too many fields in FEN string")
|
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||||
inc(index)
|
inc(index)
|
||||||
result.updateAttackedSquares()
|
result.updateAttackedSquares()
|
||||||
if result.inCheck(result.getActiveColor().opposite):
|
if result.inCheck(result.getActiveColor().opposite):
|
||||||
# Opponent king cannot be captured!
|
# Opponent king cannot be captured on the next move
|
||||||
raise newException(ValueError, "invalid position: opponent king can be captured")
|
raise newException(ValueError, "invalid position: opponent king can be captured")
|
||||||
|
if result.position.pieces.white.king == emptyLocation() or result.position.pieces.black.king == emptyLocation():
|
||||||
|
# Both kings must be on the board
|
||||||
|
raise newException(ValueError, "invalid position: exactly one king of each color must be present")
|
||||||
|
|
||||||
|
|
||||||
proc newDefaultChessboard*: ChessBoard {.inline.} =
|
proc newDefaultChessboard*: ChessBoard {.inline.} =
|
||||||
|
@ -588,26 +582,12 @@ proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
||||||
color = self.getActiveColor()
|
color = self.getActiveColor()
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
#[if self.cache.inCheck.white.valid:
|
|
||||||
return self.cache.inCheck.white.data]#
|
|
||||||
result = self.isAttacked(self.position.pieces.white.king, Black)
|
result = self.isAttacked(self.position.pieces.white.king, Black)
|
||||||
of Black:
|
of Black:
|
||||||
#[if self.cache.inCheck.black.valid:
|
|
||||||
return self.cache.inCheck.black.data]#
|
|
||||||
result = self.isAttacked(self.position.pieces.black.king, White)
|
result = self.isAttacked(self.position.pieces.black.king, White)
|
||||||
else:
|
else:
|
||||||
# Unreachable
|
# Unreachable
|
||||||
discard
|
discard
|
||||||
#[case color:
|
|
||||||
of White:
|
|
||||||
self.cache.inCheck.white.valid = true
|
|
||||||
self.cache.inCheck.white.data = result
|
|
||||||
of Black:
|
|
||||||
self.cache.inCheck.black.valid = true
|
|
||||||
self.cache.inCheck.black.data = result
|
|
||||||
else:
|
|
||||||
# Unreachable
|
|
||||||
discard]#
|
|
||||||
|
|
||||||
|
|
||||||
proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king: bool] {.inline.} =
|
proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king: bool] {.inline.} =
|
||||||
|
@ -617,17 +597,12 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
|
||||||
var color = color
|
var color = color
|
||||||
if color == None:
|
if color == None:
|
||||||
color = self.getActiveColor()
|
color = self.getActiveColor()
|
||||||
# If the rooks or king have been moved, castling
|
# Check if castling rights are still available for moving side
|
||||||
# rights have been lost
|
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
#[if self.cache.canCastle.white.valid:
|
|
||||||
return self.cache.canCastle.white.data]#
|
|
||||||
result.king = self.position.castlingAvailable.white.king
|
result.king = self.position.castlingAvailable.white.king
|
||||||
result.queen = self.position.castlingAvailable.white.queen
|
result.queen = self.position.castlingAvailable.white.queen
|
||||||
of Black:
|
of Black:
|
||||||
#[if self.cache.canCastle.black.valid:
|
|
||||||
return self.cache.canCastle.black.data#]#
|
|
||||||
result.king = self.position.castlingAvailable.black.king
|
result.king = self.position.castlingAvailable.black.king
|
||||||
result.queen = self.position.castlingAvailable.black.queen
|
result.queen = self.position.castlingAvailable.black.queen
|
||||||
of None:
|
of None:
|
||||||
|
@ -641,9 +616,9 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
|
||||||
loc: Location
|
loc: Location
|
||||||
queenSide: Location
|
queenSide: Location
|
||||||
kingSide: Location
|
kingSide: Location
|
||||||
# If the path between the king and rook on a given side is blocked or any of the
|
# If the path between the king and rook on a given side is blocked, or any of the
|
||||||
# squares where the king would travel to are attacked by the opponent,
|
# squares where the king would move to are attacked by the opponent, then castling
|
||||||
# then castling is (temporarily) prohibited on that side
|
# is temporarily prohibited on that side
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
loc = self.position.pieces.white.king
|
loc = self.position.pieces.white.king
|
||||||
|
@ -662,42 +637,53 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
|
||||||
var
|
var
|
||||||
location = loc
|
location = loc
|
||||||
otherPiece: Piece
|
otherPiece: Piece
|
||||||
while location != loc + shortCastleKing():
|
moveKing: bool = true
|
||||||
|
while true:
|
||||||
location = location + kingSide
|
location = location + kingSide
|
||||||
|
if location == color.kingSideRook():
|
||||||
|
# No need to do any extra checks: if the piece
|
||||||
|
# on this square were not a rook of the same color
|
||||||
|
# as the castling king, then we wouldn't have gotten
|
||||||
|
# here in the first place (it would've had to be either
|
||||||
|
# moved or captured, and both of those actions are detected
|
||||||
|
# and accounted for way before this point)
|
||||||
|
break
|
||||||
|
if location == loc + shortCastleKing():
|
||||||
|
moveKing = false
|
||||||
otherPiece = self.grid[location.row, location.col]
|
otherPiece = self.grid[location.row, location.col]
|
||||||
|
|
||||||
if otherPiece.color != None or self.isAttacked(location, color.opposite()):
|
if otherPiece.color != None:
|
||||||
|
result.king = false
|
||||||
|
break
|
||||||
|
if moveKing and self.isAttacked(location, color.opposite()):
|
||||||
result.king = false
|
result.king = false
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
if result.queen:
|
if result.queen:
|
||||||
# Long castle
|
# Long castle
|
||||||
var
|
var
|
||||||
location = loc
|
location = loc
|
||||||
otherPiece: Piece
|
otherPiece: Piece
|
||||||
while location != loc + longCastleKing():
|
moveKing: bool = true
|
||||||
|
while true:
|
||||||
location = location + queenSide
|
location = location + queenSide
|
||||||
|
if location == color.queenSideRook():
|
||||||
|
break
|
||||||
|
if location == loc + longCastleKing():
|
||||||
|
moveKing = false
|
||||||
otherPiece = self.grid[location.row, location.col]
|
otherPiece = self.grid[location.row, location.col]
|
||||||
|
|
||||||
if otherPiece.color != None or self.isAttacked(location, color.opposite()):
|
if otherPiece.color != None:
|
||||||
|
result.queen = false
|
||||||
|
break
|
||||||
|
if moveKing and self.isAttacked(location, color.opposite()):
|
||||||
result.queen = false
|
result.queen = false
|
||||||
break
|
break
|
||||||
|
|
||||||
#[case color:
|
|
||||||
of White:
|
|
||||||
self.cache.canCastle.white.data = result
|
|
||||||
self.cache.canCastle.white.valid = true
|
|
||||||
of Black:
|
|
||||||
self.cache.canCastle.black.data = result
|
|
||||||
self.cache.canCastle.black.valid = true
|
|
||||||
else:
|
|
||||||
discard]#
|
|
||||||
|
|
||||||
|
|
||||||
proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
||||||
## Returns the squares that need to be covered to
|
## Returns the squares that need to be covered to
|
||||||
## resolve the current check (includes capturing
|
## resolve the current check (including capturing
|
||||||
## the checking piece). In case of double check, an
|
## the checking piece). In case of double check, an
|
||||||
## empty list is returned (as the king must move)
|
## empty list is returned (as the king must move)
|
||||||
var king: Location
|
var king: Location
|
||||||
|
@ -707,7 +693,7 @@ proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
||||||
of Black:
|
of Black:
|
||||||
king = self.position.pieces.black.king
|
king = self.position.pieces.black.king
|
||||||
else:
|
else:
|
||||||
return @[]
|
return
|
||||||
|
|
||||||
let attackers: seq[Location] = self.getAttackers(king, color.opposite())
|
let attackers: seq[Location] = self.getAttackers(king, color.opposite())
|
||||||
if attackers.len() > 1:
|
if attackers.len() > 1:
|
||||||
|
@ -722,8 +708,8 @@ proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
||||||
# Blocking the attack is also a viable strategy
|
# Blocking the attack is also a viable strategy
|
||||||
# (unless the check is from a knight or a pawn,
|
# (unless the check is from a knight or a pawn,
|
||||||
# in which case either the king has to move or
|
# in which case either the king has to move or
|
||||||
# that piece has to be captured)
|
# that piece has to be captured, but this is
|
||||||
if attackerPiece.kind notin [Knight, Pawn]:
|
# already implicitly handled by the loop below)
|
||||||
var location = attacker
|
var location = attacker
|
||||||
while location != king:
|
while location != king:
|
||||||
location = location + attack.direction
|
location = location + attack.direction
|
||||||
|
@ -754,7 +740,7 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
flags: seq[MoveFlag] = @[]
|
flags: seq[MoveFlag] = @[]
|
||||||
doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}"
|
doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}"
|
||||||
# Pawns can move forward one square
|
# Pawns can move forward one square
|
||||||
let forward = (piece.color.forward() + location)
|
let forward = (piece.color.topSide() + location)
|
||||||
# Only if the square is empty though
|
# Only if the square is empty though
|
||||||
if forward.isValid() and self.grid[forward.row, forward.col].color == None:
|
if forward.isValid() and self.grid[forward.row, forward.col].color == None:
|
||||||
locations.add(forward)
|
locations.add(forward)
|
||||||
|
@ -766,13 +752,12 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
if double.isValid() and self.grid[forward.row, forward.col].color == None and self.grid[double.row, double.col].color == None:
|
if double.isValid() and self.grid[forward.row, forward.col].color == None and self.grid[double.row, double.col].color == None:
|
||||||
locations.add(double)
|
locations.add(double)
|
||||||
flags.add(DoublePush)
|
flags.add(DoublePush)
|
||||||
let enPassantPiece = self.getEnPassantTarget() + piece.color.opposite().topSide()
|
let enPassantPawn = self.getEnPassantTarget() + piece.color.opposite().topSide()
|
||||||
let enPassantPawn = self.grid[enPassantPiece.row, enPassantPiece.col]
|
|
||||||
# They can also move on either diagonal one
|
# They can also move on either diagonal one
|
||||||
# square, but only to capture or for en passant
|
# square, but only to capture or for en passant
|
||||||
for diagonal in [location + piece.color.topRightDiagonal(), location + piece.color.topLeftDiagonal()]:
|
for diagonal in [location + piece.color.topRightDiagonal(), location + piece.color.topLeftDiagonal()]:
|
||||||
if diagonal.isValid():
|
if diagonal.isValid():
|
||||||
if enPassantPawn.color == piece.color.opposite() and diagonal == self.position.enPassantSquare:
|
if diagonal == self.position.enPassantSquare and self.grid[enPassantPawn.row, enPassantPawn.col].color == self.getActiveColor().opposite():
|
||||||
# Ensure en passant doesn't create a check
|
# Ensure en passant doesn't create a check
|
||||||
let king = self.getKing(piece.color)
|
let king = self.getKing(piece.color)
|
||||||
var ok = true
|
var ok = true
|
||||||
|
@ -1253,9 +1238,6 @@ proc updateAttackedSquares(self: ChessBoard) =
|
||||||
self.updateKnightAttacks()
|
self.updateKnightAttacks()
|
||||||
# Kings
|
# Kings
|
||||||
self.updateKingAttacks()
|
self.updateKingAttacks()
|
||||||
# Invalidate the cache whenever updates to the
|
|
||||||
# metadata are made
|
|
||||||
#self.invalidateCache()
|
|
||||||
|
|
||||||
|
|
||||||
proc removePiece(self: ChessBoard, location: Location, attack: bool = true) =
|
proc removePiece(self: ChessBoard, location: Location, attack: bool = true) =
|
||||||
|
@ -1361,9 +1343,7 @@ proc movePiece(self: ChessBoard, move: Move, attack: bool = true) =
|
||||||
self.grid[move.targetSquare.row, move.targetSquare.col] = piece
|
self.grid[move.targetSquare.row, move.targetSquare.col] = piece
|
||||||
if attack:
|
if attack:
|
||||||
self.updateAttackedSquares()
|
self.updateAttackedSquares()
|
||||||
#else:
|
|
||||||
# Just to be sure
|
|
||||||
# self.invalidateCache()
|
|
||||||
|
|
||||||
|
|
||||||
proc movePiece(self: ChessBoard, startSquare, targetSquare: Location, attack: bool = true) =
|
proc movePiece(self: ChessBoard, startSquare, targetSquare: Location, attack: bool = true) =
|
||||||
|
@ -1371,20 +1351,13 @@ proc movePiece(self: ChessBoard, startSquare, targetSquare: Location, attack: bo
|
||||||
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare), attack)
|
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare), attack)
|
||||||
|
|
||||||
|
|
||||||
#[func invalidateCache(self: ChessBoard) {.inline.} =
|
|
||||||
## Invalidates the internal caches
|
|
||||||
self.cache.canCastle.white.valid = false
|
|
||||||
self.cache.canCastle.black.valid = false
|
|
||||||
self.cache.inCheck.white.valid = false
|
|
||||||
self.cache.inCheck.black.valid = false]#
|
|
||||||
|
|
||||||
|
|
||||||
proc doMove(self: ChessBoard, move: Move) =
|
proc doMove(self: ChessBoard, move: Move) =
|
||||||
## Internal function called by makeMove after
|
## Internal function called by makeMove after
|
||||||
## performing legality checks on the given move. Can
|
## performing legality checks. Can be used in
|
||||||
## be used in performance-critical paths where
|
## performance-critical paths where a move is
|
||||||
## a move is already known to be legal
|
## already known to be legal
|
||||||
|
|
||||||
|
|
||||||
# Record final position for future reference
|
# Record final position for future reference
|
||||||
self.positions.add(self.position)
|
self.positions.add(self.position)
|
||||||
|
@ -1392,17 +1365,28 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
# Final checks
|
# Final checks
|
||||||
let piece = self.grid[move.startSquare.row, move.startSquare.col]
|
let piece = self.grid[move.startSquare.row, move.startSquare.col]
|
||||||
|
|
||||||
# Needed to detect draw by the 50 move rule
|
|
||||||
var
|
var
|
||||||
halfMoveClock = self.position.halfMoveClock
|
halfMoveClock = self.position.halfMoveClock
|
||||||
fullMoveCount = self.position.fullMoveCount
|
fullMoveCount = self.position.fullMoveCount
|
||||||
castlingAvailable = self.position.castlingAvailable
|
castlingAvailable = self.position.castlingAvailable
|
||||||
|
enPassantTarget = self.getEnPassantTarget()
|
||||||
|
# Needed to detect draw by the 50 move rule
|
||||||
if piece.kind == Pawn or move.flag == Capture:
|
if piece.kind == Pawn or move.flag == Capture:
|
||||||
halfMoveClock = 0
|
halfMoveClock = 0
|
||||||
else:
|
else:
|
||||||
inc(halfMoveClock)
|
inc(halfMoveClock)
|
||||||
if piece.color == Black:
|
if piece.color == Black:
|
||||||
inc(fullMoveCount)
|
inc(fullMoveCount)
|
||||||
|
|
||||||
|
# En passant check
|
||||||
|
if enPassantTarget != emptyLocation():
|
||||||
|
let enPassantPawn = enPassantTarget + piece.color.topSide()
|
||||||
|
if self.grid[enPassantPawn.row, enPassantPawn.col].color == piece.color.opposite():
|
||||||
|
enPassantTarget = emptyLocation()
|
||||||
|
|
||||||
|
if move.flag == DoublePush:
|
||||||
|
enPassantTarget = move.targetSquare + piece.color.bottomSide()
|
||||||
|
|
||||||
# Castling check: have the rooks moved?
|
# Castling check: have the rooks moved?
|
||||||
if piece.kind == Rook:
|
if piece.kind == Rook:
|
||||||
case piece.color:
|
case piece.color:
|
||||||
|
@ -1462,12 +1446,11 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||||
halfMoveClock: halfMoveClock,
|
halfMoveClock: halfMoveClock,
|
||||||
fullMoveCount: fullMoveCount,
|
fullMoveCount: fullMoveCount,
|
||||||
#captured: if move.flag == Capture: self.grid[move.targetSquare.row, move.targetSquare.col] else: emptyPiece(),
|
|
||||||
turn: self.getActiveColor().opposite,
|
turn: self.getActiveColor().opposite,
|
||||||
castlingAvailable: castlingAvailable,
|
castlingAvailable: castlingAvailable,
|
||||||
move: move,
|
move: move,
|
||||||
pieces: self.position.pieces,
|
pieces: self.position.pieces,
|
||||||
enPassantSquare: if move.flag == EnPassant: move.targetSquare + piece.color.bottomSide() else: emptyLocation()
|
enPassantSquare: enPassantTarget
|
||||||
)
|
)
|
||||||
# Update position metadata
|
# Update position metadata
|
||||||
|
|
||||||
|
@ -1489,7 +1472,6 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
|
|
||||||
if move.flag == Capture:
|
if move.flag == Capture:
|
||||||
# Get rid of captured pieces
|
# Get rid of captured pieces
|
||||||
#self.position.captured = self.grid[move.targetSquare.row, move.targetSquare.col]
|
|
||||||
self.removePiece(move.targetSquare, attack=false)
|
self.removePiece(move.targetSquare, attack=false)
|
||||||
|
|
||||||
if move.flag == EnPassant:
|
if move.flag == EnPassant:
|
||||||
|
@ -1979,8 +1961,4 @@ when isMainModule:
|
||||||
testPiece(b.getPiece("d8"), Queen, Black)
|
testPiece(b.getPiece("d8"), Queen, Black)
|
||||||
setControlCHook(proc () {.noconv.} = quit(0))
|
setControlCHook(proc () {.noconv.} = quit(0))
|
||||||
|
|
||||||
|
|
||||||
#b = newChessboardFromFEN("r3k2r/Pppp1ppp/1b3nbN/nP6/BBPPP3/q4N2/Pp4PP/R2Q1RK1 b kq d3 0 1")
|
|
||||||
#let m = Move(startSquare: "c7".algebraicToLocation, targetSquare: "c5".algebraicToLocation, flag: DoublePush)
|
|
||||||
#b.makeMove()
|
|
||||||
quit(main())
|
quit(main())
|
|
@ -62,7 +62,10 @@ def main(args: Namespace) -> int:
|
||||||
positions["stockfish"][move] = int(nodes)
|
positions["stockfish"][move] = int(nodes)
|
||||||
for (source, target, promotion, nodes) in pattern.findall(nimfish_output):
|
for (source, target, promotion, nodes) in pattern.findall(nimfish_output):
|
||||||
move = f"{source}{target}{promotion}"
|
move = f"{source}{target}{promotion}"
|
||||||
|
if move in positions["all"]:
|
||||||
positions["all"][move].append(int(nodes))
|
positions["all"][move].append(int(nodes))
|
||||||
|
else:
|
||||||
|
positions["all"][move] = [int(nodes)]
|
||||||
positions["nimfish"][move] = int(nodes)
|
positions["nimfish"][move] = int(nodes)
|
||||||
|
|
||||||
missing = {
|
missing = {
|
||||||
|
|
Loading…
Reference in New Issue