En passant implemented
This commit is contained in:
parent
140f0e61d0
commit
50d9108542
|
@ -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) =
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue