En passant implemented

This commit is contained in:
Mattia Giambirtone 2023-10-15 22:46:22 +02:00
parent 140f0e61d0
commit 50d9108542
2 changed files with 92 additions and 27 deletions

View File

@ -49,7 +49,7 @@ type
kind*: PieceKind
Move* = object
Move* = ref object
## A chess move
piece*: Piece
startSquare*: Location
@ -88,10 +88,13 @@ for _ in countup(0, 63):
func emptyPiece*: Piece {.inline.} = Piece(kind: Empty, color: None)
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)
proc algebraicToPosition*(s: string): Location {.inline.}
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 =
@ -100,7 +103,7 @@ proc newChessboard: ChessBoard =
# Turns our flat sequence into an 8x8 grid
result.grid = newMatrixFromSeq[Piece](empty, (8, 8))
result.attacked = (@[], @[])
result.enPassantSquare = emptyMove()
result.enPassantSquare = emptyMove
result.turn = White
@ -339,11 +342,27 @@ proc getPiece*(self: ChessBoard, square: string): Piece =
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.} =
## Returns whether the given move is a capture
## or not
let target = self.grid[move.targetSquare.row, move.targetSquare.col]
return target.color == move.piece.color.opposite()
return self.getCapture(move) != emptyLocation()
proc testMoveOffsets(self: ChessBoard, move: Move): bool =
@ -360,13 +379,13 @@ proc testMoveOffsets(self: ChessBoard, move: Move): bool =
of Pawn:
if move.targetSquare.col != move.startSquare.col:
# 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
# is a capture
return self.isCapture(move)
# En passant is possible, check if the destination is
# 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
# is possible
return self.isCapture(move)
@ -383,12 +402,15 @@ proc testMoveOffsets(self: ChessBoard, move: Move): bool =
return false
if rows == 2:
# 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
if move.startSquare.row != startRow:
# Pawn has already moved more than once, double push
# is not allowed
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
else:
return false
@ -408,6 +430,7 @@ proc removePiece(self: ChessBoard, location: Location) =
## Removes a piece from the board, updating necessary
## metadata
var piece = self.grid[location.row, location.col]
self.grid[location.row, location.col] = emptyPiece()
case piece.color:
of White:
case piece.kind:
@ -451,12 +474,13 @@ proc updatePositions(self: ChessBoard, move: Move) =
# Empty out the starting square
self.grid[move.startSquare.row, move.startSquare.col] = emptyPiece()
if self.isCapture(move):
# Move has captured a piece: remove the destination square' piece as well.
let capture = self.getCapture(move)
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
# of metadata that needs to be updated to do this properly and I thought
# it'd fit into its neat little function
self.removePiece(move.targetSquare)
self.removePiece(capture)
# Update the positional metadata of the moving piece
case move.piece.color:
of White:
@ -514,42 +538,50 @@ proc doMove(self: ChessBoard, move: Move) =
## a move is already known to be legal
self.updatePositions(move)
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()
proc checkMove(self: ChessBoard, startSquare, targetSquare: string): Move =
## Internal function called by makeMove to check a move for legality
var pieceToMove = self.getPiece(startSquare)
# Start square doesn't contain a piece or it is of the wrong color for which
# turn it is to move
# 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 pieceToMove.kind == Empty or pieceToMove.color != self.turn:
return emptyMove()
return emptyMove
var destination = self.getPiece(targetSquare)
# 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:
return emptyMove()
return emptyMove
var
startLocation = startSquare.algebraicToPosition()
targetLocation = targetSquare.algebraicToPosition()
targetLocation = targetSquare.algebraicToPosition()
result = Move(startSquare: startLocation, targetSquare: targetLocation, piece: pieceToMove)
if not self.testMoveOffsets(result):
# Piece cannot move in this direction
return emptyMove()
# Piece cannot arrive to destination (either
# because it is blocked or because the moving
# pattern is incorrect)
return emptyMove
# 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
## 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.
## 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
## too) and the locations will both be set to the tuple (-1, -1)
result = self.checkMove(startSquare, targetSquare)
if result == emptyMove():
if result == emptyMove:
return
self.doMove(result)
@ -561,7 +593,7 @@ proc `$`*(self: ChessBoard): string =
result &= "\n"
for piece in row:
if piece.kind == Empty:
result &= " "
result &= "x "
continue
if piece.color == White:
result &= &"{char(piece.kind).toUpperAscii()} "
@ -572,6 +604,33 @@ proc `$`*(self: ChessBoard): string =
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:
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =

View File

@ -1,8 +1,14 @@
import board as chess
# En passant my beloved
var board = newDefaultChessboard()
discard board.makeMove("a2", "a4")
discard board.makeMove("b7", "b5")
discard board.makeMove("a4", "b5")
board.makeMove("a2", "a4")
echo board.pretty()
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()