More additions to move generation and initial sliding work
This commit is contained in:
parent
25ebe7f409
commit
de0864c066
|
@ -103,86 +103,6 @@ proc algebraicToPosition*(s: string): Location {.inline.}
|
|||
proc getCapture*(self: ChessBoard, move: Move): Location
|
||||
func emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
|
||||
|
||||
# Movement offsets for the various pieces. They are
|
||||
# different for each color because the board is an 8x8
|
||||
# grid indexed at 0, which means that white is at the
|
||||
# bottom of the grid going up (indexes are decreasing)
|
||||
# and black is at the top of the grid going down (indeces
|
||||
# are increasing). These offsets are needed to take that
|
||||
# into account when doing move generation or checking for
|
||||
# captures
|
||||
proc getMovementOffsets(self: ChessBoard, location: Location): seq[Location] =
|
||||
let piece = self.grid[location.row, location.col]
|
||||
case piece.color:
|
||||
of White:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
# Pawns can move forward one square. In our flipped
|
||||
# board configuration, that means moving up one row
|
||||
# while keeping the column the same
|
||||
if location.row in 1..6 and self.grid[location.row - 1, location.col].color == None:
|
||||
result.add((location.row - 1, location.col))
|
||||
if self.enPassantSquare.piece.color == piece.color.opposite:
|
||||
if abs(self.enPassantSquare.targetSquare.col - location.col) == 1 and abs(self.enPassantSquare.targetSquare.row - location.row) == 1:
|
||||
# Only viable if the piece is on the diagonal of the target
|
||||
result.add(self.enPassantSquare.targetSquare)
|
||||
# They can also move on either diagonal one
|
||||
# square, but only to capture
|
||||
if location.col in 1..6 and location.row in 1..6:
|
||||
if self.grid[location.row + 1, location.col + 1].color == Black:
|
||||
# Top right diagonal (white side)
|
||||
result.add((location.row + 1, location.col + 1))
|
||||
if self.grid[location.row - 1, location.col - 1].color == Black:
|
||||
# Top left diagonal
|
||||
result.add((location.row + 1, location.col + 1))
|
||||
# Pawn is at the right side, can only capture
|
||||
# on the left one
|
||||
elif location.col == 0 and location.row < 7 and self.grid[location.row + 1, location.col + 1].color == Black:
|
||||
result.add((location.row + 1, location.col + 1))
|
||||
# Pawn is at the left side, can only capture
|
||||
# on the right one
|
||||
elif location.col == 7 and location.row < 7 and self.grid[location.row + 1, location.col - 1].color == Black:
|
||||
result.add((location.row - 1, location.col - 1))
|
||||
of Bishop:
|
||||
return @[]
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
# Pawns can move forward one square. In our flipped
|
||||
# board configuration, that means moving down one row
|
||||
# while keeping the column the same
|
||||
if location.row in 1..6 and self.grid[location.row - 1, location.col].color == None:
|
||||
result.add((1, 0))
|
||||
if self.enPassantSquare.piece.color == piece.color.opposite:
|
||||
if abs(self.enPassantSquare.targetSquare.col - location.col) == 1 and abs(self.enPassantSquare.targetSquare.row - location.row) == 1:
|
||||
# Only viable if the piece is on the diagonal of the target
|
||||
result.add(self.enPassantSquare.targetSquare)
|
||||
# They can also move on either diagonal one
|
||||
# square, but only to capture
|
||||
if location.col in 1..6 and location.row in 1..6:
|
||||
if self.grid[location.row - 1, location.col - 1].color == White:
|
||||
# Top right diagonal (black side)
|
||||
result.add((1, 1))
|
||||
if self.grid[location.row + 1, location.col + 1].color == White:
|
||||
# Top left diagonal
|
||||
result.add((-1, -1))
|
||||
# Pawn is at the right side, can only capture
|
||||
# on the left one
|
||||
elif location.col > 0 and location.row > 0 and self.grid[location.row - 1, location.col + 1].color == White:
|
||||
result.add((-1, -1))
|
||||
# Pawn is at the left side, can only capture
|
||||
# on the right one
|
||||
elif location.col == 7 and location.row > 0 and self.grid[location.row + 1, location.col + 1].color == White:
|
||||
result.add((1, 1))
|
||||
of Bishop:
|
||||
return @[]
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
func getStartRow(piece: Piece): int {.inline.} =
|
||||
## Retrieves the starting row of
|
||||
|
@ -205,6 +125,11 @@ func getStartRow(piece: Piece): int {.inline.} =
|
|||
return 0
|
||||
|
||||
|
||||
func getLastRow(color: PieceColor): int {.inline.} =
|
||||
## Retrieves the location of the last
|
||||
## row relative to the given color
|
||||
|
||||
|
||||
proc newChessboard: ChessBoard =
|
||||
## Returns a new, empty chessboard
|
||||
new(result)
|
||||
|
@ -452,65 +377,95 @@ proc getPiece*(self: ChessBoard, square: string): Piece =
|
|||
|
||||
|
||||
proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
## Generates pawn moves
|
||||
## Generates the possible moves for the pawn in the given
|
||||
## location
|
||||
var
|
||||
piece = self.grid[location.row, location.col]
|
||||
locations: seq[Location] = @[]
|
||||
doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}"
|
||||
for offset in self.getMovementOffsets(location):
|
||||
result.add(Move(startSquare: location, targetSquare: offset, piece: self.grid[location.row, location.col]))
|
||||
case piece.color:
|
||||
of White:
|
||||
# Pawns can move forward one square. In our flipped
|
||||
# board configuration, that means moving up one row
|
||||
# while keeping the column the same
|
||||
if location.row in 1..6 and self.grid[location.row - 1, location.col].color == None:
|
||||
locations.add((location.row - 1, location.col))
|
||||
if self.enPassantSquare.piece.color == piece.color.opposite:
|
||||
if abs(self.enPassantSquare.targetSquare.col - location.col) == 1 and abs(self.enPassantSquare.targetSquare.row - location.row) == 1:
|
||||
# Only viable if the piece is on the diagonal of the target
|
||||
locations.add(self.enPassantSquare.targetSquare)
|
||||
# They can also move on either diagonal one
|
||||
# square, but only to capture
|
||||
if location.col in 1..6 and location.row in 1..6:
|
||||
if self.grid[location.row + 1, location.col + 1].color == Black:
|
||||
# Top right diagonal (white side)
|
||||
locations.add((location.row + 1, location.col + 1))
|
||||
if self.grid[location.row - 1, location.col - 1].color == Black:
|
||||
# Top left diagonal
|
||||
locations.add((location.row + 1, location.col + 1))
|
||||
# Pawn is at the right side, can only capture
|
||||
# on the left one
|
||||
elif location.col == 0 and location.row < 7 and self.grid[location.row + 1, location.col + 1].color == Black:
|
||||
locations.add((location.row + 1, location.col + 1))
|
||||
# Pawn is at the left side, can only capture
|
||||
# on the right one
|
||||
elif location.col == 7 and location.row < 7 and self.grid[location.row + 1, location.col - 1].color == Black:
|
||||
locations.add((location.row - 1, location.col - 1))
|
||||
of Black:
|
||||
# Pawns can move forward one square. In our flipped
|
||||
# board configuration, that means moving down one row
|
||||
# while keeping the column the same
|
||||
if location.row in 1..6 and self.grid[location.row - 1, location.col].color == None:
|
||||
locations.add((1, 0))
|
||||
if self.enPassantSquare.piece.color == piece.color.opposite:
|
||||
if abs(self.enPassantSquare.targetSquare.col - location.col) == 1 and abs(self.enPassantSquare.targetSquare.row - location.row) == 1:
|
||||
# Only viable if the piece is on the diagonal of the target
|
||||
locations.add(self.enPassantSquare.targetSquare)
|
||||
# They can also move on either diagonal one
|
||||
# square, but only to capture
|
||||
if location.col in 1..6 and location.row in 1..6:
|
||||
if self.grid[location.row - 1, location.col - 1].color == White:
|
||||
# Top right diagonal (black side)
|
||||
locations.add((1, 1))
|
||||
if self.grid[location.row + 1, location.col + 1].color == White:
|
||||
# Top left diagonal
|
||||
locations.add((-1, -1))
|
||||
# Pawn is at the right side, can only capture
|
||||
# on the left one
|
||||
elif location.col > 0 and location.row > 0 and self.grid[location.row - 1, location.col + 1].color == White:
|
||||
locations.add((-1, -1))
|
||||
# Pawn is at the left side, can only capture
|
||||
# on the right one
|
||||
elif location.col == 7 and location.row > 0 and self.grid[location.row + 1, location.col + 1].color == White:
|
||||
locations.add((1, 1))
|
||||
else:
|
||||
discard
|
||||
for target in locations:
|
||||
if target.row == piece.color.getLastRow():
|
||||
# Generate all promotion moves
|
||||
for promotionType in [PromoteToKnight, PromoteToBishop, PromoteToRook, PromoteToQueen]:
|
||||
result.add(Move(startSquare: location, targetSquare: target, piece: self.grid[location.row, location.col], flag: promotionType))
|
||||
else:
|
||||
result.add(Move(startSquare: location, targetSquare: target, piece: self.grid[location.row, location.col]))
|
||||
|
||||
|
||||
|
||||
proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
## Generates sliding moves
|
||||
## Generates sliding moves for the sliding piece in the given location
|
||||
var
|
||||
square: Piece
|
||||
piece = self.grid[location.row, location.col]
|
||||
newLocation: Location
|
||||
doAssert piece.kind in [Bishop, Rook, Queen], &"generateSlidingMoves called on a {piece.kind}"
|
||||
for offset in self.getMovementOffsets(location):
|
||||
newLocation = (location.row + offset.row, location.col + offset.col)
|
||||
# Keep sliding until there is a friendly piece or a capture in the way
|
||||
while true:
|
||||
square = self.grid[newLocation.row, newLocation.col]
|
||||
# Friendly piece: cannot move any further
|
||||
if square.color == piece.color:
|
||||
break
|
||||
# Empty square or capture: can do!
|
||||
if square.color == None or square.color == piece.color.opposite():
|
||||
result.add(Move(startSquare: location, targetSquare: newLocation, piece: piece))
|
||||
# Continue in this direction
|
||||
newLocation.row += offset.row
|
||||
# Check if we reached the end of the board
|
||||
if newLocation.row < 0 or newLocation.row > 7:
|
||||
break
|
||||
newLocation.col += offset.col
|
||||
if newLocation.col < 0 or newLocation.col > 7:
|
||||
break
|
||||
|
||||
|
||||
|
||||
proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
|
||||
## Returns the list of possible moves for the
|
||||
## piece in the given location
|
||||
let piece = self.grid[location.row, location.col]
|
||||
case piece.color:
|
||||
of White:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
return self.generatePawnMoves(location)
|
||||
of Bishop:
|
||||
return self.generateSlidingMoves(location)
|
||||
else:
|
||||
discard
|
||||
of Black:
|
||||
case piece.kind:
|
||||
of Pawn:
|
||||
return self.generatePawnMoves(location)
|
||||
of Bishop:
|
||||
return self.generateSlidingMoves(location)
|
||||
else:
|
||||
discard
|
||||
case piece.kind:
|
||||
of Queen, Bishop, Rook:
|
||||
return self.generateSlidingMoves(location)
|
||||
of Pawn:
|
||||
return self.generatePawnMoves(location)
|
||||
else:
|
||||
return @[]
|
||||
|
||||
|
@ -537,56 +492,69 @@ proc isCapture*(self: ChessBoard, move: Move): bool {.inline.} =
|
|||
return self.getCapture(move) != emptyLocation()
|
||||
|
||||
|
||||
proc validatePawnMove(self: ChessBoard, move: Move): bool =
|
||||
## Returns true if the given pawn move is allowed
|
||||
## (internal helper to testMoveOffsets)
|
||||
if move.targetSquare.col != move.startSquare.col:
|
||||
# Pawn can only change column in case of capture or en passant
|
||||
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
|
||||
if self.enPassantSquare.targetSquare != move.targetSquare:
|
||||
# We still need to check for captures even if en passant
|
||||
# is possible
|
||||
return self.isCapture(move)
|
||||
# Number of rows traveled
|
||||
var rows: int
|
||||
# Due to our unique board layout, we need to do this nonsense
|
||||
if move.piece.color == White:
|
||||
rows = move.startSquare.row - move.targetSquare.row
|
||||
else:
|
||||
rows = move.targetSquare.row - move.startSquare.row
|
||||
if rows < 0 or rows > 2:
|
||||
# Pawns don't go backwards, I'm afraid. They also can't
|
||||
# go any further than 2 squares
|
||||
return false
|
||||
if rows == 2:
|
||||
# Check if double pawn pushing is possible (only the first
|
||||
# move for each pawn)
|
||||
if move.startSquare.row != move.piece.getStartRow():
|
||||
# Pawn has already moved more than once, double push
|
||||
# is not allowed
|
||||
return false
|
||||
# En passant is now possible
|
||||
let targetSquare: Location = ((if move.piece.color == White: move.targetSquare.row + 1 else: move.targetSquare.row - 1), move.targetSquare.col)
|
||||
self.enPassantSquare = Move(piece: move.piece, startSquare: move.startSquare, targetSquare: targetSquare)
|
||||
# Captures are checked earlier, so we only need to make sure we aren't blocked by
|
||||
# a piece
|
||||
return self.grid[move.targetSquare.row, move.targetSquare.col].kind == Empty
|
||||
|
||||
|
||||
proc validateSlidingMove(self: ChessBoard, move: Move): bool =
|
||||
## Returns true if the given pawn move is allowed
|
||||
## (internal helper to testMoveOffsets)
|
||||
|
||||
var directions: seq[Location]
|
||||
|
||||
|
||||
proc testMoveOffsets(self: ChessBoard, move: Move): bool =
|
||||
## Returns true if the piece in the given
|
||||
## move is allowed to move in the direction
|
||||
## specified. This does not take pins nor checks
|
||||
## into account, but other rules like double pawn
|
||||
## pushes and en passant are validated here. Note
|
||||
## that this is an internal method called by checkMove
|
||||
## and it does not validate whether the target square
|
||||
## is occupied or not (it is assumed the check has been
|
||||
## performed beforehand, like checkMove does)
|
||||
## move is pseudo-legal: this does not take pins
|
||||
## nor checks into account, but other rules like
|
||||
## double pawn pushes and en passant are validated
|
||||
## here. Note that this is an internal method called
|
||||
## by checkMove and it does not validate whether the
|
||||
## target square is occupied or not (it is assumed the
|
||||
## check has been performed beforehand, like checkMove
|
||||
## does)
|
||||
case move.piece.kind:
|
||||
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():
|
||||
# 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
|
||||
if self.enPassantSquare.targetSquare != move.targetSquare:
|
||||
# We still need to check for captures even if en passant
|
||||
# is possible
|
||||
return self.isCapture(move)
|
||||
# Number of rows traveled
|
||||
var rows: int
|
||||
# Due to our unique board layout, we need to do this nonsense
|
||||
if move.piece.color == White:
|
||||
rows = move.startSquare.row - move.targetSquare.row
|
||||
else:
|
||||
rows = move.targetSquare.row - move.startSquare.row
|
||||
if rows < 0 or rows > 2:
|
||||
# Pawns don't go backwards, I'm afraid. They also can't
|
||||
# go any further than 2 squares
|
||||
return false
|
||||
if rows == 2:
|
||||
# Check if double pawn pushing is possible (only the first
|
||||
# move for each pawn)
|
||||
if move.startSquare.row != move.piece.getStartRow():
|
||||
# Pawn has already moved more than once, double push
|
||||
# is not allowed
|
||||
return false
|
||||
# En passant is now possible
|
||||
let targetSquare: Location = ((if move.piece.color == White: move.targetSquare.row + 1 else: move.targetSquare.row - 1), move.targetSquare.col)
|
||||
self.enPassantSquare = Move(piece: move.piece, startSquare: move.startSquare, targetSquare: targetSquare)
|
||||
# Captures are checked earlier, so we only need to make sure we aren't blocked by
|
||||
# a piece
|
||||
return self.grid[move.targetSquare.row, move.targetSquare.col].kind == Empty
|
||||
return self.validatePawnMove(move)
|
||||
of Bishop:
|
||||
discard
|
||||
return self.validateSlidingMove(move)
|
||||
else:
|
||||
return false
|
||||
|
||||
|
|
Loading…
Reference in New Issue