Fixed position handling system
This commit is contained in:
parent
4586b44ec1
commit
a52783fa15
|
@ -100,7 +100,6 @@ type
|
||||||
## A chess board object
|
## A chess board object
|
||||||
grid: Matrix[Piece]
|
grid: Matrix[Piece]
|
||||||
position: Position
|
position: Position
|
||||||
positionIndex: int
|
|
||||||
# List of reached positions
|
# List of reached positions
|
||||||
positions: seq[Position]
|
positions: seq[Position]
|
||||||
|
|
||||||
|
@ -114,7 +113,7 @@ for _ in countup(0, 63):
|
||||||
func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
|
func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
|
||||||
func emptyLocation*: Location {.inline.} = (-1 , -1)
|
func emptyLocation*: Location {.inline.} = (-1 , -1)
|
||||||
func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White)
|
func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White)
|
||||||
proc algebraicToPosition*(s: string): Location {.inline.}
|
proc algebraicToLocation*(s: string): Location {.inline.}
|
||||||
proc getCapture*(self: ChessBoard, move: Move): Location
|
proc getCapture*(self: ChessBoard, move: Move): Location
|
||||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move
|
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move
|
||||||
proc makeMove*(self: ChessBoard, move: Move): Move
|
proc makeMove*(self: ChessBoard, move: Move): Move
|
||||||
|
@ -208,6 +207,11 @@ proc getCastlingInformation*(self: ChessBoard): tuple[queen, king: bool] =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
|
proc getEnPassantTarget*(self: ChessBoard): Location =
|
||||||
|
## Returns the current en passant target square
|
||||||
|
return self.position.enPassantSquare.targetSquare
|
||||||
|
|
||||||
|
|
||||||
func getStartRow(piece: Piece): int {.inline.} =
|
func getStartRow(piece: Piece): int {.inline.} =
|
||||||
## Retrieves the starting row of
|
## Retrieves the starting row of
|
||||||
## the given piece inside our 8x8
|
## the given piece inside our 8x8
|
||||||
|
@ -251,7 +255,6 @@ proc newChessboard: ChessBoard =
|
||||||
move: emptyMove(),
|
move: emptyMove(),
|
||||||
turn: White)
|
turn: White)
|
||||||
|
|
||||||
result.positionIndex = 0
|
|
||||||
|
|
||||||
|
|
||||||
proc newChessboardFromFEN*(state: string): ChessBoard =
|
proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
|
@ -371,7 +374,7 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
||||||
# Field is already uninitialized to the correct state
|
# Field is already uninitialized to the correct state
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
result.position.enPassantSquare.targetSquare = state[index..index+1].algebraicToPosition()
|
result.position.enPassantSquare.targetSquare = state[index..index+1].algebraicToLocation()
|
||||||
# Just for cleanliness purposes, we fill in the other metadata as
|
# Just for cleanliness purposes, we fill in the other metadata as
|
||||||
# well
|
# well
|
||||||
result.position.enPassantSquare.piece.color = result.getActiveColor()
|
result.position.enPassantSquare.piece.color = result.getActiveColor()
|
||||||
|
@ -464,7 +467,7 @@ func rankToColumn(rank: int): int =
|
||||||
return indeces[rank - 1]
|
return indeces[rank - 1]
|
||||||
|
|
||||||
|
|
||||||
proc algebraicToPosition*(s: string): Location {.inline.} =
|
proc algebraicToLocation*(s: string): Location =
|
||||||
## Converts a square location from algebraic
|
## Converts a square location from algebraic
|
||||||
## notation to its corresponding row and column
|
## notation to its corresponding row and column
|
||||||
## in the chess grid (0 indexed)
|
## in the chess grid (0 indexed)
|
||||||
|
@ -483,10 +486,16 @@ proc algebraicToPosition*(s: string): Location {.inline.} =
|
||||||
return (rank, file)
|
return (rank, file)
|
||||||
|
|
||||||
|
|
||||||
|
proc locationToAlgebraic*(loc: Location): string =
|
||||||
|
## Converts a location from our internal row, column
|
||||||
|
## notation to a square in algebraic notation
|
||||||
|
return &"{char(uint8(loc.col) + uint8('a'))}{char(uint8(loc.row) + uint8('0'))}"
|
||||||
|
|
||||||
|
|
||||||
proc getPiece*(self: ChessBoard, square: string): Piece =
|
proc getPiece*(self: ChessBoard, square: string): Piece =
|
||||||
## Gets the piece on the given square
|
## Gets the piece on the given square
|
||||||
## in algebraic notation
|
## in algebraic notation
|
||||||
let loc = square.algebraicToPosition()
|
let loc = square.algebraicToLocation()
|
||||||
return self.grid[loc.row, loc.col]
|
return self.grid[loc.row, loc.col]
|
||||||
|
|
||||||
|
|
||||||
|
@ -570,7 +579,7 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
continue
|
continue
|
||||||
# Move is just a pawn push
|
# Move is just a pawn push
|
||||||
result.add(Move(startSquare: location, targetSquare: newLocation, piece: piece))
|
result.add(Move(startSquare: location, targetSquare: newLocation, piece: piece))
|
||||||
|
|
||||||
|
|
||||||
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||||
## Generates moves for the sliding piece in the given location
|
## Generates moves for the sliding piece in the given location
|
||||||
|
@ -695,7 +704,7 @@ 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.algebraicToLocation()
|
||||||
for move in self.position.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)
|
||||||
|
@ -707,7 +716,7 @@ proc getAttackers*(self: ChessBoard, square: string): seq[Piece] =
|
||||||
proc getAttackersFor*(self: ChessBoard, square: string, color: PieceColor): seq[Piece] =
|
proc getAttackersFor*(self: ChessBoard, square: string, color: PieceColor): seq[Piece] =
|
||||||
## Returns all the attackers of the given square
|
## Returns all the attackers of the given square
|
||||||
## for the given color
|
## for the given color
|
||||||
let loc = square.algebraicToPosition()
|
let loc = square.algebraicToLocation()
|
||||||
case color:
|
case color:
|
||||||
of White:
|
of White:
|
||||||
for move in self.position.attacked.black:
|
for move in self.position.attacked.black:
|
||||||
|
@ -755,7 +764,7 @@ proc isAttacked*(self: ChessBoard, loc: Location): bool =
|
||||||
proc isAttacked*(self: ChessBoard, square: string): bool =
|
proc isAttacked*(self: ChessBoard, square: string): bool =
|
||||||
## Returns whether the given square is attacked
|
## Returns whether the given square is attacked
|
||||||
## by its opponent
|
## by its opponent
|
||||||
return self.isAttacked(square.algebraicToPosition())
|
return self.isAttacked(square.algebraicToLocation())
|
||||||
|
|
||||||
|
|
||||||
proc updateAttackedSquares(self: ChessBoard) =
|
proc updateAttackedSquares(self: ChessBoard) =
|
||||||
|
@ -850,14 +859,6 @@ 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) =
|
proc movePiece(self: ChessBoard, move: Move) =
|
||||||
## Internal helper to move a piece. Does
|
## Internal helper to move a piece. Does
|
||||||
## not update attacked squares, just position
|
## not update attacked squares, just position
|
||||||
|
@ -925,11 +926,7 @@ proc updatePositions(self: ChessBoard, move: Move) =
|
||||||
## the pieces on the board after a move
|
## the pieces on the board after a move
|
||||||
let capture = self.getCapture(move)
|
let capture = self.getCapture(move)
|
||||||
if capture != emptyLocation():
|
if capture != emptyLocation():
|
||||||
# Move has captured a piece: remove it as well. We call a helper instead
|
self.position.captured = self.grid[capture.row, capture.col]
|
||||||
# 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.handleCapture(move)
|
|
||||||
# Update the positional metadata of the moving piece
|
# Update the positional metadata of the moving piece
|
||||||
self.movePiece(move)
|
self.movePiece(move)
|
||||||
|
|
||||||
|
@ -970,10 +967,6 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
self.position.halfMoveClock = 0
|
self.position.halfMoveClock = 0
|
||||||
if (self.position.halfMoveClock and 1) == 0: # Equivalent to (x mod 2) == 0, just much faster
|
if (self.position.halfMoveClock and 1) == 0: # Equivalent to (x mod 2) == 0, just much faster
|
||||||
inc(self.position.fullMoveCount)
|
inc(self.position.fullMoveCount)
|
||||||
# En passant is possible only immediately after the
|
|
||||||
# pawn has moved
|
|
||||||
if self.position.enPassantSquare != emptyMove() and self.position.enPassantSquare.piece.color == self.getActiveColor().opposite():
|
|
||||||
self.position.enPassantSquare = emptyMove()
|
|
||||||
# TODO: Castling
|
# TODO: Castling
|
||||||
|
|
||||||
self.position.move = move
|
self.position.move = move
|
||||||
|
@ -987,16 +980,24 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
# Create new position with
|
# Create new position with
|
||||||
var newPos = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
var newPos = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||||
captured: emptyPiece(),
|
captured: emptyPiece(),
|
||||||
turn: self.position.turn.opposite(),
|
turn: self.getActiveColor().opposite,
|
||||||
# Inherit values from current position
|
# Inherit values from current position
|
||||||
# (they are already up to date by this point)
|
# (they are already up to date by this point)
|
||||||
castling: self.position.castling,
|
castling: self.position.castling,
|
||||||
enPassantSquare: self.position.enPassantSquare,
|
attacked: self.position.attacked,
|
||||||
attacked: (@[], @[])
|
# Updated at the next call to doMove()
|
||||||
|
move: emptyMove(),
|
||||||
|
pieces: self.position.pieces,
|
||||||
)
|
)
|
||||||
|
# Check for double pawn push
|
||||||
|
if move.piece.kind == Pawn and abs(move.startSquare.row - move.targetSquare.row) == 2:
|
||||||
|
newPos.enPassantSquare = Move(piece: move.piece,
|
||||||
|
startSquare: (move.startSquare.row, move.startSquare.col),
|
||||||
|
targetSquare: move.targetSquare + move.piece.bottomSide())
|
||||||
|
else:
|
||||||
|
newPos.enPassantSquare = emptyMove()
|
||||||
|
|
||||||
self.position = newPos
|
self.position = newPos
|
||||||
inc(self.positionIndex)
|
|
||||||
|
|
||||||
|
|
||||||
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||||
|
@ -1042,43 +1043,41 @@ proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||||
|
|
||||||
|
|
||||||
proc undoLastMove*(self: ChessBoard): Move {.discardable.} =
|
proc undoLastMove*(self: ChessBoard): Move {.discardable.} =
|
||||||
## Undoes the last move, restoring any captured pieces,
|
## Undoes the last move, restoring the previous position.
|
||||||
## as well castling and en passant status. If there are
|
## If there are no positions to roll back to to, this is a
|
||||||
## no moves to undo, this is a no-op. Returns the move
|
## no-op. Returns the move that was performed (may be empty)
|
||||||
## that was performed (which may be an empty move)
|
|
||||||
result = emptyMove()
|
result = emptyMove()
|
||||||
if self.positions.len() == 0:
|
if self.positions.len() == 0:
|
||||||
return
|
return
|
||||||
let positionIndex = max(0, self.positionIndex - 1)
|
var
|
||||||
if positionIndex in 0..self.positions.high():
|
previous = self.positions[^1]
|
||||||
self.positionIndex = positionIndex
|
oppositeMove = Move(piece: previous.move.piece, targetSquare: previous.move.startSquare, startSquare: previous.move.targetSquare)
|
||||||
self.position = self.positions[positionIndex]
|
self.removePiece(previous.move.startSquare)
|
||||||
let
|
self.position = previous
|
||||||
currentMove = self.position.move
|
if previous.move != emptyMove():
|
||||||
oppositeMove = Move(piece: currentMove.piece, targetSquare: currentMove.startSquare, startSquare: currentMove.targetSquare)
|
self.spawnPiece(previous.move.startSquare, previous.move.piece)
|
||||||
self.spawnPiece(currentMove.startSquare, currentMove.piece)
|
|
||||||
if self.position.captured != emptyPiece():
|
|
||||||
self.spawnPiece(self.position.move.targetSquare, self.position.captured)
|
|
||||||
self.updateAttackedSquares()
|
self.updateAttackedSquares()
|
||||||
self.updatePositions(oppositeMove)
|
self.updatePositions(oppositeMove)
|
||||||
return self.position.move
|
if previous.captured != emptyPiece():
|
||||||
|
self.spawnPiece(previous.move.targetSquare, previous.captured)
|
||||||
|
discard self.positions.pop()
|
||||||
|
return self.position.move
|
||||||
|
|
||||||
|
|
||||||
|
proc isLegal(self: ChessBoard, move: Move): bool =
|
||||||
|
## Returns whether the given move is legal
|
||||||
|
|
||||||
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)
|
# 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's not this player's turn to move
|
||||||
if move.piece.kind == Empty or move.piece.color != self.getActiveColor():
|
if (move.piece.kind == Empty and move.targetSquare != self.getEnPassantTarget()) 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 friendly piece
|
||||||
# being moved: illegal!
|
|
||||||
if destination.kind != Empty and destination.color == self.getActiveColor():
|
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.undoLastMove()
|
defer: self.undoLastMove()
|
||||||
|
@ -1093,12 +1092,11 @@ proc checkMove(self: ChessBoard, move: Move): bool =
|
||||||
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||||
## Like the other makeMove(), but with a Move object
|
## Like the other makeMove(), but with a Move object
|
||||||
result = move
|
result = move
|
||||||
if not self.checkMove(move):
|
if not self.isLegal(move):
|
||||||
return emptyMove()
|
return emptyMove()
|
||||||
self.doMove(result)
|
self.doMove(result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.discardable.} =
|
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.discardable.} =
|
||||||
## Makes a move on the board from the chosen start square to
|
## Makes a move on the board from the chosen start square to
|
||||||
## the chosen target square, ensuring it is legal (turns are
|
## the chosen target square, ensuring it is legal (turns are
|
||||||
|
@ -1108,8 +1106,8 @@ proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.disc
|
||||||
## illegal, but the move's piece kind will be Empty (its color will be None
|
## illegal, but the move's piece kind will be Empty (its color will be None
|
||||||
## too) and the locations will both be set to the tuple (-1, -1)
|
## too) and the locations will both be set to the tuple (-1, -1)
|
||||||
var
|
var
|
||||||
startLocation = startSquare.algebraicToPosition()
|
startLocation = startSquare.algebraicToLocation()
|
||||||
targetLocation = targetSquare.algebraicToPosition()
|
targetLocation = targetSquare.algebraicToLocation()
|
||||||
result = Move(startSquare: startLocation, targetSquare: targetLocation, piece: self.grid[startLocation.row, startLocation.col])
|
result = Move(startSquare: startLocation, targetSquare: targetLocation, piece: self.grid[startLocation.row, startLocation.col])
|
||||||
return self.makeMove(result)
|
return self.makeMove(result)
|
||||||
|
|
||||||
|
@ -1120,8 +1118,6 @@ proc makeMove*(self: ChessBoard, startSquare, targetSquare: Location): Move {.di
|
||||||
return self.makeMove(result)
|
return self.makeMove(result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc `$`*(self: ChessBoard): string =
|
proc `$`*(self: ChessBoard): string =
|
||||||
result &= "- - - - - - - -"
|
result &= "- - - - - - - -"
|
||||||
for i, row in self.grid:
|
for i, row in self.grid:
|
||||||
|
|
|
@ -9,7 +9,7 @@ import std/strutils
|
||||||
when isMainModule:
|
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/2p/8/8/8/8/P7/RNBQKBNR w KQkq - 0 1")
|
||||||
data: string
|
data: string
|
||||||
move: Move
|
move: Move
|
||||||
|
|
||||||
|
@ -17,9 +17,17 @@ when isMainModule:
|
||||||
while true:
|
while true:
|
||||||
echo &"{board.pretty()}"
|
echo &"{board.pretty()}"
|
||||||
echo &"Turn: {board.getActiveColor()}"
|
echo &"Turn: {board.getActiveColor()}"
|
||||||
|
stdout.write(&"En passant target: ")
|
||||||
|
if board.getEnPassantTarget() != emptyLocation():
|
||||||
|
echo board.getEnPassantTarget()
|
||||||
|
else:
|
||||||
|
echo "None"
|
||||||
|
stdout.write(&"Check: ")
|
||||||
if board.inCheck():
|
if board.inCheck():
|
||||||
echo &"Check!"
|
echo &"Yes"
|
||||||
stdout.write("Move (from, to) -> ")
|
else:
|
||||||
|
echo "No"
|
||||||
|
stdout.write("\nMove -> ")
|
||||||
try:
|
try:
|
||||||
data = readLine(stdin).strip(chars={'\0', ' '})
|
data = readLine(stdin).strip(chars={'\0', ' '})
|
||||||
except IOError:
|
except IOError:
|
||||||
|
|
Loading…
Reference in New Issue