More movegen bug fixes (close!)
This commit is contained in:
parent
89a96eaf52
commit
54a6217bd3
|
@ -76,7 +76,7 @@ type
|
||||||
|
|
||||||
Position* = ref object
|
Position* = ref object
|
||||||
## A chess position
|
## A chess position
|
||||||
# Did the rooks on either side/the king move?
|
# Did the rooks on either side or the king move?
|
||||||
castlingAvailable: tuple[white, black: tuple[queen, king: bool]]
|
castlingAvailable: tuple[white, black: tuple[queen, king: bool]]
|
||||||
# Number of half-moves that were performed
|
# Number of half-moves that were performed
|
||||||
# to reach this position starting from the
|
# to reach this position starting from the
|
||||||
|
@ -95,7 +95,7 @@ type
|
||||||
pieces: tuple[white: Pieces, black: Pieces]
|
pieces: tuple[white: Pieces, black: Pieces]
|
||||||
# Squares attacked by both sides
|
# Squares attacked by both sides
|
||||||
attacked: tuple[white: Attacked, black: Attacked]
|
attacked: tuple[white: Attacked, black: Attacked]
|
||||||
# Pieces pinned by both sides
|
# Pieces pinned by both sides (only absolute pins)
|
||||||
pinned: tuple[white: Attacked, black: Attacked]
|
pinned: tuple[white: Attacked, black: Attacked]
|
||||||
# Active color
|
# Active color
|
||||||
turn: PieceColor
|
turn: PieceColor
|
||||||
|
@ -105,14 +105,14 @@ type
|
||||||
|
|
||||||
# The actual board where pieces live
|
# The actual board where pieces live
|
||||||
# (flattened 8x8 matrix)
|
# (flattened 8x8 matrix)
|
||||||
grid: seq[Piece]
|
grid: array[64, Piece]
|
||||||
# The current position
|
# The current position
|
||||||
position: Position
|
position: Position
|
||||||
# List of all previously reached positions
|
# List of all previously reached positions
|
||||||
positions: seq[Position]
|
positions: seq[Position]
|
||||||
|
|
||||||
|
|
||||||
# A bunch of simple utility functions
|
# 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)
|
||||||
|
@ -145,7 +145,7 @@ proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} =
|
||||||
for x in other:
|
for x in other:
|
||||||
self.add(x)
|
self.add(x)
|
||||||
|
|
||||||
proc resetBoard*(self: ChessBoard)
|
proc updateBoard*(self: ChessBoard)
|
||||||
|
|
||||||
# Due to our board layout, directions of movement are reversed for white and 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 everywhere
|
# we need these helpers to avoid going mad with integer tuples and minus signs everywhere
|
||||||
|
@ -290,10 +290,8 @@ func getLastRow(color: PieceColor): int {.inline.} =
|
||||||
proc newChessboard: ChessBoard =
|
proc newChessboard: ChessBoard =
|
||||||
## Returns a new, empty chessboard
|
## Returns a new, empty chessboard
|
||||||
new(result)
|
new(result)
|
||||||
# Turns our flat sequence into an 8x8 grid
|
for i in 0..63:
|
||||||
result.grid = newSeqOfCap[Piece](64)
|
result.grid[i] = emptyPiece()
|
||||||
for _ in 0..63:
|
|
||||||
result.grid.add(emptyPiece())
|
|
||||||
result.position = Position(attacked: (@[], @[]),
|
result.position = Position(attacked: (@[], @[]),
|
||||||
enPassantSquare: emptyLocation(),
|
enPassantSquare: emptyLocation(),
|
||||||
turn: White,
|
turn: White,
|
||||||
|
@ -313,10 +311,10 @@ proc newChessboard: ChessBoard =
|
||||||
|
|
||||||
|
|
||||||
func coordToIndex(row, col: int): int {.inline.} = (row * 8) + col
|
func coordToIndex(row, col: int): int {.inline.} = (row * 8) + col
|
||||||
func `[]`(self: seq[Piece], row, column: Natural): Piece {.inline.} = self[coordToIndex(row, column)]
|
func `[]`(self: array[64, Piece], row, column: Natural): Piece {.inline.} = self[coordToIndex(row, column)]
|
||||||
proc `[]=`(self: var seq[Piece], row, column: Natural, piece: Piece) {.inline.} = self[coordToIndex(row, column)] = piece
|
proc `[]=`(self: var array[64, Piece], row, column: Natural, piece: Piece) {.inline.} = self[coordToIndex(row, column)] = piece
|
||||||
func `[]`(self: seq[Piece], loc: Location): Piece {.inline.} = self[loc.row, loc.col]
|
func `[]`(self: array[64, Piece], loc: Location): Piece {.inline.} = self[loc.row, loc.col]
|
||||||
proc `[]=`(self: var seq[Piece], loc: Location, piece: Piece) {.inline.} = self[loc.row, loc.col] = piece
|
proc `[]=`(self: var array[64, Piece], loc: Location, piece: Piece) {.inline.} = self[loc.row, loc.col] = piece
|
||||||
|
|
||||||
|
|
||||||
proc newChessboardFromFEN*(fen: string): ChessBoard =
|
proc newChessboardFromFEN*(fen: string): ChessBoard =
|
||||||
|
@ -535,10 +533,10 @@ func rankToColumn(rank: int): int8 {.inline.} =
|
||||||
return indeces[rank - 1]
|
return indeces[rank - 1]
|
||||||
|
|
||||||
|
|
||||||
func rowToFile(row: int): int {.inline.} =
|
func rowToFile(row: int): int8 {.inline.} =
|
||||||
## Converts a row into our grid into
|
## Converts a row into our grid into
|
||||||
## a chess file
|
## a chess file
|
||||||
const indeces = [8, 7, 6, 5, 4, 3, 2, 1]
|
const indeces: array[8, int8] = [8, 7, 6, 5, 4, 3, 2, 1]
|
||||||
return indeces[row]
|
return indeces[row]
|
||||||
|
|
||||||
|
|
||||||
|
@ -641,7 +639,9 @@ func getFlags*(move: Move): seq[MoveFlag] =
|
||||||
result.add(Default)
|
result.add(Default)
|
||||||
|
|
||||||
|
|
||||||
func getKing(self: ChessBoard, color: PieceColor): Location {.inline.} =
|
func getKing(self: ChessBoard, color: PieceColor = None): Location {.inline.} =
|
||||||
|
## Returns the location of the king for the given
|
||||||
|
## color (if it is None, the active color is used)
|
||||||
var color = color
|
var color = color
|
||||||
if color == None:
|
if color == None:
|
||||||
color = self.getActiveColor()
|
color = self.getActiveColor()
|
||||||
|
@ -803,7 +803,8 @@ proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
||||||
let
|
let
|
||||||
attacker = attackers[0]
|
attacker = attackers[0]
|
||||||
attackerPiece = self.grid[attacker.row, attacker.col]
|
attackerPiece = self.grid[attacker.row, attacker.col]
|
||||||
attack = self.getAttackFor(attacker, king)
|
|
||||||
|
var attack = self.getAttackFor(attacker, king)
|
||||||
# Capturing the piece resolves the check
|
# Capturing the piece resolves the check
|
||||||
result.add(attacker)
|
result.add(attacker)
|
||||||
# Blocking the attack is also a viable strategy
|
# Blocking the attack is also a viable strategy
|
||||||
|
@ -940,9 +941,15 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
if otherPiece.color == piece.color:
|
if otherPiece.color == piece.color:
|
||||||
break
|
break
|
||||||
if checked and square notin resolutions:
|
if checked and square notin resolutions:
|
||||||
# We don't break out of the loop because
|
# We don't always break out of the loop because
|
||||||
# we might resolve the check later
|
# we might resolve the check later
|
||||||
continue
|
if otherPiece.color == None:
|
||||||
|
# We can still move in this direction, so maybe
|
||||||
|
# the check can be resolved later
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Our movement is blocked, switch to next direction
|
||||||
|
break
|
||||||
if otherPiece.color == piece.color.opposite:
|
if otherPiece.color == piece.color.opposite:
|
||||||
# Target square contains an enemy piece: capture
|
# Target square contains an enemy piece: capture
|
||||||
# it and stop going any further
|
# it and stop going any further
|
||||||
|
@ -950,7 +957,7 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
# Can't capture the king
|
# Can't capture the king
|
||||||
result.add(Move(startSquare: location, targetSquare: square, flags: Capture.uint16))
|
result.add(Move(startSquare: location, targetSquare: square, flags: Capture.uint16))
|
||||||
break
|
break
|
||||||
# Target square is empty
|
# Target square is empty, keep going
|
||||||
result.add(Move(startSquare: location, targetSquare: square))
|
result.add(Move(startSquare: location, targetSquare: square))
|
||||||
|
|
||||||
|
|
||||||
|
@ -1174,11 +1181,11 @@ proc updatePawnAttacks(self: ChessBoard) =
|
||||||
# squares they can move to do not match the squares
|
# squares they can move to do not match the squares
|
||||||
# they can capture on. Sneaky fucks)
|
# they can capture on. Sneaky fucks)
|
||||||
self.addAttack((loc, loc + White.topRightDiagonal(), White.topRightDiagonal()), White)
|
self.addAttack((loc, loc + White.topRightDiagonal(), White.topRightDiagonal()), White)
|
||||||
self.addAttack((loc, loc + White.topLeftDiagonal(), White.topRightDiagonal()), White)
|
self.addAttack((loc, loc + White.topLeftDiagonal(), White.topLeftDiagonal()), White)
|
||||||
# We do the same thing for black
|
# We do the same thing for black
|
||||||
for loc in self.position.pieces.black.pawns:
|
for loc in self.position.pieces.black.pawns:
|
||||||
self.addAttack((loc, loc + Black.topRightDiagonal(), Black.topRightDiagonal()), Black)
|
self.addAttack((loc, loc + Black.topRightDiagonal(), Black.topRightDiagonal()), Black)
|
||||||
self.addAttack((loc, loc + Black.topLeftDiagonal(), Black.topRightDiagonal()), Black)
|
self.addAttack((loc, loc + Black.topLeftDiagonal(), Black.topLeftDiagonal()), Black)
|
||||||
|
|
||||||
|
|
||||||
proc updateKingAttacks(self: ChessBoard) =
|
proc updateKingAttacks(self: ChessBoard) =
|
||||||
|
@ -1258,7 +1265,7 @@ proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked
|
||||||
# We found an enemy piece that is not
|
# We found an enemy piece that is not
|
||||||
# the enemy king. We don't break out
|
# the enemy king. We don't break out
|
||||||
# immediately because we first want
|
# immediately because we first want
|
||||||
# to check if we've pinned a piece
|
# to check if we've pinned it to the king
|
||||||
var
|
var
|
||||||
otherSquare: Location = square
|
otherSquare: Location = square
|
||||||
behindPiece: Piece
|
behindPiece: Piece
|
||||||
|
@ -1274,6 +1281,13 @@ proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked
|
||||||
# this axis in both directions
|
# this axis in both directions
|
||||||
result.pins.add((loc, square, direction))
|
result.pins.add((loc, square, direction))
|
||||||
result.pins.add((loc, square, -direction))
|
result.pins.add((loc, square, -direction))
|
||||||
|
if otherPiece.kind == Pawn and square.row == otherPiece.getStartRow():
|
||||||
|
# The pinned piece is a pawn which hasn't moved yet:
|
||||||
|
# we allow it to move two squares as well
|
||||||
|
if square.col == loc.col:
|
||||||
|
# The pawn can only push two squares if it's being pinned from the
|
||||||
|
# top
|
||||||
|
result.pins.add((loc, square, otherPiece.color.doublePush()))
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -1332,12 +1346,11 @@ proc updateAttackedSquares(self: ChessBoard) =
|
||||||
self.updateKingAttacks()
|
self.updateKingAttacks()
|
||||||
|
|
||||||
|
|
||||||
proc removePiece(self: ChessBoard, location: Location, attack: bool = true, empty: bool = true) =
|
proc removePiece(self: ChessBoard, location: Location, attack: 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]
|
||||||
if empty:
|
self.grid[location.row, location.col] = emptyPiece()
|
||||||
self.grid[location.row, location.col] = emptyPiece()
|
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of White:
|
of White:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
|
@ -1383,6 +1396,10 @@ proc movePiece(self: ChessBoard, move: Move, attack: bool = true) =
|
||||||
## not update attacked squares metadata, just
|
## not update attacked squares metadata, just
|
||||||
## positional info and the grid itself
|
## positional info and the grid itself
|
||||||
let piece = self.grid[move.startSquare.row, move.startSquare.col]
|
let piece = self.grid[move.startSquare.row, move.startSquare.col]
|
||||||
|
let targetSquare = self.getPiece(move.targetSquare)
|
||||||
|
if targetSquare.color != None:
|
||||||
|
raise newException(AccessViolationDefect, &"attempted to overwrite a piece! {move}")
|
||||||
|
# Update positional metadata
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of White:
|
of White:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
|
@ -1432,7 +1449,7 @@ proc movePiece(self: ChessBoard, move: Move, attack: bool = true) =
|
||||||
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 on the board
|
||||||
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()
|
||||||
|
@ -1459,7 +1476,7 @@ 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
|
||||||
enPassantTarget = self.getEnPassantTarget()
|
enPassantTarget = emptyLocation()
|
||||||
# Needed to detect draw by the 50 move rule
|
# Needed to detect draw by the 50 move rule
|
||||||
if piece.kind == Pawn or move.isCapture():
|
if piece.kind == Pawn or move.isCapture():
|
||||||
halfMoveClock = 0
|
halfMoveClock = 0
|
||||||
|
@ -1467,12 +1484,6 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
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.isDoublePush():
|
if move.isDoublePush():
|
||||||
enPassantTarget = move.targetSquare + piece.color.bottomSide()
|
enPassantTarget = move.targetSquare + piece.color.bottomSide()
|
||||||
|
@ -1566,11 +1577,16 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
if move.isEnPassant():
|
if move.isEnPassant():
|
||||||
# Make the en passant pawn disappear
|
# Make the en passant pawn disappear
|
||||||
self.removePiece(move.targetSquare + piece.color.bottomSide(), attack=false)
|
self.removePiece(move.targetSquare + piece.color.bottomSide(), attack=false)
|
||||||
|
|
||||||
|
|
||||||
|
if move.isCapture():
|
||||||
|
# Get rid of captured pieces
|
||||||
|
self.removePiece(move.targetSquare, attack=false)
|
||||||
|
# Move the piece to its target square and update attack metadata
|
||||||
|
self.movePiece(move, attack=false)
|
||||||
if move.isPromotion():
|
if move.isPromotion():
|
||||||
# Move is a pawn promotion: get rid of the pawn
|
# Move is a pawn promotion: get rid of the pawn
|
||||||
# and spawn a new piece
|
# and spawn a new piece
|
||||||
self.removePiece(move.targetSquare, attack=false)
|
|
||||||
case move.getPromotionType():
|
case move.getPromotionType():
|
||||||
of PromoteToBishop:
|
of PromoteToBishop:
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color))
|
self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color))
|
||||||
|
@ -1582,16 +1598,10 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
|
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
self.updateAttackedSquares()
|
self.updateAttackedSquares()
|
||||||
|
|
||||||
if move.isCapture():
|
|
||||||
# Get rid of captured pieces
|
|
||||||
self.removePiece(move.targetSquare, attack=false, empty=false)
|
|
||||||
# Move the piece to its target square and update attack metadata
|
|
||||||
self.movePiece(move)
|
|
||||||
# TODO: Remove this, once I figure out what the heck is wrong
|
# TODO: Remove this, once I figure out what the heck is wrong
|
||||||
# with updating the board representation
|
# with updating the board representation
|
||||||
self.resetBoard()
|
self.updateBoard()
|
||||||
|
|
||||||
|
|
||||||
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||||
|
@ -1614,7 +1624,7 @@ proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||||
of Queen:
|
of Queen:
|
||||||
self.position.pieces.white.queens.add(location)
|
self.position.pieces.white.queens.add(location)
|
||||||
of King:
|
of King:
|
||||||
self.position.pieces.white.king = location
|
doAssert false, "attempted to spawn a white king"
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
of Black:
|
of Black:
|
||||||
|
@ -1630,7 +1640,7 @@ proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||||
of Queen:
|
of Queen:
|
||||||
self.position.pieces.black.queens.add(location)
|
self.position.pieces.black.queens.add(location)
|
||||||
of King:
|
of King:
|
||||||
self.position.pieces.black.king = location
|
doAssert false, "attempted to spawn a black king"
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
|
@ -1639,8 +1649,8 @@ proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||||
self.grid[location.row, location.col] = piece
|
self.grid[location.row, location.col] = piece
|
||||||
|
|
||||||
|
|
||||||
proc resetBoard*(self: ChessBoard) =
|
proc updateBoard*(self: ChessBoard) =
|
||||||
## Resets the internal grid representation
|
## Updates the internal grid representation
|
||||||
## according to the positional data stored
|
## according to the positional data stored
|
||||||
## in the chessboard
|
## in the chessboard
|
||||||
for i in 0..63:
|
for i in 0..63:
|
||||||
|
@ -1672,7 +1682,7 @@ proc resetBoard*(self: ChessBoard) =
|
||||||
proc undoLastMove*(self: ChessBoard) =
|
proc undoLastMove*(self: ChessBoard) =
|
||||||
if self.positions.len() > 0:
|
if self.positions.len() > 0:
|
||||||
self.position = self.positions.pop()
|
self.position = self.positions.pop()
|
||||||
self.resetBoard()
|
self.updateBoard()
|
||||||
|
|
||||||
|
|
||||||
proc isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
proc isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
||||||
|
@ -2048,7 +2058,7 @@ proc handlePositionCommand(board: var ChessBoard, command: seq[string]) =
|
||||||
return
|
return
|
||||||
# Makes sure we don't leave the board in an invalid state if
|
# Makes sure we don't leave the board in an invalid state if
|
||||||
# some error occurs
|
# some error occurs
|
||||||
var tempBoard = newChessboard()
|
var tempBoard: ChessBoard
|
||||||
case command[1]:
|
case command[1]:
|
||||||
of "startpos":
|
of "startpos":
|
||||||
tempBoard = newDefaultChessboard()
|
tempBoard = newDefaultChessboard()
|
||||||
|
@ -2139,7 +2149,7 @@ const HELP_TEXT = """Nimfish help menu:
|
||||||
- move <move>: Perform the given move in algebraic notation
|
- move <move>: Perform the given move in algebraic notation
|
||||||
- castle: Print castling rights for each side
|
- castle: Print castling rights for each side
|
||||||
- check: Print if the current side to move is in check
|
- check: Print if the current side to move is in check
|
||||||
- undo, u: Undoes the last move that was performed. Can be used in succession
|
- undo, u: Undoes the last move. Can be used in succession
|
||||||
- turn: Print which side is to move
|
- turn: Print which side is to move
|
||||||
- ep: Print the current en passant target
|
- ep: Print the current en passant target
|
||||||
- pretty: Shorthand for "position pretty"
|
- pretty: Shorthand for "position pretty"
|
||||||
|
|
Loading…
Reference in New Issue