From 80a9cfe82702a0df5447a97d33e54118794b7ba9 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Tue, 17 Oct 2023 12:08:07 +0200 Subject: [PATCH] Fix diagonal checks for black --- src/Chess/board.nim | 171 +++++++++++++++++++++++++++---------------- src/Chess/player.nim | 47 ++++++++---- 2 files changed, 138 insertions(+), 80 deletions(-) diff --git a/src/Chess/board.nim b/src/Chess/board.nim index 628b3fc..ffc27c7 100644 --- a/src/Chess/board.nim +++ b/src/Chess/board.nim @@ -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.} = diff --git a/src/Chess/player.nim b/src/Chess/player.nim index 6e3876d..f4cb7c5 100644 --- a/src/Chess/player.nim +++ b/src/Chess/player.nim @@ -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()}" \ No newline at end of file + + +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()}"