diff --git a/src/Chess/board.nim b/src/Chess/board.nim index 48a8776..309aa06 100644 --- a/src/Chess/board.nim +++ b/src/Chess/board.nim @@ -124,6 +124,7 @@ func `+`*(a, b: Location): Location = (a.row + b.row, a.col + b.col) func `-`*(a: Location): Location = (-a.row, -a.col) func `-`*(a, b: Location): Location = (a.row - b.row, a.col - b.col) func isValid*(a: Location): bool {.inline.} = a.row in 0..7 and a.col in 0..7 +func isLightSquare(a: Location): bool {.inline.} = (a.row + a.col and 2) == 0 proc generateMoves(self: ChessBoard, location: Location): seq[Move] proc getAttackers*(self: ChessBoard, loc: Location, color: PieceColor): seq[Location] proc getAttackFor*(self: ChessBoard, source, target: Location): tuple[source, target, direction: Location] @@ -1063,9 +1064,74 @@ proc generateKnightMoves(self: ChessBoard, location: Location): seq[Move] = result.add(Move(startSquare: location, targetSquare: square)) +proc checkInsufficientMaterialPieceCount(self: ChessBoard, color: PieceColor): bool = + ## Helper function for checkInsufficientMaterial + let + friendlyPawns = self.countPieces(Piece(kind: Pawn, color: color)) + friendlyRooks = self.countPieces(Piece(kind: Rook, color: color)) + friendlyQueens = self.countPieces(Piece(kind: Queen, color: color)) + friendlyKnights = self.countPieces(Piece(kind: Knight, color: color)) + friendlyBishops = self.countPieces(Piece(kind: Bishop, color: color)) + enemyPawns = self.countPieces(Piece(kind: Pawn, color: color.opposite())) + enemyRooks = self.countPieces(Piece(kind: Rook, color: color.opposite())) + enemyQueens = self.countPieces(Piece(kind: Queen, color: color.opposite())) + enemyKnights = self.countPieces(Piece(kind: Knight, color: color.opposite())) + enemyBishops = self.countPieces(Piece(kind: Bishop, color: color.opposite())) + if friendlyPawns > 0 or friendlyRooks > 0 or friendlyQueens > 0: + return false + if friendlyKnights >= 2: + return false + if friendlyKnights + friendlyBishops >= 2: + return false + if friendlyKnights >= 1 and (enemyPawns > 0 or enemyRooks > 0 or enemyBishops > 0 or enemyKnights > 0 or enemyQueens > 0): + return false + if friendlyBishops >= 1 and (enemyKnights > 0 or enemyPawns > 0): + return false + return true + + +proc checkInsufficientMaterial(self: ChessBoard): bool = + ## Checks if the given position has not enough material for either side to + ## checkmate the enemy king. Note that the criteria as implemented here are + ## not fully compliant with FIDE rules (they just define a draw by insufficient + ## material as "[...] the position is such that the opponent cannot checkmate + ## the player’s king by any possible series of legal moves.", which is really + ## tricky to implement efficiently). For more info see https://www.reddit.com/r/chess/comments/se89db/a_writeup_on_definitions_of_insufficient_material/ + if not (self.checkInsufficientMaterialPieceCount(White) and self.checkInsufficientMaterialPieceCount(Black)): + return false + let + whiteBishops = self.countPieces(Piece(kind: Bishop, color: White)) + blackBishops = self.countPieces(Piece(kind: Bishop, color: Black)) + if blackBishops + whiteBishops >= 2: + var + darkSquare = 0 + lightSquare = 0 + for bishop in self.position.pieces.black.bishops: + if bishop.isLightSquare(): + lightSquare += 1 + else: + darkSquare += 1 + for bishop in self.position.pieces.white.bishops: + if bishop.isLightSquare(): + lightSquare += 1 + else: + darkSquare += 1 + if darkSquare >= 1 and lightSquare >= 1: + return false + return true + + proc generateMoves(self: ChessBoard, location: Location): seq[Move] = ## Returns the list of possible legal chess moves for the ## piece in the given location + if self.position.halfMoveClock == 100: + # Draw by 50-move rule + return @[] + # TODO: Check for draw by insufficient material + #[ + if self.checkInsufficientMaterial(): + return @[] + ]# let piece = self.grid[location.row, location.col] case piece.kind: of Queen, Bishop, Rook: @@ -1079,7 +1145,6 @@ proc generateMoves(self: ChessBoard, location: Location): seq[Move] = else: return @[] - proc generateAllMoves*(self: ChessBoard): seq[Move] = ## Returns the list of all possible legal moves ## in the current position