En passant implemented
This commit is contained in:
parent
140f0e61d0
commit
50d9108542
|
@ -49,7 +49,7 @@ type
|
||||||
kind*: PieceKind
|
kind*: PieceKind
|
||||||
|
|
||||||
|
|
||||||
Move* = object
|
Move* = ref object
|
||||||
## A chess move
|
## A chess move
|
||||||
piece*: Piece
|
piece*: Piece
|
||||||
startSquare*: Location
|
startSquare*: Location
|
||||||
|
@ -88,10 +88,13 @@ 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 emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
|
|
||||||
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 algebraicToPosition*(s: string): Location {.inline.}
|
||||||
func `==`(a, b: Location): bool {.inline.} = a.row == b.row and a.col == b.col
|
func `==`(a, b: Location): bool {.inline.} = a.row == b.row and a.col == b.col
|
||||||
|
proc getCapture*(self: ChessBoard, move: Move): Location
|
||||||
|
|
||||||
|
var emptyMove = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc newChessboard: ChessBoard =
|
proc newChessboard: ChessBoard =
|
||||||
|
@ -100,7 +103,7 @@ proc newChessboard: ChessBoard =
|
||||||
# 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.attacked = (@[], @[])
|
||||||
result.enPassantSquare = emptyMove()
|
result.enPassantSquare = emptyMove
|
||||||
result.turn = White
|
result.turn = White
|
||||||
|
|
||||||
|
|
||||||
|
@ -339,11 +342,27 @@ proc getPiece*(self: ChessBoard, square: string): Piece =
|
||||||
return self.grid[loc.row, loc.col]
|
return self.grid[loc.row, loc.col]
|
||||||
|
|
||||||
|
|
||||||
|
proc getCapture*(self: ChessBoard, move: Move): Location =
|
||||||
|
## Returns the location that would be captured if this
|
||||||
|
## move were played on the board, taking en passant and
|
||||||
|
## other things into account. An empty location is returned
|
||||||
|
## if no piece is captured by the given move
|
||||||
|
result = emptyLocation()
|
||||||
|
let target = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||||
|
if target.color == None:
|
||||||
|
if move.targetSquare != self.enPassantSquare.targetSquare:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
return (self.enPassantSquare.targetSquare.row + 1,
|
||||||
|
self.enPassantSquare.targetSquare.col)
|
||||||
|
if target.color == move.piece.color.opposite():
|
||||||
|
return move.targetSquare
|
||||||
|
|
||||||
|
|
||||||
proc isCapture*(self: ChessBoard, move: Move): bool {.inline.} =
|
proc isCapture*(self: ChessBoard, move: Move): bool {.inline.} =
|
||||||
## Returns whether the given move is a capture
|
## Returns whether the given move is a capture
|
||||||
## or not
|
## or not
|
||||||
let target = self.grid[move.targetSquare.row, move.targetSquare.col]
|
return self.getCapture(move) != emptyLocation()
|
||||||
return target.color == move.piece.color.opposite()
|
|
||||||
|
|
||||||
|
|
||||||
proc testMoveOffsets(self: ChessBoard, move: Move): bool =
|
proc testMoveOffsets(self: ChessBoard, move: Move): bool =
|
||||||
|
@ -360,13 +379,13 @@ proc testMoveOffsets(self: ChessBoard, move: Move): bool =
|
||||||
of Pawn:
|
of Pawn:
|
||||||
if move.targetSquare.col != move.startSquare.col:
|
if move.targetSquare.col != move.startSquare.col:
|
||||||
# Pawn can only change column in case of capture or en passant
|
# Pawn can only change column in case of capture or en passant
|
||||||
if self.enPassantSquare == emptyMove():
|
if self.enPassantSquare == emptyMove:
|
||||||
# No en passant possible, only possibility
|
# No en passant possible, only possibility
|
||||||
# is a capture
|
# is a capture
|
||||||
return self.isCapture(move)
|
return self.isCapture(move)
|
||||||
# En passant is possible, check if the destination is
|
# En passant is possible, check if the destination is
|
||||||
# its target square
|
# its target square
|
||||||
elif self.enPassantSquare.targetSquare != move.targetSquare:
|
if self.enPassantSquare.targetSquare != move.targetSquare:
|
||||||
# We still need to check for captures even if en passant
|
# We still need to check for captures even if en passant
|
||||||
# is possible
|
# is possible
|
||||||
return self.isCapture(move)
|
return self.isCapture(move)
|
||||||
|
@ -383,12 +402,15 @@ proc testMoveOffsets(self: ChessBoard, move: Move): bool =
|
||||||
return false
|
return false
|
||||||
if rows == 2:
|
if rows == 2:
|
||||||
# Check if double pawn pushing is possible (only the first
|
# Check if double pawn pushing is possible (only the first
|
||||||
# move)
|
# move for each pawn)
|
||||||
let startRow = if move.piece.color == White: 6 else: 1
|
let startRow = if move.piece.color == White: 6 else: 1
|
||||||
if move.startSquare.row != startRow:
|
if move.startSquare.row != startRow:
|
||||||
# Pawn has already moved more than once, double push
|
# Pawn has already moved more than once, double push
|
||||||
# is not allowed
|
# is not allowed
|
||||||
return false
|
return false
|
||||||
|
# En passant is now possible
|
||||||
|
self.enPassantSquare = Move(piece: move.piece, startSquare: move.startSquare,
|
||||||
|
targetSquare: (move.targetSquare.row - 1, move.targetSquare.col))
|
||||||
return true
|
return true
|
||||||
else:
|
else:
|
||||||
return false
|
return false
|
||||||
|
@ -408,6 +430,7 @@ proc removePiece(self: ChessBoard, location: Location) =
|
||||||
## Removes a piece from the board, updating necessary
|
## Removes a piece from the board, updating necessary
|
||||||
## metadata
|
## metadata
|
||||||
var piece = self.grid[location.row, location.col]
|
var piece = self.grid[location.row, location.col]
|
||||||
|
self.grid[location.row, location.col] = emptyPiece()
|
||||||
case piece.color:
|
case piece.color:
|
||||||
of White:
|
of White:
|
||||||
case piece.kind:
|
case piece.kind:
|
||||||
|
@ -451,12 +474,13 @@ proc updatePositions(self: ChessBoard, move: Move) =
|
||||||
|
|
||||||
# Empty out the starting square
|
# Empty out the starting square
|
||||||
self.grid[move.startSquare.row, move.startSquare.col] = emptyPiece()
|
self.grid[move.startSquare.row, move.startSquare.col] = emptyPiece()
|
||||||
if self.isCapture(move):
|
let capture = self.getCapture(move)
|
||||||
# Move has captured a piece: remove the destination square' piece as well.
|
if capture != emptyLocation():
|
||||||
|
# Move has captured a piece: remove the destination square's piece as well.
|
||||||
# We call a helper instead of doing it ourselves because there's a bunch
|
# We call a helper instead of doing it ourselves because there's a bunch
|
||||||
# of metadata that needs to be updated to do this properly and I thought
|
# of metadata that needs to be updated to do this properly and I thought
|
||||||
# it'd fit into its neat little function
|
# it'd fit into its neat little function
|
||||||
self.removePiece(move.targetSquare)
|
self.removePiece(capture)
|
||||||
# Update the positional metadata of the moving piece
|
# Update the positional metadata of the moving piece
|
||||||
case move.piece.color:
|
case move.piece.color:
|
||||||
of White:
|
of White:
|
||||||
|
@ -514,42 +538,50 @@ proc doMove(self: ChessBoard, move: Move) =
|
||||||
## a move is already known to be legal
|
## a move is already known to be legal
|
||||||
self.updatePositions(move)
|
self.updatePositions(move)
|
||||||
self.updateAttackedSquares()
|
self.updateAttackedSquares()
|
||||||
|
# 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()
|
self.turn = self.turn.opposite()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc checkMove(self: ChessBoard, startSquare, targetSquare: string): Move =
|
proc checkMove(self: ChessBoard, startSquare, targetSquare: string): Move =
|
||||||
## Internal function called by makeMove to check a move for legality
|
## Internal function called by makeMove to check a move for legality
|
||||||
var pieceToMove = self.getPiece(startSquare)
|
var pieceToMove = self.getPiece(startSquare)
|
||||||
# Start square doesn't contain a piece or it is of the wrong color for which
|
# Start square doesn't contain a piece (and it isn't the en passant square)
|
||||||
# turn it is to move
|
# or it is of the wrong color for which turn it is to move
|
||||||
if pieceToMove.kind == Empty or pieceToMove.color != self.turn:
|
if pieceToMove.kind == Empty or pieceToMove.color != self.turn:
|
||||||
return emptyMove()
|
return emptyMove
|
||||||
var destination = self.getPiece(targetSquare)
|
var destination = self.getPiece(targetSquare)
|
||||||
# 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.turn:
|
||||||
return emptyMove()
|
return emptyMove
|
||||||
var
|
var
|
||||||
startLocation = startSquare.algebraicToPosition()
|
startLocation = startSquare.algebraicToPosition()
|
||||||
targetLocation = targetSquare.algebraicToPosition()
|
targetLocation = targetSquare.algebraicToPosition()
|
||||||
result = Move(startSquare: startLocation, targetSquare: targetLocation, piece: pieceToMove)
|
result = Move(startSquare: startLocation, targetSquare: targetLocation, piece: pieceToMove)
|
||||||
if not self.testMoveOffsets(result):
|
if not self.testMoveOffsets(result):
|
||||||
# Piece cannot move in this direction
|
# Piece cannot arrive to destination (either
|
||||||
return emptyMove()
|
# because it is blocked or because the moving
|
||||||
|
# pattern is incorrect)
|
||||||
|
return emptyMove
|
||||||
# TODO: Check for checks and pins (moves are currently pseudo-legal)
|
# TODO: Check for checks and pins (moves are currently pseudo-legal)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move =
|
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
|
||||||
## taken into account). This function returns a Move object: if the move
|
## taken into account!). This function returns a Move object: if the move
|
||||||
## is legal and has been performed, the fields will be populated properly.
|
## is legal and has been performed, the fields will be populated properly.
|
||||||
## For efficiency purposes, no exceptions are raised if the move is
|
## For efficiency purposes, no exceptions are raised if the move is
|
||||||
## 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)
|
||||||
|
|
||||||
result = self.checkMove(startSquare, targetSquare)
|
result = self.checkMove(startSquare, targetSquare)
|
||||||
if result == emptyMove():
|
if result == emptyMove:
|
||||||
return
|
return
|
||||||
self.doMove(result)
|
self.doMove(result)
|
||||||
|
|
||||||
|
@ -561,7 +593,7 @@ proc `$`*(self: ChessBoard): string =
|
||||||
result &= "\n"
|
result &= "\n"
|
||||||
for piece in row:
|
for piece in row:
|
||||||
if piece.kind == Empty:
|
if piece.kind == Empty:
|
||||||
result &= " "
|
result &= "x "
|
||||||
continue
|
continue
|
||||||
if piece.color == White:
|
if piece.color == White:
|
||||||
result &= &"{char(piece.kind).toUpperAscii()} "
|
result &= &"{char(piece.kind).toUpperAscii()} "
|
||||||
|
@ -572,6 +604,33 @@ proc `$`*(self: ChessBoard): string =
|
||||||
result &= "\na b c d e f g h"
|
result &= "\na b c d e f g h"
|
||||||
|
|
||||||
|
|
||||||
|
proc pretty*(self: ChessBoard): string =
|
||||||
|
## Returns a colorized version of the
|
||||||
|
## board for easier visualization
|
||||||
|
result &= "- - - - - - - -"
|
||||||
|
|
||||||
|
for i, row in self.grid:
|
||||||
|
result &= "\n"
|
||||||
|
for j, piece in row:
|
||||||
|
if piece.kind == Empty:
|
||||||
|
result &= "\x1b[36;1mx"
|
||||||
|
# Avoids the color overflowing
|
||||||
|
# onto the numbers
|
||||||
|
if j < 7:
|
||||||
|
result &= " \x1b[0m"
|
||||||
|
else:
|
||||||
|
result &= "\x1b[0m "
|
||||||
|
continue
|
||||||
|
if piece.color == White:
|
||||||
|
result &= &"\x1b[37;1m{char(piece.kind).toUpperAscii()}\x1b[0m "
|
||||||
|
else:
|
||||||
|
result &= &"\x1b[30;1m{char(piece.kind)} "
|
||||||
|
result &= &"\x1b[33;1m{rankToColumn(i + 1) + 1}\x1b[0m"
|
||||||
|
|
||||||
|
result &= "\n- - - - - - - -"
|
||||||
|
result &= "\n\x1b[31;1ma b c d e f g h"
|
||||||
|
result &= "\x1b[0m"
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
|
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
import board as chess
|
import board as chess
|
||||||
|
|
||||||
|
# En passant my beloved
|
||||||
var board = newDefaultChessboard()
|
var board = newDefaultChessboard()
|
||||||
discard board.makeMove("a2", "a4")
|
board.makeMove("a2", "a4")
|
||||||
discard board.makeMove("b7", "b5")
|
echo board.pretty()
|
||||||
discard board.makeMove("a4", "b5")
|
board.makeMove("h7", "h5")
|
||||||
|
echo board.pretty()
|
||||||
|
board.makeMove("a4", "a5")
|
||||||
|
echo board.pretty()
|
||||||
|
board.makeMove("b7", "b5")
|
||||||
|
echo board.pretty()
|
||||||
|
board.makeMove("a5", "b6")
|
||||||
|
echo board.pretty()
|
||||||
|
|
Loading…
Reference in New Issue