Fix bugs in move handling
This commit is contained in:
parent
fcbe15f275
commit
0496047164
|
@ -35,7 +35,7 @@ type
|
|||
## A chess position
|
||||
|
||||
# Castling metadata. Updated on every move
|
||||
castling: array[64, uint8]
|
||||
castlingRights: array[64, uint8]
|
||||
# Number of half-moves that were performed
|
||||
# to reach this position starting from the
|
||||
# root of the tree
|
||||
|
@ -81,8 +81,8 @@ proc pretty*(self: ChessBoard): string
|
|||
proc spawnPiece(self: ChessBoard, square: Square, piece: Piece)
|
||||
proc toFEN*(self: ChessBoard): string
|
||||
proc unmakeMove*(self: ChessBoard)
|
||||
proc movePiece(self: ChessBoard, move: Move, attack: bool = true)
|
||||
proc removePiece(self: ChessBoard, square: Square, attack: bool = true)
|
||||
proc movePiece(self: ChessBoard, move: Move)
|
||||
proc removePiece(self: ChessBoard, square: Square)
|
||||
|
||||
|
||||
proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} =
|
||||
|
@ -163,6 +163,7 @@ proc newChessboard: ChessBoard =
|
|||
new(result)
|
||||
for i in 0..63:
|
||||
result.grid[i] = nullPiece()
|
||||
result.currPos = -1
|
||||
result.position = Position(enPassantSquare: nullSquare(), turn: White)
|
||||
|
||||
# Indexing operations
|
||||
|
@ -326,18 +327,18 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
|
|||
discard
|
||||
of 'K':
|
||||
discard
|
||||
# result.position.castlingAvailable.white.king = true
|
||||
# result.position.castlingRightsAvailable.white.king = true
|
||||
of 'Q':
|
||||
discard
|
||||
# result.position.castlingAvailable.white.queen = true
|
||||
# result.position.castlingRightsAvailable.white.queen = true
|
||||
of 'k':
|
||||
discard
|
||||
# result.position.castlingAvailable.black.king = true
|
||||
# result.position.castlingRightsAvailable.black.king = true
|
||||
of 'q':
|
||||
discard
|
||||
# result.position.castlingAvailable.black.queen = true
|
||||
# result.position.castlingRightsAvailable.black.queen = true
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castling availability section")
|
||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||
of 3:
|
||||
# En passant target square
|
||||
case c:
|
||||
|
@ -472,7 +473,7 @@ func isCastling*(move: Move): bool {.inline.} =
|
|||
|
||||
|
||||
func getCastlingType*(move: Move): MoveFlag {.inline.} =
|
||||
## Returns the castling type of the given move.
|
||||
## Returns the castlingRights type of the given move.
|
||||
## The return value of this function is only valid
|
||||
## if isCastling() returns true
|
||||
for flag in [CastleLong, CastleShort]:
|
||||
|
@ -644,7 +645,7 @@ proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
|
|||
for square in pawns.forwardRelativeTo(sideToMove) and allowedSquares:
|
||||
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square))
|
||||
# Double push
|
||||
let rank = sideToMove.getFirstRank() # Only pawns on their starting rank can double push
|
||||
let rank = if sideToMove == White: getRankMask(6) else: getRankMask(1) # Only pawns on their starting rank can double push
|
||||
for square in (pawns and rank).doubleForwardRelativeTo(sideToMove) and allowedSquares:
|
||||
moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush))
|
||||
|
||||
|
@ -964,8 +965,8 @@ proc removePieceFromBitboard(self: ChessBoard, square: Square) =
|
|||
self.position.pieces.white.queens.uint64.clearBit(square.int8)
|
||||
of King:
|
||||
self.position.pieces.white.king.uint64.clearBit(square.int8)
|
||||
else:
|
||||
discard
|
||||
of Empty:
|
||||
doAssert false, &"cannot remove empty white piece from {square}"
|
||||
of Black:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
|
@ -979,11 +980,11 @@ proc removePieceFromBitboard(self: ChessBoard, square: Square) =
|
|||
of Queen:
|
||||
self.position.pieces.black.queens.uint64.clearBit(square.int8)
|
||||
of King:
|
||||
self.position.pieces.black.king.uint64.clearBit(square.int8)
|
||||
else:
|
||||
discard
|
||||
self.position.pieces.black.king.uint64.clearBit(square.int8)
|
||||
of Empty:
|
||||
doAssert false, &"cannot remove empty black piece from {square}"
|
||||
else:
|
||||
discard
|
||||
doAssert false, &"cannot remove empty piece from colorless square {square}"
|
||||
|
||||
|
||||
proc addPieceToBitboard(self: ChessBoard, square: Square, piece: Piece) =
|
||||
|
@ -1026,73 +1027,16 @@ proc addPieceToBitboard(self: ChessBoard, square: Square, piece: Piece) =
|
|||
discard
|
||||
|
||||
|
||||
proc removePiece(self: ChessBoard, square: Square, attack: bool = true) =
|
||||
proc removePiece(self: ChessBoard, square: Square) =
|
||||
## Removes a piece from the board, updating necessary
|
||||
## metadata
|
||||
var piece = self.grid[square]
|
||||
self.grid[square] = nullPiece()
|
||||
doAssert piece.kind != Empty and piece.color != None
|
||||
self.removePieceFromBitboard(square)
|
||||
#[if attack:
|
||||
self.updateAttackedSquares()]#
|
||||
self.grid[square] = nullPiece()
|
||||
|
||||
|
||||
proc updateMovepieces(self: ChessBoard, move: Move) =
|
||||
## Updates our bitboard representation after a move: note that this
|
||||
## does *not* handle captures, en passant, promotions etc. as those
|
||||
## are already called by helpers such as removePiece() and spawnPiece()
|
||||
var bitboard: uint64
|
||||
let piece = self.grid[move.startSquare]
|
||||
# TODO: Should we use our helpers or is it faster to branch only once?
|
||||
case piece.color:
|
||||
of White:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
self.position.pieces.white.pawns.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.white.pawns.uint64.clearBit(move.startSquare.int8)
|
||||
of Bishop:
|
||||
self.position.pieces.white.bishops.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.white.bishops.uint64.clearBit(move.startSquare.int8)
|
||||
of Knight:
|
||||
self.position.pieces.white.knights.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.white.knights.uint64.clearBit(move.startSquare.int8)
|
||||
of Rook:
|
||||
self.position.pieces.white.rooks.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.white.rooks.uint64.clearBit(move.startSquare.int8)
|
||||
of Queen:
|
||||
self.position.pieces.white.queens.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.white.queens.uint64.clearBit(move.startSquare.int8)
|
||||
of King:
|
||||
self.position.pieces.white.king.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.white.king.uint64.clearBit(move.startSquare.int8)
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
self.position.pieces.black.pawns.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.black.pawns.uint64.clearBit(move.startSquare.int8)
|
||||
of Bishop:
|
||||
self.position.pieces.black.bishops.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.black.bishops.uint64.clearBit(move.startSquare.int8)
|
||||
of Knight:
|
||||
self.position.pieces.black.knights.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.black.knights.uint64.clearBit(move.startSquare.int8)
|
||||
of Rook:
|
||||
self.position.pieces.black.rooks.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.black.rooks.uint64.clearBit(move.startSquare.int8)
|
||||
of Queen:
|
||||
self.position.pieces.black.queens.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.black.queens.uint64.clearBit(move.startSquare.int8)
|
||||
of King:
|
||||
self.position.pieces.black.king.uint64.setBit(move.targetSquare.int8)
|
||||
self.position.pieces.black.king.uint64.clearBit(move.startSquare.int8)
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc movePiece(self: ChessBoard, move: Move, attack: bool = true) =
|
||||
proc movePiece(self: ChessBoard, move: Move) =
|
||||
## Internal helper to move a piece. If attack
|
||||
## is set to false, then this function does
|
||||
## not update attacked squares metadata, just
|
||||
|
@ -1100,20 +1044,15 @@ proc movePiece(self: ChessBoard, move: Move, attack: bool = true) =
|
|||
let piece = self.grid[move.startSquare]
|
||||
let targetSquare = self.getPiece(move.targetSquare)
|
||||
if targetSquare.color != None:
|
||||
raise newException(AccessViolationDefect, &"attempted to overwrite a piece! {move}")
|
||||
raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}")
|
||||
# Update positional metadata
|
||||
self.updateMovePieces(move)
|
||||
# Empty out the starting square
|
||||
self.grid[move.startSquare] = nullPiece()
|
||||
# Actually move the piece on the board
|
||||
self.grid[move.targetSquare] = piece
|
||||
#[if attack:
|
||||
self.updateAttackedSquares()]#
|
||||
self.removePiece(move.startSquare)
|
||||
self.spawnPiece(move.targetSquare, piece)
|
||||
|
||||
|
||||
proc movePiece(self: ChessBoard, startSquare, targetSquare: Square, attack: bool = true) =
|
||||
proc movePiece(self: ChessBoard, startSquare, targetSquare: Square) =
|
||||
## Like the other movePiece(), but with two squares
|
||||
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare), attack)
|
||||
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare))
|
||||
|
||||
|
||||
proc doMove(self: ChessBoard, move: Move) =
|
||||
|
@ -1124,14 +1063,16 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
|
||||
# Record final position for future reference
|
||||
self.positions.add(self.position)
|
||||
inc(self.currPos)
|
||||
|
||||
# Final checks
|
||||
let piece = self.grid[move.startSquare]
|
||||
doAssert piece.kind != Empty and piece.color != None
|
||||
|
||||
var
|
||||
halfMoveClock = self.position.halfMoveClock
|
||||
fullMoveCount = self.position.fullMoveCount
|
||||
castling = self.position.castling
|
||||
castlingRights = self.position.castlingRights
|
||||
enPassantTarget = nullSquare()
|
||||
# Needed to detect draw by the 50 move rule
|
||||
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
|
||||
|
@ -1144,69 +1085,26 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
if move.isDoublePush():
|
||||
enPassantTarget = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
|
||||
|
||||
# Castling check: have the rooks moved?
|
||||
if piece.kind == Rook:
|
||||
discard
|
||||
# case piece.color:
|
||||
# of White:
|
||||
# if rowFromSquare(move.startSquare) == piece.getStartRank():
|
||||
# if columnFromSquare(move.startSquare) == 0:
|
||||
# # Queen side
|
||||
# castlingAvailable.white.queen = false
|
||||
# elif columnfromSquare(move.startSquare) == 7:
|
||||
# # King side
|
||||
# castlingAvailable.white.king = false
|
||||
# of Black:
|
||||
# if rowFromSquare(move.startSquare) == piece.getStartRank():
|
||||
# if columnFromSquare(move.startSquare) == 0:
|
||||
# # Queen side
|
||||
# castlingAvailable.black.queen = false
|
||||
# elif columnFromSquare(move.startSquare) == 7:
|
||||
# # King side
|
||||
# castlingAvailable.black.king = false
|
||||
# else:
|
||||
# discard
|
||||
# Has a rook been captured?
|
||||
|
||||
|
||||
# Create new position
|
||||
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||
halfMoveClock: halfMoveClock,
|
||||
fullMoveCount: fullMoveCount,
|
||||
turn: self.getSideToMove().opposite,
|
||||
castling: castling,
|
||||
castlingRights: castlingRights,
|
||||
enPassantSquare: enPassantTarget,
|
||||
pieces: self.position.pieces
|
||||
)
|
||||
# Update position metadata
|
||||
|
||||
if move.isCastling():
|
||||
# Move the rook onto the
|
||||
# correct file when castling
|
||||
var
|
||||
square: Square
|
||||
target: Square
|
||||
flag: MoveFlag
|
||||
if move.getCastlingType() == CastleShort:
|
||||
square = piece.color.kingSideRook()
|
||||
target = shortCastleRook(piece.color)
|
||||
flag = CastleShort
|
||||
else:
|
||||
square = piece.color.queenSideRook()
|
||||
target = longCastleRook(piece.color)
|
||||
flag = CastleLong
|
||||
let rook = self.grid[square]
|
||||
self.movePiece(createMove(square, target, flag), attack=false)
|
||||
|
||||
if move.isEnPassant():
|
||||
# Make the en passant pawn disappear
|
||||
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare(), attack=false)
|
||||
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare())
|
||||
|
||||
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)
|
||||
self.removePiece(move.targetSquare)
|
||||
# Move the piece to its target square
|
||||
self.movePiece(move)
|
||||
if move.isPromotion():
|
||||
# Move is a pawn promotion: get rid of the pawn
|
||||
# and spawn a new piece
|
||||
|
@ -1223,7 +1121,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
#self.updateAttackedSquares()
|
||||
self.updateBoard()
|
||||
|
||||
|
||||
proc spawnPiece(self: ChessBoard, square: Square, piece: Piece) =
|
||||
|
@ -1261,26 +1159,18 @@ proc updateBoard*(self: ChessBoard) =
|
|||
self.grid[sq] = Piece(color: White, kind: Queen)
|
||||
for sq in self.position.pieces.black.queens:
|
||||
self.grid[sq] = Piece(color: Black, kind: Queen)
|
||||
self.grid[self.position.pieces.white.king.toSquare()] = Piece(color: White, kind: King)
|
||||
self.grid[self.position.pieces.black.king.toSquare()] = Piece(color: Black, kind: King)
|
||||
for sq in self.position.pieces.white.king:
|
||||
self.grid[sq] = Piece(color: White, kind: King)
|
||||
for sq in self.position.pieces.black.king:
|
||||
self.grid[sq] = Piece(color: Black, kind: King)
|
||||
|
||||
|
||||
proc unmakeMove*(self: ChessBoard) =
|
||||
## Reverts to the previous board position,
|
||||
## if one exists
|
||||
if self.currPos > 0:
|
||||
if self.currPos >= 0:
|
||||
self.position = self.positions[self.currPos]
|
||||
dec(self.currPos)
|
||||
self.position = self.positions[self.currPos]
|
||||
self.updateBoard()
|
||||
|
||||
|
||||
proc redoMove*(self: ChessBoard) =
|
||||
## Reverts to the next board position, if one
|
||||
## exists. Only makes sense after a call to
|
||||
## unmakeMove
|
||||
if self.positions.high() > self.currPos:
|
||||
inc(self.currPos)
|
||||
self.position = self.positions[self.currPos]
|
||||
self.updateBoard()
|
||||
|
||||
|
||||
|
@ -1386,7 +1276,6 @@ proc pretty*(self: ChessBoard): string =
|
|||
result &= "\x1b[0m"
|
||||
|
||||
|
||||
|
||||
proc toFEN*(self: ChessBoard): string =
|
||||
## Returns a FEN string of the current
|
||||
## position in the chessboard
|
||||
|
@ -1413,8 +1302,8 @@ proc toFEN*(self: ChessBoard): string =
|
|||
result &= " "
|
||||
# Castling availability
|
||||
result &= "-"
|
||||
# let castleWhite = self.position.castlingAvailable.white
|
||||
# let castleBlack = self.position.castlingAvailable.black
|
||||
# let castleWhite = self.position.castlingRightsAvailable.white
|
||||
# let castleBlack = self.position.castlingRightsAvailable.black
|
||||
# if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||
# result &= "-"
|
||||
# else:
|
||||
|
@ -1623,13 +1512,13 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discarda
|
|||
|
||||
# Since the user tells us just the source and target square of the move,
|
||||
# we have to figure out all the flags by ourselves (whether it's a double
|
||||
# push, a capture, a promotion, castling, etc.)
|
||||
# push, a capture, a promotion, etc.)
|
||||
|
||||
if board.grid[targetSquare].kind != Empty:
|
||||
flags.add(Capture)
|
||||
|
||||
#elif board.grid[startSquare].kind == Pawn and abs(rowFromSquare(startSquare) - rowFromSquare(targetSquare)) == 2:
|
||||
# flags.add(DoublePush)
|
||||
elif board.grid[startSquare].kind == Pawn and abs(rankFromSquare(startSquare) - rankFromSquare(targetSquare)) == 2:
|
||||
flags.add(DoublePush)
|
||||
|
||||
if len(moveString) == 5:
|
||||
# Promotion
|
||||
|
@ -1759,7 +1648,7 @@ const HELP_TEXT = """Nimfish help menu:
|
|||
- position fen "..." moves a2a3 a7a6
|
||||
- clear: Clear the screen
|
||||
- move <move>: Perform the given move in algebraic notation
|
||||
- castle: Print castling rights for each side
|
||||
- castle: Print castlingRights rights for each side
|
||||
- check: Print if the current side to move is in check
|
||||
- undo, u: Undoes the last move. Can be used in succession
|
||||
- turn: Print which side is to move
|
||||
|
@ -1836,7 +1725,7 @@ proc main: int =
|
|||
echo board.getPiece(cmd[1])
|
||||
except ValueError:
|
||||
echo "error: get: invalid square"
|
||||
continue
|
||||
continue
|
||||
of "castle":
|
||||
let canCastle = board.canCastle(board.getSideToMove())
|
||||
echo &"Castling rights for {($board.getSideToMove()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
|
|
|
@ -240,7 +240,7 @@ func computeKingBitboards: array[64, Bitboard] =
|
|||
movements = movements or king.leftRelativeTo(White)
|
||||
movements = movements or king.rightRelativeTo(White)
|
||||
movements = movements or king.backwardRelativeTo(White)
|
||||
movements = movements or king.forwardLeftRelativeTo(White)
|
||||
movements = movements or king.forwardRightRelativeTo(White)
|
||||
movements = movements or king.backwardRightRelativeTo(White)
|
||||
movements = movements or king.backwardLeftRelativeTo(White)
|
||||
# We don't *need* to mask the king off: the engine already masks off
|
||||
|
@ -265,6 +265,7 @@ func computeKnightBitboards: array[64, Bitboard] =
|
|||
movements = movements or knight.shortKnightDownRightRelativeTo(White)
|
||||
movements = movements or knight.shortKnightUpLeftRelativeTo(White)
|
||||
movements = movements or knight.shortKnightUpRightRelativeTo(White)
|
||||
movements = movements and not knight
|
||||
result[i] = movements
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue