Initial work on the move list
This commit is contained in:
parent
ca498ebc42
commit
79477fe077
|
@ -21,11 +21,12 @@ import std/strformat
|
|||
|
||||
type
|
||||
# Useful type aliases
|
||||
Location = tuple[row, col: int]
|
||||
Location* = tuple[row, col: int]
|
||||
|
||||
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
|
||||
bishops: seq[Location], knights: seq[Location],
|
||||
pawns: seq[Location]]
|
||||
Castling* = tuple[white, black: tuple[queen, king: bool]]
|
||||
|
||||
PieceColor* = enum
|
||||
## A piece color enumeration
|
||||
|
@ -65,11 +66,15 @@ type
|
|||
targetSquare*: Location
|
||||
flag*: MoveFlag
|
||||
|
||||
ChessBoard* = ref object
|
||||
## A chess board object
|
||||
grid: Matrix[Piece]
|
||||
# Currently active color
|
||||
turn: PieceColor
|
||||
Position* = ref object
|
||||
## A chess position
|
||||
move: Move
|
||||
# Stores castling metadata
|
||||
castling: Castling
|
||||
# Number of half-moves that were performed
|
||||
# to reach this position starting from the
|
||||
# root of the tree
|
||||
plyFromRoot: int
|
||||
# Number of half moves since
|
||||
# last piece capture or pawn movement.
|
||||
# Used for the 50-move rule
|
||||
|
@ -77,8 +82,6 @@ type
|
|||
# Full move counter. Increments
|
||||
# every 2 ply
|
||||
fullMoveCount: int
|
||||
# Stores metadata for castling.
|
||||
castling: tuple[white, black: tuple[queen, king: bool]]
|
||||
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
||||
# If en passant is not possible, both the row and
|
||||
# column of the position will be set to -1
|
||||
|
@ -87,6 +90,19 @@ type
|
|||
pieces: tuple[white: Pieces, black: Pieces]
|
||||
# Potential attacking moves for black and white
|
||||
attacked: tuple[white: seq[Move], black: seq[Move]]
|
||||
# Has any piece been captured to reach this position?
|
||||
captured: Piece
|
||||
# Active color
|
||||
turn: PieceColor
|
||||
|
||||
|
||||
ChessBoard* = ref object
|
||||
## A chess board object
|
||||
grid: Matrix[Piece]
|
||||
position: Position
|
||||
positionIndex: int
|
||||
# List of reached positions
|
||||
positions: seq[Position]
|
||||
|
||||
|
||||
# Initialized only once, copied every time
|
||||
|
@ -177,7 +193,19 @@ func topRightKnightMove(piece: Piece, long: bool = true): Location {.inline.} =
|
|||
proc getActiveColor*(self: ChessBoard): PieceColor =
|
||||
## Returns the currently active color
|
||||
## (turn of who has to move)
|
||||
return self.turn
|
||||
return self.position.turn
|
||||
|
||||
|
||||
proc getCastlingInformation*(self: ChessBoard): tuple[queen, king: bool] =
|
||||
## Returns whether castling is possible
|
||||
## for the given color
|
||||
case self.getActiveColor():
|
||||
of White:
|
||||
return self.position.castling.white
|
||||
of Black:
|
||||
return self.position.castling.black
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
func getStartRow(piece: Piece): int {.inline.} =
|
||||
|
@ -218,9 +246,12 @@ proc newChessboard: ChessBoard =
|
|||
new(result)
|
||||
# Turns our flat sequence into an 8x8 grid
|
||||
result.grid = newMatrixFromSeq[Piece](empty, (8, 8))
|
||||
result.attacked = (@[], @[])
|
||||
result.enPassantSquare = emptyMove()
|
||||
result.turn = White
|
||||
result.position = Position(attacked: (@[], @[]),
|
||||
enPassantSquare: emptyMove(),
|
||||
move: emptyMove(),
|
||||
turn: White)
|
||||
|
||||
result.positionIndex = 0
|
||||
|
||||
|
||||
proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||
|
@ -261,33 +292,33 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
|||
of Black:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
result.pieces.black.pawns.add((row, column))
|
||||
result.position.pieces.black.pawns.add((row, column))
|
||||
of Bishop:
|
||||
result.pieces.black.bishops.add((row, column))
|
||||
result.position.pieces.black.bishops.add((row, column))
|
||||
of Knight:
|
||||
result.pieces.black.knights.add((row, column))
|
||||
result.position.pieces.black.knights.add((row, column))
|
||||
of Rook:
|
||||
result.pieces.black.rooks.add((row, column))
|
||||
result.position.pieces.black.rooks.add((row, column))
|
||||
of Queen:
|
||||
result.pieces.black.queens.add((row, column))
|
||||
result.position.pieces.black.queens.add((row, column))
|
||||
of King:
|
||||
result.pieces.black.king = (row, column)
|
||||
result.position.pieces.black.king = (row, column)
|
||||
else:
|
||||
discard
|
||||
of White:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
result.pieces.white.pawns.add((row, column))
|
||||
result.position.pieces.white.pawns.add((row, column))
|
||||
of Bishop:
|
||||
result.pieces.white.bishops.add((row, column))
|
||||
result.position.pieces.white.bishops.add((row, column))
|
||||
of Knight:
|
||||
result.pieces.white.knights.add((row, column))
|
||||
result.position.pieces.white.knights.add((row, column))
|
||||
of Rook:
|
||||
result.pieces.white.rooks.add((row, column))
|
||||
result.position.pieces.white.rooks.add((row, column))
|
||||
of Queen:
|
||||
result.pieces.white.queens.add((row, column))
|
||||
result.position.pieces.white.queens.add((row, column))
|
||||
of King:
|
||||
result.pieces.white.king = (row, column)
|
||||
result.position.pieces.white.king = (row, column)
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
|
@ -310,9 +341,9 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
|||
# Active color
|
||||
case c:
|
||||
of 'w':
|
||||
result.turn = White
|
||||
result.position.turn = White
|
||||
of 'b':
|
||||
result.turn = Black
|
||||
result.position.turn = Black
|
||||
else:
|
||||
raise newException(ValueError, "invalid active color identifier in FEN string")
|
||||
of 2:
|
||||
|
@ -324,13 +355,13 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
|||
# by default
|
||||
discard
|
||||
of 'K':
|
||||
result.castling.white.king = true
|
||||
result.position.castling.white.king = true
|
||||
of 'Q':
|
||||
result.castling.white.queen = true
|
||||
result.position.castling.white.queen = true
|
||||
of 'k':
|
||||
result.castling.black.king = true
|
||||
result.position.castling.black.king = true
|
||||
of 'q':
|
||||
result.castling.black.queen = true
|
||||
result.position.castling.black.queen = true
|
||||
else:
|
||||
raise newException(ValueError, "invalid castling availability in FEN string")
|
||||
of 3:
|
||||
|
@ -340,11 +371,11 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
|||
# Field is already uninitialized to the correct state
|
||||
discard
|
||||
else:
|
||||
result.enPassantSquare.targetSquare = state[index..index+1].algebraicToPosition()
|
||||
# Just for cleanliness purposes, we fill in the other positional metadata as
|
||||
result.position.enPassantSquare.targetSquare = state[index..index+1].algebraicToPosition()
|
||||
# Just for cleanliness purposes, we fill in the other metadata as
|
||||
# well
|
||||
result.enPassantSquare.piece.color = if result.turn == Black: White else: Black
|
||||
result.enPassantSquare.piece.kind = Pawn
|
||||
result.position.enPassantSquare.piece.color = result.getActiveColor()
|
||||
result.position.enPassantSquare.piece.kind = Pawn
|
||||
# Square metadata is 2 bytes long
|
||||
inc(index)
|
||||
of 4:
|
||||
|
@ -356,14 +387,14 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
|||
# Backtrack so the space is seen by the
|
||||
# next iteration of the loop
|
||||
dec(index)
|
||||
result.halfMoveClock = parseInt(s)
|
||||
result.position.halfMoveClock = parseInt(s)
|
||||
of 5:
|
||||
# Fullmove number
|
||||
var s = ""
|
||||
while index <= state.high():
|
||||
s.add(state[index])
|
||||
inc(index)
|
||||
result.fullMoveCount = parseInt(s)
|
||||
result.position.fullMoveCount = parseInt(s)
|
||||
else:
|
||||
raise newException(ValueError, "too many fields in FEN string")
|
||||
inc(index)
|
||||
|
@ -382,15 +413,15 @@ proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int =
|
|||
of White:
|
||||
case kind:
|
||||
of Pawn:
|
||||
return self.pieces.white.pawns.len()
|
||||
return self.position.pieces.white.pawns.len()
|
||||
of Bishop:
|
||||
return self.pieces.white.bishops.len()
|
||||
return self.position.pieces.white.bishops.len()
|
||||
of Knight:
|
||||
return self.pieces.white.knights.len()
|
||||
return self.position.pieces.white.knights.len()
|
||||
of Rook:
|
||||
return self.pieces.white.rooks.len()
|
||||
return self.position.pieces.white.rooks.len()
|
||||
of Queen:
|
||||
return self.pieces.white.queens.len()
|
||||
return self.position.pieces.white.queens.len()
|
||||
of King:
|
||||
# There shall be only one, forever
|
||||
return 1
|
||||
|
@ -399,15 +430,15 @@ proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int =
|
|||
of Black:
|
||||
case kind:
|
||||
of Pawn:
|
||||
return self.pieces.black.pawns.len()
|
||||
return self.position.pieces.black.pawns.len()
|
||||
of Bishop:
|
||||
return self.pieces.black.bishops.len()
|
||||
return self.position.pieces.black.bishops.len()
|
||||
of Knight:
|
||||
return self.pieces.black.knights.len()
|
||||
return self.position.pieces.black.knights.len()
|
||||
of Rook:
|
||||
return self.pieces.black.rooks.len()
|
||||
return self.position.pieces.black.rooks.len()
|
||||
of Queen:
|
||||
return self.pieces.black.queens.len()
|
||||
return self.position.pieces.black.queens.len()
|
||||
of King:
|
||||
# In perpetuity
|
||||
return 1
|
||||
|
@ -468,7 +499,7 @@ proc getCapture*(self: ChessBoard, move: Move): Location =
|
|||
result = emptyLocation()
|
||||
let target = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||
if target.color == None:
|
||||
if move.targetSquare != self.enPassantSquare.targetSquare:
|
||||
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)
|
||||
|
@ -497,10 +528,10 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
# If the pawn is on its first rank, it can push two squares
|
||||
if location.row == piece.getStartRow():
|
||||
locations.add(piece.doublePush())
|
||||
if self.enPassantSquare.piece.color == piece.color.opposite:
|
||||
if abs(self.enPassantSquare.targetSquare.col - location.col) == 1 and abs(self.enPassantSquare.targetSquare.row - location.row) == 1:
|
||||
if self.position.enPassantSquare.piece.color == piece.color.opposite:
|
||||
if abs(self.position.enPassantSquare.targetSquare.col - location.col) == 1 and abs(self.position.enPassantSquare.targetSquare.row - location.row) == 1:
|
||||
# Only viable if the piece is on the diagonal of the target
|
||||
locations.add(self.enPassantSquare.targetSquare)
|
||||
locations.add(self.position.enPassantSquare.targetSquare)
|
||||
# They can also move on either diagonal one
|
||||
# square, but only to capture
|
||||
if location.col in 1..6:
|
||||
|
@ -665,10 +696,10 @@ proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
proc getAttackers*(self: ChessBoard, square: string): seq[Piece] =
|
||||
## Returns all the attackers of the given square
|
||||
let loc = square.algebraicToPosition()
|
||||
for move in self.attacked.black:
|
||||
for move in self.position.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
for move in self.attacked.white:
|
||||
for move in self.position.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
|
||||
|
@ -679,11 +710,11 @@ proc getAttackersFor*(self: ChessBoard, square: string, color: PieceColor): seq[
|
|||
let loc = square.algebraicToPosition()
|
||||
case color:
|
||||
of White:
|
||||
for move in self.attacked.black:
|
||||
for move in self.position.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
of Black:
|
||||
for move in self.attacked.white:
|
||||
for move in self.position.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
result.add(move.piece)
|
||||
else:
|
||||
|
@ -700,21 +731,21 @@ proc isAttacked*(self: ChessBoard, loc: Location): bool =
|
|||
let piece = self.grid[loc.row, loc.col]
|
||||
case piece.color:
|
||||
of White:
|
||||
for move in self.attacked.black:
|
||||
for move in self.position.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
of Black:
|
||||
for move in self.attacked.white:
|
||||
for move in self.position.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
of None:
|
||||
case self.turn:
|
||||
case self.getActiveColor():
|
||||
of White:
|
||||
for move in self.attacked.black:
|
||||
for move in self.position.attacked.black:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
of Black:
|
||||
for move in self.attacked.white:
|
||||
for move in self.position.attacked.white:
|
||||
if move.targetSquare == loc:
|
||||
return true
|
||||
else:
|
||||
|
@ -735,46 +766,46 @@ proc updateAttackedSquares(self: ChessBoard) =
|
|||
# O(1) operation, because we're only updating the length
|
||||
# field without deallocating the memory, which will promptly
|
||||
# be reused by us again. Neat!
|
||||
self.attacked.white.setLen(0)
|
||||
self.attacked.black.setLen(0)
|
||||
self.position.attacked.white.setLen(0)
|
||||
self.position.attacked.black.setLen(0)
|
||||
# Go over each piece one by one and see which squares
|
||||
# it currently attacks
|
||||
|
||||
# Pawns
|
||||
for loc in self.pieces.white.pawns:
|
||||
for loc in self.position.pieces.white.pawns:
|
||||
for move in self.generateMoves(loc):
|
||||
self.attacked.white.add(move)
|
||||
self.position.attacked.white.add(move)
|
||||
# Bishops
|
||||
for loc in self.pieces.white.bishops:
|
||||
for loc in self.position.pieces.white.bishops:
|
||||
for move in self.generateMoves(loc):
|
||||
self.attacked.white.add(move)
|
||||
# rooks
|
||||
for loc in self.pieces.white.rooks:
|
||||
self.position.attacked.white.add(move)
|
||||
# Rooks
|
||||
for loc in self.position.pieces.white.rooks:
|
||||
for move in self.generateMoves(loc):
|
||||
self.attacked.white.add(move)
|
||||
self.position.attacked.white.add(move)
|
||||
# Queens
|
||||
for loc in self.pieces.white.queens:
|
||||
for loc in self.position.pieces.white.queens:
|
||||
for move in self.generateMoves(loc):
|
||||
self.attacked.white.add(move)
|
||||
self.position.attacked.white.add(move)
|
||||
# King
|
||||
for move in self.generateMoves(self.pieces.white.king):
|
||||
self.attacked.white.add(move)
|
||||
for move in self.generateMoves(self.position.pieces.white.king):
|
||||
self.position.attacked.white.add(move)
|
||||
|
||||
# Same for black
|
||||
for loc in self.pieces.black.pawns:
|
||||
for loc in self.position.pieces.black.pawns:
|
||||
for move in self.generateMoves(loc):
|
||||
self.attacked.black.add(move)
|
||||
for loc in self.pieces.black.bishops:
|
||||
self.position.attacked.black.add(move)
|
||||
for loc in self.position.pieces.black.bishops:
|
||||
for move in self.generateMoves(loc):
|
||||
self.attacked.black.add(move)
|
||||
for loc in self.pieces.black.rooks:
|
||||
self.position.attacked.black.add(move)
|
||||
for loc in self.position.pieces.black.rooks:
|
||||
for move in self.generateMoves(loc):
|
||||
self.attacked.black.add(move)
|
||||
for loc in self.pieces.black.queens:
|
||||
self.position.attacked.black.add(move)
|
||||
for loc in self.position.pieces.black.queens:
|
||||
for move in self.generateMoves(loc):
|
||||
self.attacked.black.add(move)
|
||||
for move in self.generateMoves(self.pieces.black.king):
|
||||
self.attacked.black.add(move)
|
||||
self.position.attacked.black.add(move)
|
||||
for move in self.generateMoves(self.position.pieces.black.king):
|
||||
self.position.attacked.black.add(move)
|
||||
|
||||
|
||||
proc removePiece(self: ChessBoard, location: Location) =
|
||||
|
@ -786,15 +817,15 @@ proc removePiece(self: ChessBoard, location: Location) =
|
|||
of White:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
self.pieces.white.pawns.delete(self.pieces.white.pawns.find(location))
|
||||
self.position.pieces.white.pawns.delete(self.position.pieces.white.pawns.find(location))
|
||||
of Bishop:
|
||||
self.pieces.white.pawns.delete(self.pieces.white.bishops.find(location))
|
||||
self.position.pieces.white.pawns.delete(self.position.pieces.white.bishops.find(location))
|
||||
of Knight:
|
||||
self.pieces.white.pawns.delete(self.pieces.white.knights.find(location))
|
||||
self.position.pieces.white.pawns.delete(self.position.pieces.white.knights.find(location))
|
||||
of Rook:
|
||||
self.pieces.white.rooks.delete(self.pieces.white.rooks.find(location))
|
||||
self.position.pieces.white.rooks.delete(self.position.pieces.white.rooks.find(location))
|
||||
of Queen:
|
||||
self.pieces.white.queens.delete(self.pieces.white.queens.find(location))
|
||||
self.position.pieces.white.queens.delete(self.position.pieces.white.queens.find(location))
|
||||
of King:
|
||||
doAssert false, "removePiece: attempted to remove the white king"
|
||||
else:
|
||||
|
@ -802,15 +833,15 @@ proc removePiece(self: ChessBoard, location: Location) =
|
|||
of Black:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
self.pieces.black.pawns.delete(self.pieces.white.pawns.find(location))
|
||||
self.position.pieces.black.pawns.delete(self.position.pieces.white.pawns.find(location))
|
||||
of Bishop:
|
||||
self.pieces.black.bishops.delete(self.pieces.black.bishops.find(location))
|
||||
self.position.pieces.black.bishops.delete(self.position.pieces.black.bishops.find(location))
|
||||
of Knight:
|
||||
self.pieces.black.knights.delete(self.pieces.black.knights.find(location))
|
||||
self.position.pieces.black.knights.delete(self.position.pieces.black.knights.find(location))
|
||||
of Rook:
|
||||
self.pieces.black.rooks.delete(self.pieces.black.rooks.find(location))
|
||||
self.position.pieces.black.rooks.delete(self.position.pieces.black.rooks.find(location))
|
||||
of Queen:
|
||||
self.pieces.black.queens.delete(self.pieces.black.queens.find(location))
|
||||
self.position.pieces.black.queens.delete(self.position.pieces.black.queens.find(location))
|
||||
of King:
|
||||
doAssert false, "removePiece: attempted to remove the black king"
|
||||
else:
|
||||
|
@ -819,6 +850,76 @@ proc removePiece(self: ChessBoard, location: Location) =
|
|||
discard
|
||||
|
||||
|
||||
proc handleCapture(self: ChessBoard, move: Move) =
|
||||
## Handles capturing (assumes the move is valid)
|
||||
let targetPiece = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||
assert self.position.captured == emptyPiece(), "capture: last capture is non-empty"
|
||||
self.position.captured = move.piece
|
||||
self.removePiece(move.targetSquare)
|
||||
|
||||
|
||||
proc movePiece(self: ChessBoard, move: Move) =
|
||||
## Internal helper to move a piece. Does
|
||||
## not update attacked squares, just position
|
||||
## metadata and the grid itself
|
||||
case move.piece.color:
|
||||
of White:
|
||||
case move.piece.kind:
|
||||
of Pawn:
|
||||
# The way things are structured, we don't care about the order
|
||||
# of this list, so we can add and remove entries as we please
|
||||
self.position.pieces.white.pawns.delete(self.position.pieces.white.pawns.find(move.startSquare))
|
||||
self.position.pieces.white.pawns.add(move.targetSquare)
|
||||
of Bishop:
|
||||
self.position.pieces.white.bishops.delete(self.position.pieces.white.bishops.find(move.startSquare))
|
||||
self.position.pieces.white.bishops.add(move.targetSquare)
|
||||
of Knight:
|
||||
self.position.pieces.white.knights.delete(self.position.pieces.white.knights.find(move.startSquare))
|
||||
self.position.pieces.white.knights.add(move.targetSquare)
|
||||
of Rook:
|
||||
self.position.pieces.white.rooks.delete(self.position.pieces.white.rooks.find(move.startSquare))
|
||||
self.position.pieces.white.rooks.add(move.targetSquare)
|
||||
of Queen:
|
||||
try:
|
||||
self.position.pieces.white.queens.delete(self.position.pieces.white.queens.find(move.startSquare))
|
||||
except:
|
||||
echo self.position.pieces.white.queens
|
||||
echo move.startSquare
|
||||
raise getCurrentException()
|
||||
self.position.pieces.white.queens.add(move.targetSquare)
|
||||
of King:
|
||||
self.position.pieces.white.king = move.targetSquare
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
case move.piece.kind:
|
||||
of Pawn:
|
||||
self.position.pieces.black.pawns.delete(self.position.pieces.black.pawns.find(move.startSquare))
|
||||
self.position.pieces.black.pawns.add(move.targetSquare)
|
||||
of Bishop:
|
||||
self.position.pieces.black.bishops.delete(self.position.pieces.black.bishops.find(move.startSquare))
|
||||
self.position.pieces.black.bishops.add(move.targetSquare)
|
||||
of Knight:
|
||||
self.position.pieces.black.knights.delete(self.position.pieces.black.knights.find(move.startSquare))
|
||||
self.position.pieces.black.knights.add(move.targetSquare)
|
||||
of Rook:
|
||||
self.position.pieces.black.rooks.delete(self.position.pieces.black.rooks.find(move.startSquare))
|
||||
self.position.pieces.black.rooks.add(move.targetSquare)
|
||||
of Queen:
|
||||
self.position.pieces.black.queens.delete(self.position.pieces.black.queens.find(move.startSquare))
|
||||
self.position.pieces.black.queens.add(move.targetSquare)
|
||||
of King:
|
||||
self.position.pieces.black.king = move.targetSquare
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
# Empty out the starting square
|
||||
self.grid[move.startSquare.row, move.startSquare.col] = emptyPiece()
|
||||
# Actually move the piece
|
||||
self.grid[move.targetSquare.row, move.targetSquare.col] = move.piece
|
||||
|
||||
|
||||
proc updatePositions(self: ChessBoard, move: Move) =
|
||||
## Internal helper to update the position of
|
||||
## the pieces on the board after a move
|
||||
|
@ -828,59 +929,9 @@ proc updatePositions(self: ChessBoard, move: Move) =
|
|||
# of doing it ourselves because there's a bunch of metadata that needs
|
||||
# to be updated to do this properly and I thought it'd fit into its neat
|
||||
# little function
|
||||
self.removePiece(capture)
|
||||
self.handleCapture(move)
|
||||
# Update the positional metadata of the moving piece
|
||||
case move.piece.color:
|
||||
of White:
|
||||
case move.piece.kind:
|
||||
of Pawn:
|
||||
# The way things are structured, we don't care about the order
|
||||
# of this list, so we can add and remove entries as we please
|
||||
self.pieces.white.pawns.delete(self.pieces.white.pawns.find(move.startSquare))
|
||||
self.pieces.white.pawns.add(move.targetSquare)
|
||||
of Bishop:
|
||||
self.pieces.white.bishops.delete(self.pieces.white.bishops.find(move.startSquare))
|
||||
self.pieces.white.bishops.add(move.targetSquare)
|
||||
of Knight:
|
||||
self.pieces.white.knights.delete(self.pieces.white.knights.find(move.startSquare))
|
||||
self.pieces.white.knights.add(move.targetSquare)
|
||||
of Rook:
|
||||
self.pieces.white.rooks.delete(self.pieces.white.rooks.find(move.startSquare))
|
||||
self.pieces.white.rooks.add(move.targetSquare)
|
||||
of Queen:
|
||||
self.pieces.white.queens.delete(self.pieces.white.queens.find(move.startSquare))
|
||||
self.pieces.white.queens.add(move.targetSquare)
|
||||
of King:
|
||||
self.pieces.white.king = move.targetSquare
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
case move.piece.kind:
|
||||
of Pawn:
|
||||
self.pieces.black.pawns.delete(self.pieces.black.pawns.find(move.startSquare))
|
||||
self.pieces.black.pawns.add(move.targetSquare)
|
||||
of Bishop:
|
||||
self.pieces.black.bishops.delete(self.pieces.black.bishops.find(move.startSquare))
|
||||
self.pieces.black.bishops.add(move.targetSquare)
|
||||
of Knight:
|
||||
self.pieces.black.knights.delete(self.pieces.black.knights.find(move.startSquare))
|
||||
self.pieces.black.knights.add(move.targetSquare)
|
||||
of Rook:
|
||||
self.pieces.black.rooks.delete(self.pieces.black.rooks.find(move.startSquare))
|
||||
self.pieces.black.rooks.add(move.targetSquare)
|
||||
of Queen:
|
||||
self.pieces.black.queens.delete(self.pieces.black.queens.find(move.startSquare))
|
||||
self.pieces.black.queens.add(move.targetSquare)
|
||||
of King:
|
||||
self.pieces.black.king = move.targetSquare
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
# Empty out the starting square
|
||||
self.grid[move.startSquare.row, move.startSquare.col] = emptyPiece()
|
||||
# Actually move the piece
|
||||
self.grid[move.targetSquare.row, move.targetSquare.col] = move.piece
|
||||
self.movePiece(move)
|
||||
|
||||
|
||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
||||
|
@ -890,15 +941,15 @@ proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
|||
## for the active color's king
|
||||
case color:
|
||||
of White:
|
||||
return self.isAttacked(self.pieces.white.king)
|
||||
return self.isAttacked(self.position.pieces.white.king)
|
||||
of Black:
|
||||
return self.isAttacked(self.pieces.black.king)
|
||||
return self.isAttacked(self.position.pieces.black.king)
|
||||
of None:
|
||||
case self.turn:
|
||||
case self.getActiveColor():
|
||||
of White:
|
||||
return self.isAttacked(self.pieces.white.king)
|
||||
return self.isAttacked(self.position.pieces.white.king)
|
||||
of Black:
|
||||
return self.isAttacked(self.pieces.black.king)
|
||||
return self.isAttacked(self.position.pieces.black.king)
|
||||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
|
@ -909,46 +960,128 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
## performing legality checks on the given move. Can
|
||||
## be used in performance-critical paths where
|
||||
## a move is already known to be legal
|
||||
self.updatePositions(move)
|
||||
self.updateAttackedSquares()
|
||||
|
||||
# Final checks
|
||||
|
||||
# Needed to detect draw by the 50 move rule
|
||||
if move.piece.kind != Pawn and not self.isCapture(move):
|
||||
inc(self.position.halfMoveClock)
|
||||
else:
|
||||
self.position.halfMoveClock = 0
|
||||
if (self.position.halfMoveClock and 1) == 0: # Equivalent to (x mod 2) == 0, just much faster
|
||||
inc(self.position.fullMoveCount)
|
||||
# En passant is possible only immediately after the
|
||||
# pawn has moved
|
||||
if self.enPassantSquare != emptyMove() and self.enPassantSquare.piece.color == self.turn.opposite():
|
||||
self.enPassantSquare = emptyMove()
|
||||
self.turn = self.turn.opposite()
|
||||
if self.position.enPassantSquare != emptyMove() and self.position.enPassantSquare.piece.color == self.getActiveColor().opposite():
|
||||
self.position.enPassantSquare = emptyMove()
|
||||
# TODO: Castling
|
||||
|
||||
self.position.move = move
|
||||
|
||||
# Update position and attack metadata
|
||||
self.updatePositions(move)
|
||||
self.updateAttackedSquares()
|
||||
|
||||
# Record final position for future reference
|
||||
self.positions.add(self.position)
|
||||
# Create new position with
|
||||
var newPos = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||
captured: emptyPiece(),
|
||||
turn: self.position.turn.opposite(),
|
||||
# Inherit values from current position
|
||||
# (they are already up to date by this point)
|
||||
castling: self.position.castling,
|
||||
enPassantSquare: self.position.enPassantSquare,
|
||||
attacked: (@[], @[])
|
||||
)
|
||||
|
||||
self.position = newPos
|
||||
inc(self.positionIndex)
|
||||
|
||||
|
||||
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||
## Internal helper to "spawn" a given piece at the given
|
||||
## location. Note that this will overwrite whatever piece
|
||||
## was previously located there: use with caution. Does
|
||||
## not automatically update the attacked square metadata
|
||||
## or other positional information
|
||||
doAssert piece.kind != King, "spawnPiece: cannot spawn a king"
|
||||
case piece.color:
|
||||
of White:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
self.position.pieces.white.pawns.add(location)
|
||||
of Knight:
|
||||
self.position.pieces.white.knights.add(location)
|
||||
of Bishop:
|
||||
self.position.pieces.white.bishops.add(location)
|
||||
of Rook:
|
||||
self.position.pieces.white.rooks.add(location)
|
||||
of Queen:
|
||||
self.position.pieces.white.queens.add(location)
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
self.position.pieces.black.pawns.add(location)
|
||||
of Knight:
|
||||
self.position.pieces.black.knights.add(location)
|
||||
of Bishop:
|
||||
self.position.pieces.black.bishops.add(location)
|
||||
of Rook:
|
||||
self.position.pieces.black.rooks.add(location)
|
||||
of Queen:
|
||||
self.position.pieces.black.queens.add(location)
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
self.grid[location.row, location.col] = piece
|
||||
|
||||
proc undoMove(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||
## Undoes the given move if possible
|
||||
result = Move(piece: move.piece, startSquare: move.targetSquare, targetSquare: move.startSquare)
|
||||
let castling = self.castling
|
||||
let enPassant = self.enPassantSquare
|
||||
# Swap start and target square and do the move in reverse
|
||||
self.doMove(result)
|
||||
# We need to reset the entire position when the move is undone!
|
||||
self.enPassantSquare = enPassant
|
||||
#self.turn = self.turn.opposite()
|
||||
self.castling = castling
|
||||
|
||||
proc undoLastMove*(self: ChessBoard): Move {.discardable.} =
|
||||
## Undoes the last move, restoring any captured pieces,
|
||||
## as well castling and en passant status. If there are
|
||||
## no moves to undo, this is a no-op. Returns the move
|
||||
## that was performed (which may be an empty move)
|
||||
result = emptyMove()
|
||||
if self.positions.len() == 0:
|
||||
return
|
||||
let positionIndex = max(0, self.positionIndex - 1)
|
||||
if positionIndex in 0..self.positions.high():
|
||||
self.positionIndex = positionIndex
|
||||
self.position = self.positions[positionIndex]
|
||||
let
|
||||
currentMove = self.position.move
|
||||
oppositeMove = Move(piece: currentMove.piece, targetSquare: currentMove.startSquare, startSquare: currentMove.targetSquare)
|
||||
self.spawnPiece(currentMove.startSquare, currentMove.piece)
|
||||
if self.position.captured != emptyPiece():
|
||||
self.spawnPiece(self.position.move.targetSquare, self.position.captured)
|
||||
self.updateAttackedSquares()
|
||||
self.updatePositions(oppositeMove)
|
||||
return self.position.move
|
||||
|
||||
|
||||
|
||||
proc checkMove(self: ChessBoard, move: Move): bool =
|
||||
## Internal function called by makeMove to check a move for legality
|
||||
# Start square doesn't contain a piece (and it isn't the en passant square)
|
||||
# or it is of the wrong color for which turn it is to move
|
||||
if move.piece.kind == Empty or move.piece.color != self.turn:
|
||||
if move.piece.kind == Empty or move.piece.color != self.getActiveColor():
|
||||
return false
|
||||
var destination = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||
# Destination square is occupied by a piece of the same color as the piece
|
||||
# being moved: illegal!
|
||||
if destination.kind != Empty and destination.color == self.turn:
|
||||
if destination.kind != Empty and destination.color == self.getActiveColor():
|
||||
return false
|
||||
if move notin self.generateMoves(move.startSquare):
|
||||
# Piece cannot arrive to destination (blocked,
|
||||
# pinned, or otherwise invalid move)
|
||||
return false
|
||||
self.doMove(move)
|
||||
defer: self.undoMove(move)
|
||||
defer: self.undoLastMove()
|
||||
# Move would reveal an attack
|
||||
# on our king: not allowed
|
||||
if self.inCheck(move.piece.color):
|
||||
|
@ -962,11 +1095,6 @@ proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
|||
result = move
|
||||
if not self.checkMove(move):
|
||||
return emptyMove()
|
||||
# 50 move rule
|
||||
if move.piece.kind != Pawn and not self.isCapture(move):
|
||||
inc(self.halfMoveClock)
|
||||
else:
|
||||
self.halfMoveClock = 0
|
||||
self.doMove(result)
|
||||
|
||||
|
||||
|
@ -986,6 +1114,13 @@ proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.disc
|
|||
return self.makeMove(result)
|
||||
|
||||
|
||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: Location): Move {.discardable.} =
|
||||
## Like the other makeMove(), but with two locations
|
||||
result = Move(startSquare: startSquare, targetSquare: targetSquare, piece: self.grid[startSquare.row, startSquare.col])
|
||||
return self.makeMove(result)
|
||||
|
||||
|
||||
|
||||
|
||||
proc `$`*(self: ChessBoard): string =
|
||||
result &= "- - - - - - - -"
|
||||
|
|
|
@ -10,8 +10,6 @@ when isMainModule:
|
|||
setControlCHook(proc () {.noconv.} = echo ""; quit(0))
|
||||
var
|
||||
board = newChessboardFromFEN("rnbqkbnr/8/8/8/8/8/8/RNBQKBNR w KQkq - 0 1")
|
||||
startSquare: string
|
||||
targetSquare: string
|
||||
data: string
|
||||
move: Move
|
||||
|
||||
|
@ -27,12 +25,13 @@ when isMainModule:
|
|||
except IOError:
|
||||
echo ""
|
||||
break
|
||||
if data == "undo":
|
||||
echo &"Undo: {board.undoLastMove()}"
|
||||
continue
|
||||
if len(data) != 4:
|
||||
continue
|
||||
startSquare = data[0..1]
|
||||
targetSquare = data[2..3]
|
||||
try:
|
||||
move = board.makeMove(startSquare, targetSquare)
|
||||
move = board.makeMove(data[0..1], data[2..3])
|
||||
except ValueError:
|
||||
echo &"Error: {getCurrentExceptionMsg()}"
|
||||
if move == emptyMove():
|
||||
|
|
Loading…
Reference in New Issue