Fix diagonal checks for black

This commit is contained in:
Mattia Giambirtone 2023-10-17 12:08:07 +02:00
parent 17f15e682c
commit e782935fd7
2 changed files with 138 additions and 80 deletions

View File

@ -109,7 +109,7 @@ proc generateMoves(self: ChessBoard, location: Location): seq[Move]
func topLeftDiagonal(piece: Piece): Location {.inline.} = (if piece.color == White: (-1, -1) else: (1, 1))
func topRightDiagonal(piece: Piece): Location {.inline.} = (if piece.color == White: (-1, 1) else: (1, -1))
func bottomLeftDiagonal(piece: Piece): Location {.inline.} = (if piece.color == White: (-1, 1) else: (1, -1))
func bottomLeftDiagonal(piece: Piece): Location {.inline.} = (if piece.color == White: (1, -1) else: (-1, 1))
func bottomRightDiagonal(piece: Piece): Location {.inline.} = (if piece.color == White: (1, 1) else: (-1, -1))
func leftSide(piece: Piece): Location {.inline.} = (if piece.color == White: (0, -1) else: (0, 1))
func rightSide(piece: Piece): Location {.inline.} = (if piece.color == White: (0, 1) else: (0, -1))
@ -117,7 +117,6 @@ func topSide(piece: Piece): Location {.inline.} = (if piece.color == White: (-1,
func bottomSide(piece: Piece): Location {.inline.} = (if piece.color == White: (1, 0) else: (-1, 0))
func forward(piece: Piece): Location {.inline.} = (if piece.color == Black: (1, 0) else: (-1, 0))
func doublePush(piece: Piece): Location {.inline.} = (if piece.color == Black: (2, 0) else: (-2, 0))
proc testMoveOffsets(self: ChessBoard, move: Move): bool
proc getActiveColor*(self: ChessBoard): PieceColor =
@ -519,14 +518,48 @@ proc generateSlidingMoves(self: ChessBoard, location: Location): seq[Move] =
if otherPiece.color == piece.color:
break
if otherPiece.color == piece.color.opposite:
# Target square contains an enemy piece: capture
# it and stop going any further
result.add(Move(startSquare: location, targetSquare: square, piece: piece))
break
# Target square is empty
result.add(Move(startSquare: location, targetSquare: square, piece: piece))
proc generateKingMoves(self: ChessBoard, location: Location): seq[Move] =
## Generates moves for the king in the given location
var
piece = self.grid[location.row, location.col]
doAssert piece.kind == King, &"generateKingMoves called on a {piece.kind}"
var directions: seq[Location] = @[piece.topLeftDiagonal(),
piece.topRightDiagonal(),
piece.bottomRightDiagonal(),
piece.bottomLeftDiagonal(),
piece.topSide(),
piece.bottomSide(),
piece.leftSide(),
piece.rightSide()]
for direction in directions:
# Step in this direction once
let square: Location = location + direction
# End of board reached
if not square.isValid():
continue
let otherPiece = self.grid[square.row, square.col]
# A friendly piece is in the way
if otherPiece.color == piece.color:
continue
if otherPiece.color == piece.color.opposite:
# Target square contains an enemy piece: capture
# it
result.add(Move(startSquare: location, targetSquare: square, piece: piece))
continue
# Target square is empty
result.add(Move(startSquare: location, targetSquare: square, piece: piece))
proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
## Returns the list of possible moves for the
## Returns the list of possible legal chess moves for the
## piece in the given location
let piece = self.grid[location.row, location.col]
case piece.kind:
@ -534,35 +567,12 @@ proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
return self.generateSlidingMoves(location)
of Pawn:
return self.generatePawnMoves(location)
of King:
return self.generateKingMoves(location)
else:
return @[]
proc testMoveOffsets(self: ChessBoard, move: Move): bool =
## Returns true if the piece in the given
## 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:
return move in self.generatePawnMoves(move.startSquare)
of Bishop, Queen, Rook:
return move in self.generateSlidingMoves(move.startSquare)
of Knight:
# TODO
discard
of King:
# TODO
discard
else:
return false
proc getAttackers*(self: ChessBoard, square: string): seq[Piece] =
## Returns all the attackers of the given square
let loc = square.algebraicToPosition()
@ -573,12 +583,31 @@ proc getAttackers*(self: ChessBoard, square: string): seq[Piece] =
if move.targetSquare == loc:
result.add(move.piece)
proc getAttackersFor*(self: ChessBoard, square: string, color: PieceColor): seq[Piece] =
## Returns all the attackers of the given square
## for the given color
let loc = square.algebraicToPosition()
case color:
of White:
for move in self.attacked.black:
if move.targetSquare == loc:
result.add(move.piece)
of Black:
for move in self.attacked.white:
if move.targetSquare == loc:
result.add(move.piece)
else:
discard
# We don't use getAttackers because this one only cares about whether
# the square is attacked or not (and can therefore exit earlier than
# getAttackers)
proc isAttacked*(self: ChessBoard, loc: Location): bool =
## Returns whether the given location is attacked
## by the current opponent
## by the opponent. If the location is empty, this
## function returns true regardless of which color
## the attackers are
let piece = self.grid[loc.row, loc.col]
case piece.color:
of White:
@ -603,10 +632,9 @@ proc isAttacked*(self: ChessBoard, loc: Location): bool =
discard
proc isAttacked*(self: ChessBoard, square: string): bool =
## Returns whether the given square is attacked
## by the current inactive color
## by its opponent
return self.isAttacked(square.algebraicToPosition())
@ -623,38 +651,41 @@ proc updateAttackedSquares(self: ChessBoard) =
# Go over each piece one by one and see which squares
# it currently attacks
# White pawns
# Pawns
for loc in self.pieces.white.pawns:
for move in self.generateMoves(loc):
self.attacked.white.add(move)
# Black pawns
for loc in self.pieces.black.pawns:
for move in self.generateMoves(loc):
self.attacked.black.add(move)
# White bishops
# Bishops
for loc in self.pieces.white.bishops:
for move in self.generateMoves(loc):
self.attacked.white.add(move)
# Black bishops
for loc in self.pieces.black.bishops:
for move in self.generateMoves(loc):
self.attacked.black.add(move)
# White rooks
# rooks
for loc in self.pieces.white.rooks:
for move in self.generateMoves(loc):
self.attacked.white.add(move)
# Black rooks
for loc in self.pieces.black.rooks:
for move in self.generateMoves(loc):
self.attacked.black.add(move)
# White queens
# Queens
for loc in self.pieces.white.queens:
for move in self.generateMoves(loc):
self.attacked.white.add(move)
# Black Queens
# King
for move in self.generateMoves(self.pieces.white.king):
self.attacked.white.add(move)
# Same for black
for loc in self.pieces.black.pawns:
for move in self.generateMoves(loc):
self.attacked.black.add(move)
for loc in self.pieces.black.bishops:
for move in self.generateMoves(loc):
self.attacked.black.add(move)
for loc in self.pieces.black.rooks:
for move in self.generateMoves(loc):
self.attacked.black.add(move)
for loc in self.pieces.black.queens:
for move in self.generateMoves(loc):
self.attacked.black.add(move)
for move in self.generateMoves(self.pieces.black.king):
self.attacked.black.add(move)
proc removePiece(self: ChessBoard, location: Location) =
@ -763,18 +794,26 @@ proc updatePositions(self: ChessBoard, move: Move) =
self.grid[move.targetSquare.row, move.targetSquare.col] = move.piece
proc inCheck(self: ChessBoard): bool =
## Returns whether the active color's
## king is in check
case self.turn:
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
## Returns whether the given color's
## king is in check. If the color is
## set to None, checks are checked
## for the active color's king
case color:
of White:
return self.isAttacked(self.pieces.white.king)
of Black:
return self.isAttacked(self.pieces.black.king)
else:
# Unreachable
discard
of None:
case self.turn:
of White:
return self.isAttacked(self.pieces.white.king)
of Black:
return self.isAttacked(self.pieces.black.king)
else:
# Unreachable
discard
proc doMove(self: ChessBoard, move: Move) =
## Internal function called by makeMove after
@ -787,6 +826,8 @@ proc doMove(self: ChessBoard, move: Move) =
# pawn has moved
if self.enPassantSquare != emptyMove() and self.enPassantSquare.piece.color == self.turn.opposite():
self.enPassantSquare = emptyMove()
self.turn = self.turn.opposite()
proc undoMove(self: ChessBoard, move: Move): Move {.discardable.} =
@ -813,18 +854,15 @@ proc checkMove(self: ChessBoard, move: Move): bool =
# being moved: illegal!
if destination.kind != Empty and destination.color == self.turn:
return false
if not self.testMoveOffsets(move):
# Piece cannot arrive to destination (either
# because it is blocked or because the moving
# pattern is incorrect)
if move notin self.generateMoves(move.startSquare):
# Piece cannot arrive to destination (blocked,
# pinned, or otherwise invalid move)
return false
echo move
self.doMove(move)
defer: self.undoMove(move)
# Move would reveal an attack
# on our king: not allowed
if self.inCheck():
echo "in check"
if self.inCheck(move.piece.color):
return false
# All checks have passed: move is legal
result = true
@ -835,8 +873,13 @@ proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
result = move
if not self.checkMove(move):
return emptyMove()
# 50 move rule
if move.piece.kind != Pawn and not self.isCapture(move):
inc(self.halfMoveClock)
else:
self.halfMoveClock = 0
self.doMove(result)
self.turn = self.turn.opposite()
proc makeMove*(self: ChessBoard, startSquare, targetSquare: string): Move {.discardable.} =

View File

@ -3,20 +3,35 @@ import std/strformat
import std/strutils
var
board = newChessboardFromFEN("rnbqkbnr/8/8/8/8/8/8/RNBQKBNR w KQkq - 0 1")
startSquare: string
targetSquare: string
move: Move
while true:
echo board.pretty()
echo &"Turn: {board.getActiveColor()}"
stdout.write("From -> ")
startSquare = readLine(stdin).strip(chars={'\0', ' '})
stdout.write("To -> ")
targetSquare = readLine(stdin)
try:
move = board.makeMove(startSquare, targetSquare)
except ValueError:
echo &"Error: {getCurrentExceptionMsg()}"
when isMainModule:
setControlCHook(proc () {.noconv.} = echo ""; quit(0))
var
board = newChessboardFromFEN("rnbqkbnr/8/8/8/8/8/8/RNBQKBNR w KQkq - 0 1")
startSquare: string
targetSquare: string
data: string
move: Move
while true:
echo &"{board.pretty()}"
echo &"Turn: {board.getActiveColor()}"
if board.inCheck():
echo &"Check!"
stdout.write("Move (from, to) -> ")
try:
data = readLine(stdin).strip(chars={'\0', ' '})
except IOError:
echo ""
break
if len(data) != 4:
continue
startSquare = data[0..1]
targetSquare = data[2..3]
try:
move = board.makeMove(startSquare, targetSquare)
except ValueError:
echo &"Error: {getCurrentExceptionMsg()}"