Initial work on the move list
This commit is contained in:
parent
ac941883ea
commit
4586b44ec1
|
@ -21,11 +21,12 @@ import std/strformat
|
||||||
|
|
||||||
type
|
type
|
||||||
# Useful type aliases
|
# Useful type aliases
|
||||||
Location = tuple[row, col: int]
|
Location* = tuple[row, col: int]
|
||||||
|
|
||||||
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
|
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
|
||||||
bishops: seq[Location], knights: seq[Location],
|
bishops: seq[Location], knights: seq[Location],
|
||||||
pawns: seq[Location]]
|
pawns: seq[Location]]
|
||||||
|
Castling* = tuple[white, black: tuple[queen, king: bool]]
|
||||||
|
|
||||||
PieceColor* = enum
|
PieceColor* = enum
|
||||||
## A piece color enumeration
|
## A piece color enumeration
|
||||||
|
@ -65,11 +66,15 @@ type
|
||||||
targetSquare*: Location
|
targetSquare*: Location
|
||||||
flag*: MoveFlag
|
flag*: MoveFlag
|
||||||
|
|
||||||
ChessBoard* = ref object
|
Position* = ref object
|
||||||
## A chess board object
|
## A chess position
|
||||||
grid: Matrix[Piece]
|
move: Move
|
||||||
# Currently active color
|
# Stores castling metadata
|
||||||
turn: PieceColor
|
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
|
# Number of half moves since
|
||||||
# last piece capture or pawn movement.
|
# last piece capture or pawn movement.
|
||||||
# Used for the 50-move rule
|
# Used for the 50-move rule
|
||||||
|
@ -77,8 +82,6 @@ type
|
||||||
# Full move counter. Increments
|
# Full move counter. Increments
|
||||||
# every 2 ply
|
# every 2 ply
|
||||||
fullMoveCount: int
|
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)
|
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
||||||
# If en passant is not possible, both the row and
|
# If en passant is not possible, both the row and
|
||||||
# column of the position will be set to -1
|
# column of the position will be set to -1
|
||||||
|
@ -87,6 +90,19 @@ type
|
||||||
pieces: tuple[white: Pieces, black: Pieces]
|
pieces: tuple[white: Pieces, black: Pieces]
|
||||||
# Potential attacking moves for black and white
|
# Potential attacking moves for black and white
|
||||||
attacked: tuple[white: seq[Move], black: seq[Move]]
|
attacked: tuple[white: 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
|
# Initialized only once, copied every time
|
||||||
|
@ -177,7 +193,19 @@ func topRightKnightMove(piece: Piece, long: bool = true): Location {.inline.} =
|
||||||
proc getActiveColor*(self: ChessBoard): PieceColor =
|
proc getActiveColor*(self: ChessBoard): PieceColor =
|
||||||
## Returns the currently active color
|
## Returns the currently active color
|
||||||
## (turn of who has to move)
|
## (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.} =
|
func getStartRow(piece: Piece): int {.inline.} =
|
||||||
|
@ -218,9 +246,12 @@ 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.attacked = (@[], @[])
|
result.position = Position(attacked: (@[], @[]),
|
||||||
result.enPassantSquare = emptyMove()
|
enPassantSquare: emptyMove(),
|
||||||
result.turn = White
|
move: emptyMove(),
|
||||||
|
turn: White)
|
||||||
|
|
||||||
|
result.positionIndex = 0
|
||||||
|
|
||||||
|
|
||||||
proc newChessboardFromFEN*(state: string): ChessBoard =
|
proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
|
@ -261,33 +292,33 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
of Black:
|
of Black:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
of Pawn:
|
of Pawn:
|
||||||
result.pieces.black.pawns.add((row, column))
|
result.position.pieces.black.pawns.add((row, column))
|
||||||
of Bishop:
|
of Bishop:
|
||||||
result.pieces.black.bishops.add((row, column))
|
result.position.pieces.black.bishops.add((row, column))
|
||||||
of Knight:
|
of Knight:
|
||||||
result.pieces.black.knights.add((row, column))
|
result.position.pieces.black.knights.add((row, column))
|
||||||
of Rook:
|
of Rook:
|
||||||
result.pieces.black.rooks.add((row, column))
|
result.position.pieces.black.rooks.add((row, column))
|
||||||
of Queen:
|
of Queen:
|
||||||
result.pieces.black.queens.add((row, column))
|
result.position.pieces.black.queens.add((row, column))
|
||||||
of King:
|
of King:
|
||||||
result.pieces.black.king = (row, column)
|
result.position.pieces.black.king = (row, column)
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
of White:
|
of White:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
of Pawn:
|
of Pawn:
|
||||||
result.pieces.white.pawns.add((row, column))
|
result.position.pieces.white.pawns.add((row, column))
|
||||||
of Bishop:
|
of Bishop:
|
||||||
result.pieces.white.bishops.add((row, column))
|
result.position.pieces.white.bishops.add((row, column))
|
||||||
of Knight:
|
of Knight:
|
||||||
result.pieces.white.knights.add((row, column))
|
result.position.pieces.white.knights.add((row, column))
|
||||||
of Rook:
|
of Rook:
|
||||||
result.pieces.white.rooks.add((row, column))
|
result.position.pieces.white.rooks.add((row, column))
|
||||||
of Queen:
|
of Queen:
|
||||||
result.pieces.white.queens.add((row, column))
|
result.position.pieces.white.queens.add((row, column))
|
||||||
of King:
|
of King:
|
||||||
result.pieces.white.king = (row, column)
|
result.position.pieces.white.king = (row, column)
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
|
@ -310,9 +341,9 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
# Active color
|
# Active color
|
||||||
case c:
|
case c:
|
||||||
of 'w':
|
of 'w':
|
||||||
result.turn = White
|
result.position.turn = White
|
||||||
of 'b':
|
of 'b':
|
||||||
result.turn = Black
|
result.position.turn = Black
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "invalid active color identifier in FEN string")
|
raise newException(ValueError, "invalid active color identifier in FEN string")
|
||||||
of 2:
|
of 2:
|
||||||
|
@ -324,13 +355,13 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
# by default
|
# by default
|
||||||
discard
|
discard
|
||||||
of 'K':
|
of 'K':
|
||||||
result.castling.white.king = true
|
result.position.castling.white.king = true
|
||||||
of 'Q':
|
of 'Q':
|
||||||
result.castling.white.queen = true
|
result.position.castling.white.queen = true
|
||||||
of 'k':
|
of 'k':
|
||||||
result.castling.black.king = true
|
result.position.castling.black.king = true
|
||||||
of 'q':
|
of 'q':
|
||||||
result.castling.black.queen = true
|
result.position.castling.black.queen = true
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "invalid castling availability in FEN string")
|
raise newException(ValueError, "invalid castling availability in FEN string")
|
||||||
of 3:
|
of 3:
|
||||||
|
@ -340,11 +371,11 @@ 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.enPassantSquare.targetSquare = state[index..index+1].algebraicToPosition()
|
result.position.enPassantSquare.targetSquare = state[index..index+1].algebraicToPosition()
|
||||||
# Just for cleanliness purposes, we fill in the other positional metadata as
|
# Just for cleanliness purposes, we fill in the other metadata as
|
||||||
# well
|
# well
|
||||||
result.enPassantSquare.piece.color = if result.turn == Black: White else: Black
|
result.position.enPassantSquare.piece.color = result.getActiveColor()
|
||||||
result.enPassantSquare.piece.kind = Pawn
|
result.position.enPassantSquare.piece.kind = Pawn
|
||||||
# Square metadata is 2 bytes long
|
# Square metadata is 2 bytes long
|
||||||
inc(index)
|
inc(index)
|
||||||
of 4:
|
of 4:
|
||||||
|
@ -356,14 +387,14 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
# 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
|
||||||
dec(index)
|
dec(index)
|
||||||
result.halfMoveClock = parseInt(s)
|
result.position.halfMoveClock = parseInt(s)
|
||||||
of 5:
|
of 5:
|
||||||
# Fullmove number
|
# Fullmove number
|
||||||
var s = ""
|
var s = ""
|
||||||
while index <= state.high():
|
while index <= state.high():
|
||||||
s.add(state[index])
|
s.add(state[index])
|
||||||
inc(index)
|
inc(index)
|
||||||
result.fullMoveCount = parseInt(s)
|
result.position.fullMoveCount = parseInt(s)
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "too many fields in FEN string")
|
raise newException(ValueError, "too many fields in FEN string")
|
||||||
inc(index)
|
inc(index)
|
||||||
|
@ -382,15 +413,15 @@ proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int =
|
||||||
of White:
|
of White:
|
||||||
case kind:
|
case kind:
|
||||||
of Pawn:
|
of Pawn:
|
||||||
return self.pieces.white.pawns.len()
|
return self.position.pieces.white.pawns.len()
|
||||||
of Bishop:
|
of Bishop:
|
||||||
return self.pieces.white.bishops.len()
|
return self.position.pieces.white.bishops.len()
|
||||||
of Knight:
|
of Knight:
|
||||||
return self.pieces.white.knights.len()
|
return self.position.pieces.white.knights.len()
|
||||||
of Rook:
|
of Rook:
|
||||||
return self.pieces.white.rooks.len()
|
return self.position.pieces.white.rooks.len()
|
||||||
of Queen:
|
of Queen:
|
||||||
return self.pieces.white.queens.len()
|
return self.position.pieces.white.queens.len()
|
||||||
of King:
|
of King:
|
||||||
# There shall be only one, forever
|
# There shall be only one, forever
|
||||||
return 1
|
return 1
|
||||||
|
@ -399,15 +430,15 @@ proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int =
|
||||||
of Black:
|
of Black:
|
||||||
case kind:
|
case kind:
|
||||||
of Pawn:
|
of Pawn:
|
||||||
return self.pieces.black.pawns.len()
|
return self.position.pieces.black.pawns.len()
|
||||||
of Bishop:
|
of Bishop:
|
||||||
return self.pieces.black.bishops.len()
|
return self.position.pieces.black.bishops.len()
|
||||||
of Knight:
|
of Knight:
|
||||||
return self.pieces.black.knights.len()
|
return self.position.pieces.black.knights.len()
|
||||||
of Rook:
|
of Rook:
|
||||||
return self.pieces.black.rooks.len()
|
return self.position.pieces.black.rooks.len()
|
||||||
of Queen:
|
of Queen:
|
||||||
return self.pieces.black.queens.len()
|
return self.position.pieces.black.queens.len()
|
||||||
of King:
|
of King:
|
||||||
# In perpetuity
|
# In perpetuity
|
||||||
return 1
|
return 1
|
||||||
|
@ -468,7 +499,7 @@ proc getCapture*(self: ChessBoard, move: Move): Location =
|
||||||
result = emptyLocation()
|
result = emptyLocation()
|
||||||
let target = self.grid[move.targetSquare.row, move.targetSquare.col]
|
let target = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||||
if target.color == None:
|
if target.color == None:
|
||||||
if move.targetSquare != self.enPassantSquare.targetSquare:
|
if move.targetSquare != self.position.enPassantSquare.targetSquare:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
return ((if move.piece.color == White: move.targetSquare.row + 1 else: move.targetSquare.row - 1), move.targetSquare.col)
|
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 the pawn is on its first rank, it can push two squares
|
||||||
if location.row == piece.getStartRow():
|
if location.row == piece.getStartRow():
|
||||||
locations.add(piece.doublePush())
|
locations.add(piece.doublePush())
|
||||||
if self.enPassantSquare.piece.color == piece.color.opposite:
|
if self.position.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 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
|
# 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
|
# They can also move on either diagonal one
|
||||||
# square, but only to capture
|
# square, but only to capture
|
||||||
if location.col in 1..6:
|
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] =
|
proc getAttackers*(self: ChessBoard, square: string): seq[Piece] =
|
||||||
## Returns all the attackers of the given square
|
## Returns all the attackers of the given square
|
||||||
let loc = square.algebraicToPosition()
|
let loc = square.algebraicToPosition()
|
||||||
for move in self.attacked.black:
|
for move in self.position.attacked.black:
|
||||||
if move.targetSquare == loc:
|
if move.targetSquare == loc:
|
||||||
result.add(move.piece)
|
result.add(move.piece)
|
||||||
for move in self.attacked.white:
|
for move in self.position.attacked.white:
|
||||||
if move.targetSquare == loc:
|
if move.targetSquare == loc:
|
||||||
result.add(move.piece)
|
result.add(move.piece)
|
||||||
|
|
||||||
|
@ -679,11 +710,11 @@ proc getAttackersFor*(self: ChessBoard, square: string, color: PieceColor): seq[
|
||||||
let loc = square.algebraicToPosition()
|
let loc = square.algebraicToPosition()
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
for move in self.attacked.black:
|
for move in self.position.attacked.black:
|
||||||
if move.targetSquare == loc:
|
if move.targetSquare == loc:
|
||||||
result.add(move.piece)
|
result.add(move.piece)
|
||||||
of Black:
|
of Black:
|
||||||
for move in self.attacked.white:
|
for move in self.position.attacked.white:
|
||||||
if move.targetSquare == loc:
|
if move.targetSquare == loc:
|
||||||
result.add(move.piece)
|
result.add(move.piece)
|
||||||
else:
|
else:
|
||||||
|
@ -700,21 +731,21 @@ proc isAttacked*(self: ChessBoard, loc: Location): bool =
|
||||||
let piece = self.grid[loc.row, loc.col]
|
let piece = self.grid[loc.row, loc.col]
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of White:
|
of White:
|
||||||
for move in self.attacked.black:
|
for move in self.position.attacked.black:
|
||||||
if move.targetSquare == loc:
|
if move.targetSquare == loc:
|
||||||
return true
|
return true
|
||||||
of Black:
|
of Black:
|
||||||
for move in self.attacked.white:
|
for move in self.position.attacked.white:
|
||||||
if move.targetSquare == loc:
|
if move.targetSquare == loc:
|
||||||
return true
|
return true
|
||||||
of None:
|
of None:
|
||||||
case self.turn:
|
case self.getActiveColor():
|
||||||
of White:
|
of White:
|
||||||
for move in self.attacked.black:
|
for move in self.position.attacked.black:
|
||||||
if move.targetSquare == loc:
|
if move.targetSquare == loc:
|
||||||
return true
|
return true
|
||||||
of Black:
|
of Black:
|
||||||
for move in self.attacked.white:
|
for move in self.position.attacked.white:
|
||||||
if move.targetSquare == loc:
|
if move.targetSquare == loc:
|
||||||
return true
|
return true
|
||||||
else:
|
else:
|
||||||
|
@ -735,46 +766,46 @@ proc updateAttackedSquares(self: ChessBoard) =
|
||||||
# O(1) operation, because we're only updating the length
|
# O(1) operation, because we're only updating the length
|
||||||
# field without deallocating the memory, which will promptly
|
# field without deallocating the memory, which will promptly
|
||||||
# be reused by us again. Neat!
|
# be reused by us again. Neat!
|
||||||
self.attacked.white.setLen(0)
|
self.position.attacked.white.setLen(0)
|
||||||
self.attacked.black.setLen(0)
|
self.position.attacked.black.setLen(0)
|
||||||
# Go over each piece one by one and see which squares
|
# Go over each piece one by one and see which squares
|
||||||
# it currently attacks
|
# it currently attacks
|
||||||
|
|
||||||
# Pawns
|
# Pawns
|
||||||
for loc in self.pieces.white.pawns:
|
for loc in self.position.pieces.white.pawns:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.attacked.white.add(move)
|
self.position.attacked.white.add(move)
|
||||||
# Bishops
|
# Bishops
|
||||||
for loc in self.pieces.white.bishops:
|
for loc in self.position.pieces.white.bishops:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.attacked.white.add(move)
|
self.position.attacked.white.add(move)
|
||||||
# rooks
|
# Rooks
|
||||||
for loc in self.pieces.white.rooks:
|
for loc in self.position.pieces.white.rooks:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.attacked.white.add(move)
|
self.position.attacked.white.add(move)
|
||||||
# Queens
|
# Queens
|
||||||
for loc in self.pieces.white.queens:
|
for loc in self.position.pieces.white.queens:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.attacked.white.add(move)
|
self.position.attacked.white.add(move)
|
||||||
# King
|
# King
|
||||||
for move in self.generateMoves(self.pieces.white.king):
|
for move in self.generateMoves(self.position.pieces.white.king):
|
||||||
self.attacked.white.add(move)
|
self.position.attacked.white.add(move)
|
||||||
|
|
||||||
# Same for black
|
# Same for black
|
||||||
for loc in self.pieces.black.pawns:
|
for loc in self.position.pieces.black.pawns:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.attacked.black.add(move)
|
self.position.attacked.black.add(move)
|
||||||
for loc in self.pieces.black.bishops:
|
for loc in self.position.pieces.black.bishops:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.attacked.black.add(move)
|
self.position.attacked.black.add(move)
|
||||||
for loc in self.pieces.black.rooks:
|
for loc in self.position.pieces.black.rooks:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.attacked.black.add(move)
|
self.position.attacked.black.add(move)
|
||||||
for loc in self.pieces.black.queens:
|
for loc in self.position.pieces.black.queens:
|
||||||
for move in self.generateMoves(loc):
|
for move in self.generateMoves(loc):
|
||||||
self.attacked.black.add(move)
|
self.position.attacked.black.add(move)
|
||||||
for move in self.generateMoves(self.pieces.black.king):
|
for move in self.generateMoves(self.position.pieces.black.king):
|
||||||
self.attacked.black.add(move)
|
self.position.attacked.black.add(move)
|
||||||
|
|
||||||
|
|
||||||
proc removePiece(self: ChessBoard, location: Location) =
|
proc removePiece(self: ChessBoard, location: Location) =
|
||||||
|
@ -786,15 +817,15 @@ proc removePiece(self: ChessBoard, location: Location) =
|
||||||
of White:
|
of White:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
of Pawn:
|
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:
|
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:
|
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:
|
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:
|
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:
|
of King:
|
||||||
doAssert false, "removePiece: attempted to remove the white king"
|
doAssert false, "removePiece: attempted to remove the white king"
|
||||||
else:
|
else:
|
||||||
|
@ -802,15 +833,15 @@ proc removePiece(self: ChessBoard, location: Location) =
|
||||||
of Black:
|
of Black:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
of Pawn:
|
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:
|
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:
|
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:
|
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:
|
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:
|
of King:
|
||||||
doAssert false, "removePiece: attempted to remove the black king"
|
doAssert false, "removePiece: attempted to remove the black king"
|
||||||
else:
|
else:
|
||||||
|
@ -819,6 +850,76 @@ proc removePiece(self: ChessBoard, location: Location) =
|
||||||
discard
|
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) =
|
proc updatePositions(self: ChessBoard, move: Move) =
|
||||||
## Internal helper to update the position of
|
## Internal helper to update the position of
|
||||||
## the pieces on the board after a move
|
## 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
|
# 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
|
# to be updated to do this properly and I thought it'd fit into its neat
|
||||||
# little function
|
# little function
|
||||||
self.removePiece(capture)
|
self.handleCapture(move)
|
||||||
# Update the positional metadata of the moving piece
|
# Update the positional metadata of the moving piece
|
||||||
case move.piece.color:
|
self.movePiece(move)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
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
|
## for the active color's king
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
return self.isAttacked(self.pieces.white.king)
|
return self.isAttacked(self.position.pieces.white.king)
|
||||||
of Black:
|
of Black:
|
||||||
return self.isAttacked(self.pieces.black.king)
|
return self.isAttacked(self.position.pieces.black.king)
|
||||||
of None:
|
of None:
|
||||||
case self.turn:
|
case self.getActiveColor():
|
||||||
of White:
|
of White:
|
||||||
return self.isAttacked(self.pieces.white.king)
|
return self.isAttacked(self.position.pieces.white.king)
|
||||||
of Black:
|
of Black:
|
||||||
return self.isAttacked(self.pieces.black.king)
|
return self.isAttacked(self.position.pieces.black.king)
|
||||||
else:
|
else:
|
||||||
# Unreachable
|
# Unreachable
|
||||||
discard
|
discard
|
||||||
|
@ -909,46 +960,128 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
## performing legality checks on the given move. Can
|
## performing legality checks on the given move. Can
|
||||||
## be used in performance-critical paths where
|
## be used in performance-critical paths where
|
||||||
## a move is already known to be legal
|
## 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
|
# En passant is possible only immediately after the
|
||||||
# pawn has moved
|
# pawn has moved
|
||||||
if self.enPassantSquare != emptyMove() and self.enPassantSquare.piece.color == self.turn.opposite():
|
if self.position.enPassantSquare != emptyMove() and self.position.enPassantSquare.piece.color == self.getActiveColor().opposite():
|
||||||
self.enPassantSquare = emptyMove()
|
self.position.enPassantSquare = emptyMove()
|
||||||
self.turn = self.turn.opposite()
|
# 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 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 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 checkMove(self: ChessBoard, move: Move): bool =
|
proc checkMove(self: ChessBoard, move: Move): bool =
|
||||||
## Internal function called by makeMove to check a move for legality
|
## 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)
|
# 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
|
# 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
|
return false
|
||||||
var destination = self.grid[move.targetSquare.row, move.targetSquare.col]
|
var destination = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||||
# Destination square is occupied by a piece of the same color as the piece
|
# Destination square is occupied by a piece of the same color as the piece
|
||||||
# being moved: illegal!
|
# being moved: illegal!
|
||||||
if destination.kind != Empty and destination.color == self.turn:
|
if destination.kind != Empty and destination.color == self.getActiveColor():
|
||||||
return false
|
return false
|
||||||
if move notin self.generateMoves(move.startSquare):
|
if move notin self.generateMoves(move.startSquare):
|
||||||
# Piece cannot arrive to destination (blocked,
|
# Piece cannot arrive to destination (blocked,
|
||||||
# pinned, or otherwise invalid move)
|
# pinned, or otherwise invalid move)
|
||||||
return false
|
return false
|
||||||
self.doMove(move)
|
self.doMove(move)
|
||||||
defer: self.undoMove(move)
|
defer: self.undoLastMove()
|
||||||
# Move would reveal an attack
|
# Move would reveal an attack
|
||||||
# on our king: not allowed
|
# on our king: not allowed
|
||||||
if self.inCheck(move.piece.color):
|
if self.inCheck(move.piece.color):
|
||||||
|
@ -962,11 +1095,6 @@ proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||||
result = move
|
result = move
|
||||||
if not self.checkMove(move):
|
if not self.checkMove(move):
|
||||||
return emptyMove()
|
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)
|
self.doMove(result)
|
||||||
|
|
||||||
|
|
||||||
|
@ -986,6 +1114,13 @@ proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.disc
|
||||||
return self.makeMove(result)
|
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 =
|
proc `$`*(self: ChessBoard): string =
|
||||||
result &= "- - - - - - - -"
|
result &= "- - - - - - - -"
|
||||||
|
|
|
@ -10,8 +10,6 @@ when isMainModule:
|
||||||
setControlCHook(proc () {.noconv.} = echo ""; quit(0))
|
setControlCHook(proc () {.noconv.} = echo ""; quit(0))
|
||||||
var
|
var
|
||||||
board = newChessboardFromFEN("rnbqkbnr/8/8/8/8/8/8/RNBQKBNR w KQkq - 0 1")
|
board = newChessboardFromFEN("rnbqkbnr/8/8/8/8/8/8/RNBQKBNR w KQkq - 0 1")
|
||||||
startSquare: string
|
|
||||||
targetSquare: string
|
|
||||||
data: string
|
data: string
|
||||||
move: Move
|
move: Move
|
||||||
|
|
||||||
|
@ -27,12 +25,13 @@ when isMainModule:
|
||||||
except IOError:
|
except IOError:
|
||||||
echo ""
|
echo ""
|
||||||
break
|
break
|
||||||
|
if data == "undo":
|
||||||
|
echo &"Undo: {board.undoLastMove()}"
|
||||||
|
continue
|
||||||
if len(data) != 4:
|
if len(data) != 4:
|
||||||
continue
|
continue
|
||||||
startSquare = data[0..1]
|
|
||||||
targetSquare = data[2..3]
|
|
||||||
try:
|
try:
|
||||||
move = board.makeMove(startSquare, targetSquare)
|
move = board.makeMove(data[0..1], data[2..3])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo &"Error: {getCurrentExceptionMsg()}"
|
echo &"Error: {getCurrentExceptionMsg()}"
|
||||||
if move == emptyMove():
|
if move == emptyMove():
|
||||||
|
|
Loading…
Reference in New Issue