From e50cfb9d64abb4c0b0809408010cdbb955c85649 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Tue, 16 Apr 2024 23:45:32 +0200 Subject: [PATCH] Refactoring of Square handling, removed old code. Initial move generation work on pawns --- src/Chess/bitboards.nim | 114 +++- src/Chess/board.nim | 1138 ++++++++------------------------------- src/Chess/moves.nim | 29 +- src/Chess/pieces.nim | 65 +-- 4 files changed, 351 insertions(+), 995 deletions(-) diff --git a/src/Chess/bitboards.nim b/src/Chess/bitboards.nim index 805e6d9..67bf118 100644 --- a/src/Chess/bitboards.nim +++ b/src/Chess/bitboards.nim @@ -6,17 +6,30 @@ import std/strutils import pieces +import moves type Bitboard* = distinct uint64 ## A bitboard + Direction* = enum + ## A move direction enumeration + Forward, + Backward, + Left, + Right + ForwardLeft, + ForwardRight, + BackwardLeft, + BackwardRight # Overloaded operators and functions for our bitboard type func `shl`*(a: Bitboard, x: Positive): Bitboard = Bitboard(a.uint64 shl x) func `shr`*(a: Bitboard, x: Positive): Bitboard = Bitboard(a.uint64 shr x) func `and`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 and b.uint64) +func `or`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 or b.uint64) +func `not`*(a: Bitboard): Bitboard = Bitboard(not a.uint64) func `shr`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 and b.uint64) func `+`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 + b.uint64) func `-`*(a, b: Bitboard): Bitboard = Bitboard(a.uint64 - b.uint64) @@ -28,14 +41,27 @@ func `div`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 div b func `*`*(a: Bitboard, b: SomeUnsignedInt): Bitboard = Bitboard(a.uint64 * b) func `==`*(a, b: Bitboard): bool {.inline.} = a.uint64 == b.uint64 func `==`*(a: Bitboard, b: SomeInteger): bool {.inline.} = a.uint64 == b.uint64 +func `!=`*(a, b: Bitboard): bool {.inline.} = a.uint64 != b.uint64 +func `!=`*(a: Bitboard, b: SomeInteger): bool {.inline.} = a.uint64 != b.uint64 -func getFileMask*(file: Positive): Bitboard = Bitboard(0x101010101010101'u64) shl file -func getRankMask*(rank: Positive): Bitboard = Bitboard(uint64.high()) shl Positive(8 * (rank + 1)) +func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64 +func getRankMask*(rank: int): Bitboard = Bitboard(uint64.high()) shl uint64(8 * (rank + 1)) func squareToBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64 -func squareToBitboard*(square: Square): Bitboard = squareToBitboard(coordToIndex(square)) +func squareToBitboard*(square: Square): Bitboard = squareToBitboard(square.int8) + +proc bitboardToSquare*(b: Bitboard): Square = Square(b.uint64.countTrailingZeroBits()) +func createMove*(startSquare: Bitboard, targetSquare: Square, flags: varargs[MoveFlag]): Move = + result = createMove(startSquare.bitboardToSquare(), targetSquare, flags) + + +func createMove*(startSquare: Square, targetSquare: Bitboard, flags: varargs[MoveFlag]): Move = + result = createMove(startSquare, targetSquare.bitboardToSquare(), flags) + + +func createMove*(startSquare, targetSquare: Bitboard, flags: varargs[MoveFlag]): Move = + result = createMove(startSquare.bitboardToSquare(), targetSquare.bitboardToSquare(), flags) -proc bitboardToSquare*(b: Bitboard): Square = Square(b.uint64.countTrailingZeroBits().indexToCoord()) func toBin*(x: Bitboard, b: Positive = 64): string = toBin(BiggestInt(x), b) func toBin*(x: uint64, b: Positive = 64): string = toBin(Bitboard(x), b) @@ -75,15 +101,19 @@ func pretty*(self: Bitboard): string = ## Returns a prettyfied version of ## the given bitboard + result &= "- - - - - - - -\n" for i, bit in self: if i > 0 and i mod 8 == 0: result &= "\n" - result &= $bit + result &= $bit & " " + result &= "\n- - - - - - - -" -func computeDiagonalpieces: array[14, Bitboard] {.compileTime.} = - ## Precomputes all the pieces for diagonals - ## at compile time +func `$`*(self: Bitboard): string = self.pretty() + + +func computeDiagonalBitboards: array[14, Bitboard] {.compileTime.} = + ## Precomputes all the bitboards for diagonals result[0] = Bitboard(0x8040201008040201'u64) var col = 1 @@ -103,27 +133,28 @@ func computeDiagonalpieces: array[14, Bitboard] {.compileTime.} = inc(col) -const diagonalpieces = computeDiagonalpieces() +const diagonalBitboards = computeDiagonalBitboards() -func getDirectionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard = - ## Get a bitmask for the given direction for a piece - ## of the given color +func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Direction): Bitboard = + ## Get a bitmask relative to the given bitboard + ## for the given direction for a piece of the + ## given color case color: of White: case direction: of Forward: - return squareToBitboard(square) shl 8 + return bitboard shr 8 of Backward: - return squareToBitboard(square) shr 8 + return bitboard shl 8 of ForwardRight: - return squareToBitboard(square) shl 9 + return bitboard shr 7 of ForwardLeft: - return squareToBitboard(square) shr 9 + return bitboard shr 9 of BackwardRight: - return squareToBitboard(square) shl 17 + return bitboard shl 9 of BackwardLeft: - return squareToBitboard(square) shr 17 + return bitboard shr 7 else: discard of Black: @@ -131,18 +162,51 @@ func getDirectionMask*(square: Square, color: PieceColor, direction: Direction): # so we avoid duplicating any code case direction: of Forward: - return getDirectionMask(square, White, Backward) + return bitboard shl 8 of Backward: - return getDirectionMask(square, White, Forward) + return bitboard shr 8 of ForwardRight: - return getDirectionMask(square, White, ForwardLeft) + return bitboard shl 7 of ForwardLeft: - return getDirectionMask(square, White, ForwardRight) + return bitboard shr 9 of BackwardRight: - return getDirectionMask(square, White, BackwardLeft) + return bitboard shr 9 of BackwardLeft: - return getDirectionMask(square, White, BackwardRight) + return bitboard shl 7 else: discard else: - discard \ No newline at end of file + discard + + +func getDirectionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard = + ## Get a bitmask for the given direction for a piece + ## of the given color located at the given square + result = getDirectionMask(squareToBitboard(square), color, direction) + + +func forwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Forward) +func doubleForwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.forwardRelativeTo(side).forwardRelativeTo(side) + +func backwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Backward) + + +# We mask off the first and last ranks/files for +# left and right movements respectively to +# avoid weird wraparounds +func topRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = + getDirectionMask(self, side, ForwardRight) and not getFileMask(7) + + +func topLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = + getDirectionMask(self, side, ForwardLeft) and not getFileMask(0) + + +func bottomRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = + let lastRank = if side == White: getRankMask(0) else: getRankMask(7) + getDirectionMask(self, side, BackwardRight) and not lastRank + + +func bottomLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = + let lastRank = if side == White: getRankMask(0) else: getRankMask(7) + getDirectionMask(self, side, BackwardLeft) and not getRankMask(7) \ No newline at end of file diff --git a/src/Chess/board.nim b/src/Chess/board.nim index 98cfc63..76af6c0 100644 --- a/src/Chess/board.nim +++ b/src/Chess/board.nim @@ -67,20 +67,10 @@ type # A bunch of simple utility functions and forward declarations proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} -proc generateMoves(self: ChessBoard, square: Square): seq[Move] -# proc getAttackers*(self: ChessBoard, square: Square, color: PieceColor): seq[Square] -# proc getAttackFor*(self: ChessBoard, source, target: Square): tuple[source, target, direction: Square] -# proc isAttacked*(self: ChessBoard, square: Square, color: PieceColor = None): bool proc isLegal(self: ChessBoard, move: Move): bool {.inline.} proc doMove(self: ChessBoard, move: Move) proc pretty*(self: ChessBoard): string proc spawnPiece(self: ChessBoard, square: Square, piece: Piece) -# proc updateAttackedSquares(self: ChessBoard) -# proc updateSlidingAttacks(self: ChessBoard) -# proc getPinnedDirections(self: ChessBoard, square: Square): seq[Square] -# proc getAttacks*(self: ChessBoard, square: Square): Attacked -# proc getSlidingAttacks(self: ChessBoard, square: Square): tuple[attacks: Attacked, pins: Attacked] -proc inCheck*(self: ChessBoard, color: PieceColor = None): bool proc toFEN*(self: ChessBoard): string proc unmakeMove*(self: ChessBoard) proc movePiece(self: ChessBoard, move: Move, attack: bool = true) @@ -94,78 +84,6 @@ proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} = proc updateBoard*(self: ChessBoard) -# Due to our board layout, directions of movement are reversed for white and black, so -# we need these helpers to avoid going mad with integer tuples and minus signs everywhere -func topLeftDiagonal(color: PieceColor): Square {.inline.} = (if color == White: (-1, -1) else: (1, 1)) -func topRightDiagonal(color: PieceColor): Square {.inline.} = (if color == White: (-1, 1) else: (1, -1)) -func bottomLeftDiagonal(color: PieceColor): Square {.inline.} = (if color == White: (1, -1) else: (-1, 1)) -func bottomRightDiagonal(color: PieceColor): Square {.inline.} = (if color == White: (1, 1) else: (-1, -1)) -func leftSide(color: PieceColor): Square {.inline.} = (if color == White: (0, -1) else: (0, 1)) -func rightSide(color: PieceColor): Square {.inline.} = (if color == White: (0, 1) else: (0, -1)) -func topSide(color: PieceColor): Square {.inline.} = (if color == White: (-1, 0) else: (1, 0)) -func bottomSide(color: PieceColor): Square {.inline.} = (if color == White: (1, 0) else: (-1, 0)) -func doublePush(color: PieceColor): Square {.inline.} = (if color == White: (-2, 0) else: (2, 0)) -func longCastleKing: Square {.inline.} = (0, -2) -func shortCastleKing: Square {.inline.} = (0, 2) -func longCastleRook: Square {.inline.} = (0, 3) -func shortCastleRook: Square {.inline.} = (0, -2) -func bottomLeftKnightMove(color: PieceColor, long: bool = true): Square {.inline.} = - if color == White: - if long: - return (2, -1) - else: - return (1, -2) - elif color == Black: - if long: - return (-2, 1) - else: - return (1, -2) - - -func bottomRightKnightMove(color: PieceColor, long: bool = true): Square {.inline.} = - if color == White: - if long: - return (2, 1) - else: - return (1, 2) - elif color == Black: - if long: - return (-2, -1) - else: - return (-1, -2) - - -func topLeftKnightMove(color: PieceColor, long: bool = true): Square {.inline.} = - if color == White: - if long: - return (-2, -1) - else: - return (-1, -2) - elif color == Black: - if long: - return (2, 1) - else: - return (1, 2) - - -func topRightKnightMove(color: PieceColor, long: bool = true): Square {.inline.} = - if color == White: - if long: - return (-2, 1) - else: - return (-1, 2) - elif color == Black: - if long: - return (2, -1) - else: - return (-1, 2) - -# These return absolute squares rather than relative direction offsets -func kingSideRook(color: PieceColor): Square {.inline.} = (if color == White: (7, 7) else: (0, 7)) -func queenSideRook(color: PieceColor): Square {.inline.} = (if color == White: (7, 0) else: (0, 0)) - - - # A bunch of getters func getActiveColor*(self: ChessBoard): PieceColor {.inline.} = ## Returns the currently active color @@ -216,9 +134,9 @@ func getKingStartingSquare(color: PieceColor): Square {.inline.} = ## for the given color case color: of White: - return (7, 4) + return makeSquare(7, 4) of Black: - return (0, 4) + return makeSquare(0, 4) else: discard @@ -234,6 +152,13 @@ func getLastRank(color: PieceColor): int {.inline.} = else: return -1 +func kingSideRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 7) else: makeSquare(0, 7)) +func queenSideRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 0) else: makeSquare(0, 0)) +func longCastleKing(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 2) else: makeSquare(0, 5)) +func shortCastleKing(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 6) else: makeSquare(0, 1)) +func longCastleRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 3) else: makeSquare(7, 5)) +func shortCastleRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(0, 0) else: makeSquare(0, 2)) + proc newChessboard: ChessBoard = ## Returns a new, empty chessboard @@ -243,10 +168,8 @@ proc newChessboard: ChessBoard = result.position = Position(enPassantSquare: nullSquare(), turn: White) # Indexing operations -func `[]`(self: array[64, Piece], row, column: Natural): Piece {.inline.} = self[coordToIndex(row, column)] -proc `[]=`(self: var array[64, Piece], row, column: Natural, piece: Piece) {.inline.} = self[coordToIndex(row, column)] = piece -func `[]`(self: array[64, Piece], square: Square): Piece {.inline.} = self[square.rank, square.file] -func `[]=`(self: var array[64, Piece], square: Square, piece: Piece) {.inline.} = self[square.rank, square.file] = piece +func `[]`(self: array[64, Piece], square: Square): Piece {.inline.} = self[square.int8] +func `[]=`(self: var array[64, Piece], square: Square, piece: Piece) {.inline.} = self[square.int8] = piece func getDirectionMask(self: ChessBoard, square: Square, direction: Direction): Bitboard = @@ -329,8 +252,8 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = # Piece of 'r', 'n', 'b', 'q', 'k', 'p': let - square: Square = (row, column) - bitIndex = square.coordToIndex() + square: Square = makeSquare(row, column) + bitIndex = square.int8 # We know for a fact these values are in our # enumeration, so all is good {.warning[HoleEnumConv]:off.} @@ -422,7 +345,7 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = # Field is already uninitialized to the correct state discard else: - result.position.enPassantSquare = fen[index..index+1].algebraicToSquare() + result.position.enPassantSquare = fen[index..index+1].toSquare() # Square metadata is 2 bytes long inc(index) of 4: @@ -507,15 +430,15 @@ func countPieces*(self: ChessBoard, piece: Piece): int {.inline.} = return self.countPieces(piece.kind, piece.color) -func getPiece*(self: ChessBoard, square: Square): Piece {.inline.} = +proc getPiece*(self: ChessBoard, square: Square): Piece {.inline.} = ## Gets the piece at the given square - return self.grid[square.rank, square.file] + return self.grid[square] -func getPiece*(self: ChessBoard, square: string): Piece {.inline.} = +proc getPiece*(self: ChessBoard, square: string): Piece {.inline.} = ## Gets the piece on the given square ## in algebraic notation - return self.getPiece(square.algebraicToSquare()) + return self.getPiece(square.toSquare()) func isPromotion*(move: Move): bool {.inline.} = @@ -589,448 +512,104 @@ func getKing(self: ChessBoard, color: PieceColor = None): Square {.inline.} = color = self.getActiveColor() case color: of White: - return self.position.pieces.white.king.uint64.countTrailingZeroBits().indexToCoord() + return Square(self.position.pieces.white.king.uint64.countTrailingZeroBits()) of Black: - return self.position.pieces.black.king.uint64.countTrailingZeroBits().indexToCoord() + return Square(self.position.pieces.black.king.uint64.countTrailingZeroBits()) else: discard -# TODO -proc inCheck*(self: ChessBoard, color: PieceColor = None): bool = false -proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king: bool] {.inline.} = (false, false) -#[ -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 - var color = color - if color == None: - color = self.getActiveColor() - #[case color: - of White: - result = self.isAttacked(self.position.pieces.white.king, Black) - of Black: - result = self.isAttacked(self.position.pieces.black.king, White) - else: - # Unreachable - discard - ]# - - -proc inDoubleCheck*(self: ChessBoard, color: PieceColor = None): bool = - ## Returns whether the given color's - ## king is in double check. If the color is - ## set to None, checks are checked - ## for the active color's king - var color = color - if color == None: - color = self.getActiveColor() +proc getOccupancyFor(self: ChessBoard, color: PieceColor): Bitboard = + ## Get the occupancy bitboard for every piece of the given color case color: of White: - result = self.getAttackers(self.position.pieces.white.king, Black).len() > 1 + let b = self.position.pieces.white + return b.pawns or b.knights or b.bishops or b.rooks or b.queens or b.king of Black: - result = self.getAttackers(self.position.pieces.black.king, White).len() > 1 + let b = self.position.pieces.black + return b.pawns or b.knights or b.bishops or b.rooks or b.queens or b.king else: - # Unreachable + # huh? discard -proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king: bool] {.inline.} = - ## Returns the sides on which castling is allowed - ## for the given color. If the color is None, the - ## currently active color is used - var color = color - if color == None: - color = self.getActiveColor() - # Check if castling rights are still available for moving side - case color: - of White: - result.king = self.position.castlingAvailable.white.king - result.queen = self.position.castlingAvailable.white.queen - of Black: - result.king = self.position.castlingAvailable.black.king - result.queen = self.position.castlingAvailable.black.queen - of None: - # Unreachable - discard - - # Some of these checks may seem redundant, but we - # perform them because they're less expensive - - # King is not on its starting square - if self.getKing(color) != getKingStartingSquare(color): - return (false, false) - if self.inCheck(color): - # King can not castle out of check - return (false, false) - if result.king or result.queen: - var - square: Square - queenSide: Square - kingSide: Square - # If the path between the king and rook on a given side is blocked, or any of the - # squares where the king would move to are attacked by the opponent, then castling - # is temporarily prohibited on that side - case color: - of White: - square = self.position.pieces.white.king.bitboardToSquare() - queenSide = color.leftSide() - kingSide = color.rightSide() - of Black: - square = self.position.pieces.black.king.bitboardToSquare() - queenSide = color.rightSide() - kingSide = color.leftSide() - of None: - # Unreachable - discard - - # We only need to check for attacked squares up until - # the point where the king arrives on the target castling - # square, but we _also_ need to make sure the path is free - # of obstacles for the rook to move past the king. This variable - # becomes false once the king has arrived on its target square so - # that we don't prevent castling when it would otherwise be allowed - # (for an example see r3k2r/p1pNqpb1/bn2pnp1/3P4/1p2P3/2N2Q1p/PPPBBPPP/R3K2R b KQkq - 0 1) - var checkAttacks = true - - if result.king: - # Short castle - var - currentSquare = square - otherPiece: Piece - while true: - currentSquare = currentSquare + kingSide - - if currentSquare == color.kingSideRook(): - break - - otherPiece = self.grid[currentSquare.rank, currentSquare.file] - - if otherPiece.color != None: - result.king = false - break - - if checkAttacks and self.isAttacked(currentSquare, color.opposite()): - result.king = false - break - - # King has arrived at the target square: we no longer - # need to check whether subsequent squares are free from - # attacks - if currentSquare == shortCastleKing() + square: - checkAttacks = false - - if result.queen: - checkAttacks = true - # Long castle - var - currentSquare = square - otherPiece: Piece - while true: - currentSquare = currentSquare + queenSide - - if currentSquare == color.queenSideRook(): - break - - otherPiece = self.grid[currentSquare.rank, currentSquare.file] - - if otherPiece.color != None: - result.queen = false - break - - if checkAttacks and self.isAttacked(currentSquare, color.opposite()): - result.queen = false - break - - if currentSquare == longCastleKing() + square: - checkAttacks = false +proc getOccupancy(self: ChessBoard): Bitboard = + ## Get the occupancy bitboard for every piece on + ## the chessboard + result = self.getOccupancyFor(Black) or self.getOccupancyFor(White) -proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Square] = - ## Returns the squares that need to be covered to - ## resolve the current check (including capturing - ## the checking piece). In case of double check, an - ## empty list is returned (as the king must move). - ## Note that this function does not handle the special - ## case of a friendly pawn being able to capture an enemy - ## pawn that is checking our friendly king via en passant: - ## that is handled internally by generatePawnMoves - var king: Square - case color: - of White: - king = self.position.pieces.white.king - of Black: - king = self.position.pieces.black.king - else: - return - - let attackers: seq[Square] = self.getAttackers(king, color.opposite()) - if attackers.len() > 1: - # Double checks require to move the king - return @[] +proc generatePawnMovements(self: ChessBoard, moves: var MoveList) = + ## Helper of generatePawnMoves for generating all non-capture + ## pawn movems let - attacker = attackers[0] - attackerPiece = self.grid[attacker] - - var attack = self.getAttackFor(attacker, king) - # Capturing the piece resolves the check - result.add(attacker) + sideToMove = self.getActiveColor() + pawns = self.getBitboard(Pawn, sideToMove) + # Can't move to these squares + occupancy = not self.getOccupancy() + offsets = if sideToMove == White: (-8, -16) else: (8, 16) + # Single pushes + for square in pawns.forwardRelativeTo(sideToMove) and occupancy: + moves.add(createMove((square - offsets[0]), square)) + # Double pushes + for square in pawns.doubleForwardRelativeTo(sideToMove) and occupancy: + moves.add(createMove((square - offsets[1]), square, DoublePush)) - # Blocking the attack is also a viable strategy - # (unless the check is from a knight or a pawn, - # in which case either the king has to move or - # that piece has to be captured, but this is - # already implicitly handled by the loop below) - var square = attacker - while square != king: - square = square + attack.direction - if not square.isValid(): - break - result.add(square) -]# -proc generatePawnMoves(self: ChessBoard, square: Square): seq[Move] = +proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) = + ## Helper of generatePawnMoves for generating all capture + ## pawn moves + let + sideToMove = self.getActiveColor() + nonSideToMove = sideToMove.opposite() + pawns = self.getBitboard(Pawn, sideToMove) + # We can only capture enemy pieces + enemyPieces = self.getOccupancyFor(nonSideToMove) + offsets = if sideToMove == White: (9, 7) else: (-7, -9) + rightMovement = pawns.topRightRelativeTo(sideToMove) + leftMovement = pawns.topLeftRelativeTo(sideToMove) + epTarget = self.getEnPassantTarget() + let epBitboard = if (epTarget != nullSquare()): epTarget.squareToBitboard() else: Bitboard(0) + # Top right attacks + for square in rightMovement and enemyPieces: + moves.add(createMove((square - offsets[0]), square, Capture)) + # Top left attacks + for square in leftMovement and enemyPieces: + moves.add(createMove((square - offsets[1]), square, Capture)) + # Special case for en passant + let + epLeft = epBitboard and leftMovement + epRight = epBitboard and rightMovement + if epLeft != 0: + moves.add(createMove(epBitboard.topLeftRelativeTo(nonSideToMove), epBitboard, EnPassant)) + elif epRight != 0: + moves.add(createMove(epBitboard.topRightRelativeTo(nonSideToMove), epBitboard, EnPassant)) + + +proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) = + ## Helper of generatePawnMoves for generating all pawn promotion + ## moves + + +proc generatePawnMoves(self: ChessBoard, moves: var MoveList) = ## Generates the possible moves for the pawn in the given ## square - #[var - piece = self.grid[square.rank, square.file] - directions: seq[Square] = @[] - assert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}" - # Pawns can move forward one square - let forward = square + piece.color.topSide() - # Only if the square is empty though - if forward.isValid() and self.grid[forward].color == None: - directions.add(piece.color.topSide()) - # If the pawn is on its first rank, it can push two squares - if square.rank == piece.getStartRank(): - let double = square + piece.color.doublePush() - # Check that both squares are empty - if double.isValid() and self.grid[forward].color == None and self.grid[double].color == None: - directions.add(piece.color.doublePush()) - let - enPassantTarget = self.getEnPassantTarget() - enPassantPawn = enPassantTarget + piece.color.opposite().topSide() - topLeft = piece.color.topLeftDiagonal() - topRight = piece.color.topRightDiagonal() - var enPassantLegal = false - # They can also move one square on either of their - # forward diagonals, but only for captures and en passant - for diagonal in [topRight, topLeft]: - let target = square + diagonal - if target.isValid(): - let otherPiece = self.grid[target] - if target == enPassantTarget and self.grid[enPassantPawn].color == piece.color.opposite(): - # En passant may be possible - let targetPawn = self.grid[enPassantPawn] - # Simulate the move and see if the king ends up in check - self.removePiece(enPassantPawn, attack=false) - self.removePiece(square, attack=false) - self.spawnPiece(target, piece) - self.updateAttackedSquares() - if not self.inCheck(piece.color): - # King is not in check after en passant: move is legal - directions.add(diagonal) - enPassantLegal = true - # Reset what we just did and reupdate the attack metadata - self.removePiece(target, attack=false) - self.spawnPiece(square, piece) - self.spawnPiece(enPassantPawn, targetPawn) - self.updateAttackedSquares() - elif otherPiece.color == piece.color.opposite() and otherPiece.kind != King: # Can't capture the king! - # A capture may be possible - directions.add(diagonal) - # Check for pins - let pinned = self.getPinnedDirections(square) - if pinned.len() > 0: - var newDirections: seq[Square] = @[] - for direction in directions: - if direction in pinned: - newDirections.add(direction) - directions = newDirections - let checked = self.inCheck() - var resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color) - # If the check comes from a pawn and en passant is legal and would capture it, - # we add that to the list of possible check resolutions - if checked and enPassantLegal: - let attackingPawn = self.getAttackFor(enPassantPawn, self.getKing(piece.color)) - if attackingPawn.source == enPassantPawn: - resolutions.add(enPassantTarget) - var targetPiece: Piece - for direction in directions: - let target = square + direction - if checked and target notin resolutions: - continue - targetPiece = self.grid[target] - var flags: uint16 = Default.uint16 - if targetPiece.color != None: - flags = flags or Capture.uint16 - elif abs(square.rank - target.rank) == 2: - flags = flags or DoublePush.uint16 - elif target == self.getEnPassantTarget(): - flags = flags or EnPassant.uint16 - if target.rank == piece.color.getLastRank(): - # Pawn reached the other side of the board: generate all potential piece promotions - for promotionType in [PromoteToKnight, PromoteToBishop, PromoteToRook, PromoteToQueen]: - result.add(Move(startSquare: square, targetSquare: target, flags: promotionType.uint16 or flags)) - continue - result.add(Move(startSquare: square, targetSquare: target, flags: flags)) - ]# + self.generatePawnMovements(moves) + self.generatePawnCaptures(moves) proc generateSlidingMoves(self: ChessBoard, square: Square): seq[Move] = ## Generates moves for the sliding piece in the given square - #[ - let piece = self.grid[square.rank, square.file] - assert piece.kind in [Bishop, Rook, Queen], &"generateSlidingMoves called on a {piece.kind}" - var directions: seq[Square] = @[] - - # Only check in the right directions for the chosen piece - if piece.kind in [Bishop, Queen]: - directions.add(piece.color.topLeftDiagonal()) - directions.add(piece.color.topRightDiagonal()) - directions.add(piece.color.bottomLeftDiagonal()) - directions.add(piece.color.bottomRightDiagonal()) - if piece.kind in [Queen, Rook]: - directions.add(piece.color.topSide()) - directions.add(piece.color.bottomSide()) - directions.add(piece.color.rightSide()) - directions.add(piece.color.leftSide()) - let pinned = self.getPinnedDirections(square) - if pinned.len() > 0: - var newDirections: seq[Square] = @[] - for direction in directions: - if direction in pinned: - newDirections.add(direction) - directions = newDirections - let - checked = self.inCheck() - resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color) - for direction in directions: - # Slide in this direction as long as it's possible - var - square: Square = square - otherPiece: Piece - while true: - square = square + direction - # End of board reached - if not square.isValid(): - break - otherPiece = self.grid[square] - # A friendly piece is in the way - if otherPiece.color == piece.color: - break - if checked and square notin resolutions: - # We don't always break out of the loop because - # we might resolve the check later - if otherPiece.color == None: - # We can still move in this direction, so maybe - # the check can be resolved later - continue - else: - # Our movement is blocked, switch to next direction - break - if otherPiece.color == piece.color.opposite: - # Target square contains an enemy piece: capture - # it and stop going any further - if otherPiece.kind != King: - # Can't capture the king - result.add(Move(startSquare: square, targetSquare: square, flags: Capture.uint16)) - break - # Target square is empty, keep going - result.add(Move(startSquare: square, targetSquare: square)) - ]# proc generateKingMoves(self: ChessBoard, square: Square): seq[Move] = ## Generates moves for the king in the given square - #[ - var - piece = self.grid[square.rank, square.file] - assert piece.kind == King, &"generateKingMoves called on a {piece.kind}" - var directions: seq[Square] = @[piece.color.topLeftDiagonal(), - piece.color.topRightDiagonal(), - piece.color.bottomRightDiagonal(), - piece.color.bottomLeftDiagonal(), - piece.color.topSide(), - piece.color.bottomSide(), - piece.color.leftSide(), - piece.color.rightSide()] - # Castling - let canCastle = self.canCastle(piece.color) - if canCastle.queen: - directions.add(longCastleKing()) - if canCastle.king: - directions.add(shortCastleKing()) - var flag = Default - for direction in directions: - # Step in this direction once - let square: Square = square + direction - # End of board reached - if not square.isValid(): - continue - if self.isAttacked(square, piece.color.opposite()): - continue - if direction == longCastleKing(): - flag = CastleLong - elif direction == shortCastleKing(): - flag = CastleShort - else: - flag = Default - let otherPiece = self.grid[square] - if otherPiece.color == piece.color.opposite(): - flag = Capture - # A friendly piece is in the way, move onto the next direction - if otherPiece.color == piece.color: - continue - # Target square is empty or contains an enemy piece: - # All good for us! - result.add(Move(startSquare: square, targetSquare: square, flags: flag.uint16)) - ]# proc generateKnightMoves(self: ChessBoard, square: Square): seq[Move] = ## Generates moves for the knight in the given square - #[ - var - piece = self.grid[square.rank, square.file] - assert piece.kind == Knight, &"generateKnightMoves called on a {piece.kind}" - var directions: seq[Square] = @[piece.color.bottomLeftKnightMove(), - piece.color.bottomRightKnightMove(), - piece.color.topLeftKnightMove(), - piece.color.topRightKnightMove(), - piece.color.bottomLeftKnightMove(long=false), - piece.color.bottomRightKnightMove(long=false), - piece.color.topLeftKnightMove(long=false), - piece.color.topRightKnightMove(long=false)] - let pinned = self.getPinnedDirections(square) - if pinned.len() > 0: - # Knight is pinned: can't move! - return @[] - let checked = self.inCheck() - let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color) - for direction in directions: - # Jump to this square - let square: Square = square + direction - # End of board reached - if not square.isValid(): - continue - let otherPiece = self.grid[square] - # A friendly piece or the opponent king is is in the way - if otherPiece.color == piece.color or otherPiece.kind == King: - continue - if checked and square notin resolutions: - continue - if otherPiece.color != None: - # Target square contains an enemy piece: capture - # it - result.add(Move(startSquare: square, targetSquare: square, flags: Capture.uint16)) - else: - # Target square is empty - result.add(Move(startSquare: square, targetSquare: square)) - ]# + proc checkInsufficientMaterialPieceCount(self: ChessBoard, color: PieceColor): bool = @@ -1090,328 +669,22 @@ proc checkInsufficientMaterial(self: ChessBoard): bool = return true -proc generateMoves(self: ChessBoard, square: Square): seq[Move] = - ## Returns the list of possible legal chess moves for the - ## piece in the given square +proc generateMoves*(self: ChessBoard, moves: var MoveList) = + ## Generates the list of all possible legal moves + ## in the current position if self.position.halfMoveClock >= 100: # Draw by 50-move rule - return @[] + return # TODO: Check for draw by insufficient material #[ if self.checkInsufficientMaterial(): return @[] ]# - let piece = self.grid[square.rank, square.file] - case piece.kind: - of Queen, Bishop, Rook: - return self.generateSlidingMoves(square) - of Pawn: - return self.generatePawnMoves(square) - of King: - return self.generateKingMoves(square) - of Knight: - return self.generateKnightMoves(square) - else: - return @[] + # TODO: Check for repetitions (requires zobrist hashing + table) + self.generatePawnMoves(moves) + # TODO: all pieces -proc generateAllMoves*(self: ChessBoard): seq[Move] = - ## Returns the list of all possible legal moves - ## in the current position - for i in 0..7: - for j in 0..7: - if self.grid[i, j].color == self.getActiveColor(): - for move in self.generateMoves((int8(i), int8(j))): - result.add(move) - -#[ -proc isAttacked*(self: ChessBoard, square: Square, color: PieceColor = None): bool = - ## Returns whether the given square is attacked - ## by the given color - var color = color - if color == None: - color = self.getActiveColor().opposite() - case color: - of Black: - for attack in self.position.attacked.black: - if attack.target == square: - return true - of White: - for attack in self.position.attacked.white: - if attack.target == square: - return true - of None: - discard - - -proc getAttackers*(self: ChessBoard, square: Square, color: PieceColor): seq[Square] = - ## Returns all the attackers of the given color - ## for the given square - case color: - of Black: - for attack in self.position.attacked.black: - if attack.target == square: - result.add(attack.source) - of White: - for attack in self.position.attacked.white: - if attack.target == square: - result.add(attack.source) - of None: - discard - - -proc getAttacks*(self: ChessBoard, square: Square): Attacked = - ## Returns all the squares attacked by the piece in the given - ## square - let piece = self.grid[square.rank, square.file] - case piece.color: - of Black: - for attack in self.position.attacked.black: - if attack.source == square: - result.add(attack) - of White: - for attack in self.position.attacked.white: - if attack.source == square: - result.add(attack) - of None: - discard - - -proc getAttackFor*(self: ChessBoard, source, target: Square): tuple[source, target, direction: Square] = - ## Returns the first attack from the given source to the - ## given target square - result = (nullSquare(), nullSquare(), nullSquare()) - let piece = self.grid[source] - case piece.color: - of Black: - for attack in self.position.attacked.black: - if attack.target == target and attack.source == source: - return attack - of White: - for attack in self.position.attacked.white: - if attack.target == target and attack.source == source: - return attack - of None: - discard - - -proc isAttacked*(self: ChessBoard, square: string): bool = - ## Returns whether the given square is attacked - ## by the current - return self.isAttacked(square.algebraicToSquare()) - - -func addAttack(self: ChessBoard, attack: tuple[source, target, direction: Square], color: PieceColor) {.inline.} = - if attack.source.isValid() and attack.target.isValid(): - case color: - of White: - self.position.attacked.white.add(attack) - of Black: - self.position.attacked.black.add(attack) - else: - discard - - -proc getPinnedDirections(self: ChessBoard, square: Square): seq[Square] = - ## Returns all the directions along which the piece in the given - ## square is pinned. If the result is non-empty, the piece at - ## the given square is only allowed to move along the directions - ## returned by this function - let piece = self.grid[square.rank, square.file] - case piece.color: - of None: - discard - of White: - for pin in self.position.pinned.black: - if pin.target == square: - result.add(pin.direction) - of Black: - for pin in self.position.pinned.white: - if pin.target == square: - result.add(pin.direction) - - -proc updatePawnAttacks(self: ChessBoard) = - ## Internal helper of updateAttackedSquares - for sq in self.position.pieces.white.pawns: - # Pawns are special in how they capture (i.e. the - # squares they can regularly move to do not match - # the squares they can capture on. Sneaky fucks) - self.addAttack((sq, sq + White.topRightDiagonal(), White.topRightDiagonal()), White) - self.addAttack((sq, sq + White.topLeftDiagonal(), White.topLeftDiagonal()), White) - # We do the same thing for black - for sq in self.position.pieces.black.pawns: - self.addAttack((sq, sq + Black.topRightDiagonal(), Black.topRightDiagonal()), Black) - self.addAttack((sq, sq + Black.topLeftDiagonal(), Black.topLeftDiagonal()), Black) - - -proc updateKingAttacks(self: ChessBoard) = - ## Internal helper of updateAttackedSquares - var king = self.position.pieces.white.king - self.addAttack((king, king + White.topRightDiagonal(), White.topRightDiagonal()), White) - self.addAttack((king, king + White.topLeftDiagonal(), White.topLeftDiagonal()), White) - self.addAttack((king, king + White.bottomLeftDiagonal(), White.bottomLeftDiagonal()), White) - self.addAttack((king, king + White.bottomRightDiagonal(), White.bottomRightDiagonal()), White) - self.addAttack((king, king + White.leftSide(), White.leftSide()), White) - self.addAttack((king, king + White.rightSide(), White.rightSide()), White) - self.addAttack((king, king + White.bottomSide(), White.bottomSide()), White) - self.addAttack((king, king + White.topSide(), White.topSide()), White) - - king = self.position.pieces.black.king - self.addAttack((king, king + Black.topRightDiagonal(), Black.topRightDiagonal()), Black) - self.addAttack((king, king + Black.topLeftDiagonal(), Black.topLeftDiagonal()), Black) - self.addAttack((king, king + Black.bottomLeftDiagonal(), Black.bottomLeftDiagonal()), Black) - self.addAttack((king, king + Black.bottomRightDiagonal(), Black.bottomRightDiagonal()), Black) - self.addAttack((king, king + Black.leftSide(), Black.leftSide()), Black) - self.addAttack((king, king + Black.rightSide(), Black.rightSide()), Black) - self.addAttack((king, king + Black.bottomSide(), Black.bottomSide()), Black) - self.addAttack((king, king + Black.topSide(), Black.topSide()), Black) - - -proc updateKnightAttacks(self: ChessBoard) = - ## Internal helper of updateAttackedSquares - for loc in self.position.pieces.white.knights: - self.addAttack((loc, loc + White.topLeftKnightMove(), White.topLeftKnightMove()), White) - self.addAttack((loc, loc + White.topRightKnightMove(), White.topRightKnightMove()), White) - self.addAttack((loc, loc + White.bottomLeftKnightMove(), White.bottomLeftKnightMove()), White) - self.addAttack((loc, loc + White.bottomRightKnightMove(), White.bottomRightKnightMove()), White) - self.addAttack((loc, loc + White.topLeftKnightMove(long=false), White.topLeftKnightMove(long=false)), White) - self.addAttack((loc, loc + White.topRightKnightMove(long=false), White.topRightKnightMove(long=false)), White) - self.addAttack((loc, loc + White.bottomLeftKnightMove(long=false), White.bottomLeftKnightMove(long=false)), White) - self.addAttack((loc, loc + White.bottomRightKnightMove(long=false), White.bottomRightKnightMove(long=false)), White) - - for loc in self.position.pieces.black.knights: - self.addAttack((loc, loc + Black.topLeftKnightMove(), Black.topLeftKnightMove()), Black) - self.addAttack((loc, loc + Black.topRightKnightMove(), Black.topRightKnightMove()), Black) - self.addAttack((loc, loc + Black.bottomLeftKnightMove(), Black.bottomLeftKnightMove()), Black) - self.addAttack((loc, loc + Black.bottomRightKnightMove(), Black.bottomRightKnightMove()), Black) - self.addAttack((loc, loc + Black.topLeftKnightMove(long=false), Black.topLeftKnightMove(long=false)), Black) - self.addAttack((loc, loc + Black.topRightKnightMove(long=false), Black.topRightKnightMove(long=false)), Black) - self.addAttack((loc, loc + Black.bottomLeftKnightMove(long=false), Black.bottomLeftKnightMove(long=false)), Black) - self.addAttack((loc, loc + Black.bottomRightKnightMove(long=false), Black.bottomRightKnightMove(long=false)), Black) - - -proc getSlidingAttacks(self: ChessBoard, square: Square): tuple[attacks: Attacked, pins: Attacked] = - ## Internal helper of updateSlidingAttacks - var - directions: seq[Square] = @[] - let piece = self.grid[square.rank, square.file] - if piece.kind in [Bishop, Queen]: - directions.add(piece.color.topLeftDiagonal()) - directions.add(piece.color.topRightDiagonal()) - directions.add(piece.color.bottomLeftDiagonal()) - directions.add(piece.color.bottomRightDiagonal()) - - if piece.kind in [Queen, Rook]: - directions.add(piece.color.topSide()) - directions.add(piece.color.bottomSide()) - directions.add(piece.color.rightSide()) - directions.add(piece.color.leftSide()) - - for direction in directions: - var - currentSquare = square - otherPiece: Piece - # Slide in this direction as long as it's possible - while true: - currentSquare = currentSquare + direction - # End of board reached - if not square.isValid(): - break - otherPiece = self.grid[square] - # Target square is attacked (even if a friendly piece - # is present, because in this case we're defending - # it) - result.attacks.add((square, currentSquare, direction)) - # Empty square, keep going - if otherPiece.color == None: - continue - if otherPiece.color == piece.color.opposite(): - if otherPiece.kind != King: - # We found an enemy piece that is not - # the enemy king. We don't break out - # immediately because we first want - # to check if we've pinned it to the king - var - otherSquare: Square = square - behindPiece: Piece - while true: - otherSquare = otherSquare + direction - if not otherSquare.isValid(): - break - behindPiece = self.grid[otherSquare] - if behindPiece.color == None: - continue - if behindPiece.color == piece.color.opposite and behindPiece.kind == King: - # The enemy king is behind this enemy piece: pin it along - # this axis in both directions - result.pins.add((square, currentSquare, direction)) - result.pins.add((square, currentSquare, -direction)) - if otherPiece.kind == Pawn and square.rank == otherPiece.getStartRank(): - # The pinned piece is a pawn which hasn't moved yet: - # we allow it to move two squares as well - if square.file == square.file: - # The pawn can only push two squares if it's being pinned from the - # top side (relative to the pawn itself) - result.pins.add((square, currentSquare, otherPiece.color.doublePush())) - else: - break - else: - # Enemy king is here: ensure it cannot move backwards by - # attacking the square behind it (if one exists and is - # valid) - let target = square + direction - if target.isValid(): - result.attacks.add((square, target, direction)) - break - - -proc updateSlidingAttacks(self: ChessBoard) = - ## Internal helper of updateAttackedSquares - var data: tuple[attacks: Attacked, pins: Attacked] - for loc in self.position.pieces.white.bishops: - data = self.getSlidingAttacks(loc) - self.position.attacked.white.extend(data.attacks) - self.position.pinned.white.extend(data.pins) - for loc in self.position.pieces.white.rooks: - data = self.getSlidingAttacks(loc) - self.position.attacked.white.extend(data.attacks) - self.position.pinned.white.extend(data.pins) - for loc in self.position.pieces.white.queens: - data = self.getSlidingAttacks(loc) - self.position.attacked.white.extend(data.attacks) - self.position.pinned.white.extend(data.pins) - for loc in self.position.pieces.black.bishops: - data = self.getSlidingAttacks(loc) - self.position.attacked.black.extend(data.attacks) - self.position.pinned.black.extend(data.pins) - for loc in self.position.pieces.black.rooks: - data = self.getSlidingAttacks(loc) - self.position.attacked.black.extend(data.attacks) - self.position.pinned.black.extend(data.pins) - for loc in self.position.pieces.black.queens: - data = self.getSlidingAttacks(loc) - self.position.attacked.black.extend(data.attacks) - self.position.pinned.black.extend(data.pins) - - -proc updateAttackedSquares(self: ChessBoard) = - ## Updates internal metadata about which squares - ## are attacked - self.position.attacked.white.setLen(0) - self.position.attacked.black.setLen(0) - self.position.pinned.white.setLen(0) - self.position.pinned.black.setLen(0) - # Pawns - self.updatePawnAttacks() - # Sliding pieces - self.updateSlidingAttacks() - # Knights - self.updateKnightAttacks() - # Kings - self.updateKingAttacks() -]# - proc removePieceFromBitboard(self: ChessBoard, square: Square) = ## Removes a piece at the given square in the chessboard from ## its respective bitboard @@ -1420,33 +693,33 @@ proc removePieceFromBitboard(self: ChessBoard, square: Square) = of White: case piece.kind: of Pawn: - self.position.pieces.white.pawns.uint64.clearBit(square.coordToIndex()) + self.position.pieces.white.pawns.uint64.clearBit(square.int8) of Bishop: - self.position.pieces.white.bishops.uint64.clearBit(square.coordToIndex()) + self.position.pieces.white.bishops.uint64.clearBit(square.int8) of Knight: - self.position.pieces.white.knights.uint64.clearBit(square.coordToIndex()) + self.position.pieces.white.knights.uint64.clearBit(square.int8) of Rook: - self.position.pieces.white.rooks.uint64.clearBit(square.coordToIndex()) + self.position.pieces.white.rooks.uint64.clearBit(square.int8) of Queen: - self.position.pieces.white.queens.uint64.clearBit(square.coordToIndex()) + self.position.pieces.white.queens.uint64.clearBit(square.int8) of King: - self.position.pieces.white.king.uint64.clearBit(square.coordToIndex()) + self.position.pieces.white.king.uint64.clearBit(square.int8) else: discard of Black: case piece.kind: of Pawn: - self.position.pieces.black.pawns.uint64.clearBit(square.coordToIndex()) + self.position.pieces.black.pawns.uint64.clearBit(square.int8) of Bishop: - self.position.pieces.black.bishops.uint64.clearBit(square.coordToIndex()) + self.position.pieces.black.bishops.uint64.clearBit(square.int8) of Knight: - self.position.pieces.black.knights.uint64.clearBit(square.coordToIndex()) + self.position.pieces.black.knights.uint64.clearBit(square.int8) of Rook: - self.position.pieces.black.rooks.uint64.clearBit(square.coordToIndex()) + self.position.pieces.black.rooks.uint64.clearBit(square.int8) of Queen: - self.position.pieces.black.queens.uint64.clearBit(square.coordToIndex()) + self.position.pieces.black.queens.uint64.clearBit(square.int8) of King: - self.position.pieces.black.king.uint64.clearBit(square.coordToIndex()) + self.position.pieces.black.king.uint64.clearBit(square.int8) else: discard else: @@ -1460,33 +733,33 @@ proc addPieceToBitboard(self: ChessBoard, square: Square, piece: Piece) = of White: case piece.kind: of Pawn: - self.position.pieces.white.pawns.uint64.setBit(square.coordToIndex()) + self.position.pieces.white.pawns.uint64.setBit(square.int8) of Bishop: - self.position.pieces.white.bishops.uint64.setBit(square.coordToIndex()) + self.position.pieces.white.bishops.uint64.setBit(square.int8) of Knight: - self.position.pieces.white.knights.uint64.setBit(square.coordToIndex()) + self.position.pieces.white.knights.uint64.setBit(square.int8) of Rook: - self.position.pieces.white.rooks.uint64.setBit(square.coordToIndex()) + self.position.pieces.white.rooks.uint64.setBit(square.int8) of Queen: - self.position.pieces.white.queens.uint64.setBit(square.coordToIndex()) + self.position.pieces.white.queens.uint64.setBit(square.int8) of King: - self.position.pieces.white.king.uint64.setBit(square.coordToIndex()) + self.position.pieces.white.king.uint64.setBit(square.int8) else: discard of Black: case piece.kind: of Pawn: - self.position.pieces.black.pawns.uint64.setBit(square.coordToIndex()) + self.position.pieces.black.pawns.uint64.setBit(square.int8) of Bishop: - self.position.pieces.black.bishops.uint64.setBit(square.coordToIndex()) + self.position.pieces.black.bishops.uint64.setBit(square.int8) of Knight: - self.position.pieces.black.knights.uint64.setBit(square.coordToIndex()) + self.position.pieces.black.knights.uint64.setBit(square.int8) of Rook: - self.position.pieces.black.rooks.uint64.setBit(square.coordToIndex()) + self.position.pieces.black.rooks.uint64.setBit(square.int8) of Queen: - self.position.pieces.black.queens.uint64.setBit(square.coordToIndex()) + self.position.pieces.black.queens.uint64.setBit(square.int8) of King: - self.position.pieces.black.king.uint64.setBit(square.coordToIndex()) + self.position.pieces.black.king.uint64.setBit(square.int8) else: discard else: @@ -1514,45 +787,45 @@ proc updateMovepieces(self: ChessBoard, move: Move) = of White: case piece.kind: of Pawn: - self.position.pieces.white.pawns.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.white.pawns.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.white.pawns.uint64.setBit(move.targetSquare.int8) + self.position.pieces.white.pawns.uint64.clearBit(move.startSquare.int8) of Bishop: - self.position.pieces.white.bishops.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.white.bishops.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.white.bishops.uint64.setBit(move.targetSquare.int8) + self.position.pieces.white.bishops.uint64.clearBit(move.startSquare.int8) of Knight: - self.position.pieces.white.knights.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.white.knights.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.white.knights.uint64.setBit(move.targetSquare.int8) + self.position.pieces.white.knights.uint64.clearBit(move.startSquare.int8) of Rook: - self.position.pieces.white.rooks.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.white.rooks.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.white.rooks.uint64.setBit(move.targetSquare.int8) + self.position.pieces.white.rooks.uint64.clearBit(move.startSquare.int8) of Queen: - self.position.pieces.white.queens.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.white.queens.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.white.queens.uint64.setBit(move.targetSquare.int8) + self.position.pieces.white.queens.uint64.clearBit(move.startSquare.int8) of King: - self.position.pieces.white.king.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.white.king.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.white.king.uint64.setBit(move.targetSquare.int8) + self.position.pieces.white.king.uint64.clearBit(move.startSquare.int8) else: discard of Black: case piece.kind: of Pawn: - self.position.pieces.black.pawns.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.black.pawns.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.black.pawns.uint64.setBit(move.targetSquare.int8) + self.position.pieces.black.pawns.uint64.clearBit(move.startSquare.int8) of Bishop: - self.position.pieces.black.bishops.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.black.bishops.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.black.bishops.uint64.setBit(move.targetSquare.int8) + self.position.pieces.black.bishops.uint64.clearBit(move.startSquare.int8) of Knight: - self.position.pieces.black.knights.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.black.knights.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.black.knights.uint64.setBit(move.targetSquare.int8) + self.position.pieces.black.knights.uint64.clearBit(move.startSquare.int8) of Rook: - self.position.pieces.black.rooks.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.black.rooks.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.black.rooks.uint64.setBit(move.targetSquare.int8) + self.position.pieces.black.rooks.uint64.clearBit(move.startSquare.int8) of Queen: - self.position.pieces.black.queens.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.black.queens.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.black.queens.uint64.setBit(move.targetSquare.int8) + self.position.pieces.black.queens.uint64.clearBit(move.startSquare.int8) of King: - self.position.pieces.black.king.uint64.setBit(move.targetSquare.coordToIndex()) - self.position.pieces.black.king.uint64.clearBit(move.startSquare.coordToIndex()) + self.position.pieces.black.king.uint64.setBit(move.targetSquare.int8) + self.position.pieces.black.king.uint64.clearBit(move.startSquare.int8) else: discard else: @@ -1569,7 +842,7 @@ proc movePiece(self: ChessBoard, move: Move, attack: bool = true) = if targetSquare.color != None: raise newException(AccessViolationDefect, &"attempted to overwrite a piece! {move}") # Update positional metadata - self.updateMovepieces(move) + self.updateMovePieces(move) # Empty out the starting square self.grid[move.startSquare] = nullPiece() # Actually move the piece on the board @@ -1609,25 +882,25 @@ proc doMove(self: ChessBoard, move: Move) = inc(fullMoveCount) if move.isDoublePush(): - enPassantTarget = move.targetSquare + piece.color.bottomSide() + enPassantTarget = move.targetSquare.squareToBitboard().backwardRelativeTo(piece.color).bitboardToSquare() # Castling check: have the rooks moved? if piece.kind == Rook: case piece.color: of White: - if move.startSquare.rank == piece.getStartRank(): - if move.startSquare.file == 0: + if rowFromSquare(move.startSquare) == piece.getStartRank(): + if colFromSquare(move.startSquare) == 0: # Queen side castlingAvailable.white.queen = false - elif move.startSquare.file == 7: + elif colfromSquare(move.startSquare) == 7: # King side castlingAvailable.white.king = false of Black: - if move.startSquare.rank == piece.getStartRank(): - if move.startSquare.file == 0: + if rowFromSquare(move.startSquare) == piece.getStartRank(): + if colFromSquare(move.startSquare) == 0: # Queen side castlingAvailable.black.queen = false - elif move.startSquare.file == 7: + elif colFromSquare(move.startSquare) == 7: # King side castlingAvailable.black.king = false else: @@ -1684,22 +957,21 @@ proc doMove(self: ChessBoard, move: Move) = var square: Square target: Square - flags: uint16 + flag: MoveFlag if move.getCastlingType() == CastleShort: square = piece.color.kingSideRook() - target = shortCastleRook() - flags = flags or CastleShort.uint16 + target = shortCastleRook(piece.color) + flag = CastleShort else: square = piece.color.queenSideRook() - target = longCastleRook() - flags = flags or CastleLong.uint16 - let rook = self.grid[square.rank, square.file] - let move = Move(startSquare: square, targetSquare: square + target, flags: flags) - self.movePiece(move, attack=false) + target = longCastleRook(piece.color) + flag = CastleLong + let rook = self.grid[square] + self.movePiece(createMove(square, target, flag), attack=false) if move.isEnPassant(): # Make the en passant pawn disappear - self.removePiece(move.targetSquare + piece.color.bottomSide(), attack=false) + self.removePiece(move.targetSquare.squareToBitboard().backwardRelativeTo(piece.color).bitboardToSquare(), attack=false) if move.isCapture(): # Get rid of captured pieces @@ -1785,7 +1057,9 @@ proc redoMove*(self: ChessBoard) = proc isLegal(self: ChessBoard, move: Move): bool {.inline.} = ## Returns whether the given move is legal - return move in self.generateMoves(move.startSquare) + var moves = MoveList() + self.generateMoves(moves) + return move in moves proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} = @@ -1804,15 +1078,17 @@ proc toChar*(piece: Piece): char = proc `$`*(self: ChessBoard): string = result &= "- - - - - - - -" + var file = 8 for i in 0..7: result &= "\n" for j in 0..7: - let piece = self.grid[i, j] + let piece = self.grid[makeSquare(i, j)] if piece.kind == Empty: result &= "x " continue result &= &"{piece.toChar()} " - result &= &"{fileToColumn(i + 1) + 1}" + result &= &"{file}" + dec(file) result &= "\n- - - - - - - -" result &= "\na b c d e f g h" @@ -1858,6 +1134,7 @@ proc toPretty*(piece: Piece): string = proc pretty*(self: ChessBoard): string = ## Returns a colored version of the ## board for easier visualization + var file = 8 for i in 0..7: if i > 0: result &= "\n" @@ -1868,12 +1145,13 @@ proc pretty*(self: ChessBoard): string = result &= "\x1b[39;44;1m" else: result &= "\x1b[39;40;1m" - let piece = self.grid[i, j] + let piece = self.grid[makeSquare(i, j)] if piece.kind == Empty: result &= " \x1b[0m" else: result &= &"{piece.toPretty()} \x1b[0m" - result &= &" \x1b[33;1m{fileToColumn(i + 1) + 1}\x1b[0m" + result &= &" \x1b[33;1m{file}\x1b[0m" + dec(file) result &= "\n\x1b[31;1ma b c d e f g h" result &= "\x1b[0m" @@ -1888,7 +1166,7 @@ proc toFEN*(self: ChessBoard): string = for i in 0..7: skip = 0 for j in 0..7: - let piece = self.grid[i, j] + let piece = self.grid[makeSquare(i, j)] if piece.kind == Empty: inc(skip) elif skip > 0: @@ -1923,7 +1201,7 @@ proc toFEN*(self: ChessBoard): string = if self.getEnPassantTarget() == nullSquare(): result &= "-" else: - result &= self.getEnPassantTarget().squareToAlgebraic() + result &= self.getEnPassantTarget().toAlgebraic() result &= " " # Halfmove clock result &= $self.getHalfMoveCount() @@ -1931,12 +1209,13 @@ proc toFEN*(self: ChessBoard): string = # Fullmove number result &= $self.getMoveCount() - +#[ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = false, bulk: bool = false): CountData = ## Counts (and debugs) the number of legal positions reached after ## the given number of ply - - let moves = self.generateAllMoves() + + var moves = MoveList() + self.generateMoves(moves) if not bulk: if len(moves) == 0 and self.inCheck(): result.checkmates = 1 @@ -1959,7 +1238,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa postfix = "q" else: postfix = "" - echo &"{move.startSquare.squareToAlgebraic()}{move.targetSquare.squareToAlgebraic()}{postfix}: 1" + echo &"{move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}{postfix}: 1" if verbose: echo "" return (uint64(len(moves)), 0, 0, 0, 0, 0, 0) @@ -1968,7 +1247,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa if verbose: let canCastle = self.canCastle(self.getActiveColor()) echo &"Ply (from root): {self.position.plyFromRoot}" - echo &"Move: {move.startSquare.squareToAlgebraic()}{move.targetSquare.squareToAlgebraic()}, from ({move.startSquare.rank}, {move.startSquare.file}) to ({move.targetSquare.rank}, {move.targetSquare.file})" + echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}, from ({move.startSquare.rank}, {move.startSquare.file}) to ({move.targetSquare.rank}, {move.targetSquare.file})" echo &"Turn: {self.getActiveColor()}" echo &"Piece: {self.grid[move.startSquare].kind}" echo &"Flags: {move.getFlags()}" @@ -1977,7 +1256,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa echo &"Position before move: {self.toFEN()}" stdout.write("En Passant target: ") if self.getEnPassantTarget() != nullSquare(): - echo self.getEnPassantTarget().squareToAlgebraic() + echo self.getEnPassantTarget().toAlgebraic() else: echo "None" echo "\n", self.pretty() @@ -2024,7 +1303,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa postfix = "q" else: discard - echo &"{move.startSquare.squareToAlgebraic()}{move.targetSquare.squareToAlgebraic()}{postfix}: {next.nodes}" + echo &"{move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}{postfix}: {next.nodes}" if verbose: echo "" result.nodes += next.nodes @@ -2034,7 +1313,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa result.castles += next.castles result.enPassant += next.enPassant result.checkmates += next.checkmates - +]# proc handleGoCommand(board: ChessBoard, command: seq[string]) = if len(command) < 2: @@ -2063,7 +1342,7 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) = break if not ok: return - try: + #[try: let ply = parseInt(args[0]) if bulk: let t = cpuTime() @@ -2086,6 +1365,7 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) = echo "Error: go: perft: invalid depth" else: echo &"Error: go: unknown subcommand '{command[1]}'" + ]# proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discardable.} = @@ -2102,12 +1382,12 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discarda flags: seq[MoveFlag] try: - startSquare = moveString[0..1].algebraicToSquare() + startSquare = moveString[0..1].toSquare() except ValueError: echo &"Error: move: invalid start square ({moveString[0..1]})" return try: - targetSquare = moveString[2..3].algebraicToSquare() + targetSquare = moveString[2..3].toSquare() except ValueError: echo &"Error: move: invalid target square ({moveString[2..3]})" return @@ -2119,7 +1399,7 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discarda if board.grid[targetSquare].kind != Empty: flags.add(Capture) - elif board.grid[startSquare].kind == Pawn and abs(startSquare.rank - targetSquare.rank) == 2: + elif board.grid[startSquare].kind == Pawn and abs(rowFromSquare(startSquare) - rowFromSquare(targetSquare)) == 2: flags.add(DoublePush) if len(moveString) == 5: @@ -2139,10 +1419,11 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discarda var move = createMove(startSquare, targetSquare, flags) - if board.getPiece(move.startSquare).kind == King and move.startSquare == board.getActiveColor().getKingStartingSquare(): - if move.targetSquare == move.startSquare + longCastleKing(): + let piece = board.getPiece(move.startSquare) + if piece.kind == King and move.startSquare == board.getActiveColor().getKingStartingSquare(): + if move.targetSquare == longCastleKing(piece.color): move.flags = move.flags or CastleLong.uint16 - elif move.targetSquare == move.startSquare + shortCastleKing(): + elif move.targetSquare == shortCastleKing(piece.color): move.flags = move.flags or CastleShort.uint16 if move.targetSquare == board.getEnPassantTarget(): move.flags = move.flags or EnPassant.uint16 @@ -2305,7 +1586,7 @@ proc main: int = of "ep": let target = board.getEnPassantTarget() if target != nullSquare(): - echo &"En passant target: {target.squareToAlgebraic()}" + echo &"En passant target: {target.toAlgebraic()}" else: echo "En passant target: None" of "get": @@ -2317,11 +1598,12 @@ proc main: int = except ValueError: echo "error: get: invalid square" continue - of "castle": + #[of "castle": let canCastle = board.canCastle() echo &"Castling rights for {($board.getActiveColor()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}" of "check": echo &"{board.getActiveColor()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}" + ]# else: echo &"Unknown command '{cmd[0]}'. Type 'help' for more information." except IOError: @@ -2365,7 +1647,6 @@ when isMainModule: testPieceCount(b, Queen, Black, 1) testPieceCount(b, King, White, 1) testPieceCount(b, King, Black, 1) - # Ensure pieces are in the correct squares. This is testing the FEN # parser @@ -2411,18 +1692,19 @@ when isMainModule: blackRooks = b.getBitboard(Rook, Black) blackQueens = b.getBitboard(Queen, Black) blackKing = b.getBitboard(King, Black) - whitePawnSquares = @[(6'i8, 0'i8), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7)] - whiteKnightSquares = @[(7'i8, 1'i8), (7, 6)] - whiteBishopSquares = @[(7'i8, 2'i8), (7, 5)] - whiteRookSquares = @[(7'i8, 0'i8), (7, 7)] - whiteQueenSquares = @[(7'i8, 3'i8)] - whiteKingSquares = @[(7'i8, 4'i8)] - blackPawnSquares = @[(1'i8, 0'i8), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7)] - blackKnightSquares = @[(0'i8, 1'i8), (0, 6)] - blackBishopSquares = @[(0'i8, 2'i8), (0, 5)] - blackRookSquares = @[(0'i8, 0'i8), (0, 7)] - blackQueenSquares = @[(0'i8, 3'i8)] - blackKingSquares = @[(0'i8, 4'i8)] + whitePawnSquares = @[makeSquare(6'i8, 0'i8), makeSquare(6, 1), makeSquare(6, 2), makeSquare(6, 3), makeSquare(6, 4), makeSquare(6, 5), makeSquare(6, 6), makeSquare(6, 7)] + whiteKnightSquares = @[makeSquare(7'i8, 1'i8), makeSquare(7, 6)] + whiteBishopSquares = @[makeSquare(7'i8, 2'i8), makeSquare(7, 5)] + whiteRookSquares = @[makeSquare(7'i8, 0'i8), makeSquare(7, 7)] + whiteQueenSquares = @[makeSquare(7'i8, 3'i8)] + whiteKingSquares = @[makeSquare(7'i8, 4'i8)] + blackPawnSquares = @[makeSquare(1'i8, 0'i8), makeSquare(1, 1), makeSquare(1, 2), makeSquare(1, 3), makeSquare(1, 4), makeSquare(1, 5), makeSquare(1, 6), makeSquare(1, 7)] + blackKnightSquares = @[makeSquare(0'i8, 1'i8), makeSquare(0, 6)] + blackBishopSquares = @[makeSquare(0'i8, 2'i8), makeSquare(0, 5)] + blackRookSquares = @[makeSquare(0'i8, 0'i8), makeSquare(0, 7)] + blackQueenSquares = @[makeSquare(0'i8, 3'i8)] + blackKingSquares = @[makeSquare(0'i8, 4'i8)] + testPieceBitboard(whitePawns, whitePawnSquares) testPieceBitboard(whiteKnights, whiteKnightSquares) @@ -2436,5 +1718,19 @@ when isMainModule: testPieceBitboard(blackRooks, blackRookSquares) testPieceBitboard(blackQueens, blackQueenSquares) testPieceBitboard(blackKing, blackKingSquares) + + var m = MoveList() + b.generatePawnMoves(m) + echo &"Pawn moves for {b.getActiveColor()} at {b.toFEN()}: " + for move in m: + echo " - ", move.startSquare, move.targetSquare + b.doMove(createMove("a2", "a3")) + m.clear() + b.generatePawnMoves(m) + echo &"Pawn moves for {b.getActiveColor()} at {b.toFEN()}: " + for move in m: + echo " - ", move.startSquare, move.targetSquare + b.doMove(createMove("a7", "a5")) + echo b.pretty() # setControlCHook(proc () {.noconv.} = quit(0)) - # quit(main()) \ No newline at end of file + # quit(main()) diff --git a/src/Chess/moves.nim b/src/Chess/moves.nim index 3c7c3ed..030c023 100644 --- a/src/Chess/moves.nim +++ b/src/Chess/moves.nim @@ -29,6 +29,12 @@ type data: array[218, Move] len: int8 +func `[]`*(self: MoveList, i: SomeInteger): Move = + when not defined(danger): + if i >= self.len: + raise newException(IndexDefect, &"move list access out of bounds ({i} >= {self.len})") + result = self.data[i] + iterator items*(self: MoveList): Move = var i = 0 @@ -48,6 +54,10 @@ func add*(self: var MoveList, move: Move) {.inline.} = inc(self.len) +func clear*(self: var MoveList) {.inline.} = + self.len = 0 + + func contains*(self: MoveList, move: Move): bool {.inline.} = for item in self: if move == item: @@ -58,17 +68,22 @@ func contains*(self: MoveList, move: Move): bool {.inline.} = func len*(self: MoveList): int {.inline.} = self.len -func createMove*(startSquare, targetSquare: string, flags: seq[MoveFlag] = @[]): Move = - result = Move(startSquare: startSquare.algebraicToSquare(), - targetSquare: targetSquare.algebraicToSquare(), flags: Default.uint16) - for flag in flags: - result.flags = result.flags or flag.uint16 +# A bunch of move creation utilities - -func createMove*(startSquare, targetSquare: Square, flags: seq[MoveFlag] = @[]): Move = +func createMove*(startSquare, targetSquare: Square, flags: varargs[MoveFlag]): Move = result = Move(startSquare: startSquare, targetSquare: targetSquare, flags: Default.uint16) for flag in flags: result.flags = result.flags or flag.uint16 +proc createMove*(startSquare, targetSquare: string, flags: varargs[MoveFlag]): Move = + result = createMove(startSquare.toSquare(), targetSquare.toSquare(), flags) + +func createMove*(startSquare, targetSquare: SomeInteger, flags: varargs[MoveFlag]): Move = + result = createMove(Square(startSquare.int8), Square(targetSquare.int8), flags) + + +func createMove*(startSquare: Square, targetSquare: SomeInteger, flags: varargs[MoveFlag]): Move = + result = createMove(startSquare, Square(targetSquare.int8), flags) + func nullMove*: Move {.inline.} = createMove(nullSquare(), nullSquare()) \ No newline at end of file diff --git a/src/Chess/pieces.nim b/src/Chess/pieces.nim index 575514b..bfcfcd9 100644 --- a/src/Chess/pieces.nim +++ b/src/Chess/pieces.nim @@ -4,7 +4,7 @@ import std/strformat type - Square* = tuple[rank, file: int8] + Square* = distinct int8 ## A square PieceColor* = enum @@ -23,55 +23,33 @@ type Queen = 'q', Rook = 'r', - Direction* = enum - ## A move direction enumeration - Forward, - Backward, - Left, - Right - ForwardLeft, - ForwardRight, - BackwardLeft, - BackwardRight - Piece* = object ## A chess piece color*: PieceColor kind*: PieceKind -func coordToIndex*(row, col: SomeInteger): int {.inline.} = (row * 8) + col -func coordToIndex*(square: Square): int {.inline.} = coordToIndex(square.rank, square.file) -func indexToCoord*(index: SomeInteger): Square {.inline.} = ((index div 8).int8, (index mod 8).int8) func nullPiece*: Piece {.inline.} = Piece(kind: Empty, color: None) -func nullSquare*: Square {.inline.} = (-1 , -1) +func nullSquare*: Square {.inline.} = Square(-1'i8) func opposite*(c: PieceColor): PieceColor {.inline.} = (if c == White: Black else: White) -func `+`*(a, b: Square): Square {.inline.} = (a.rank + b.rank, a.file + b.file) -func `-`*(a: Square): Square {.inline.} = (-a.rank, -a.file) -func `-`*(a, b: Square): Square{.inline.} = (a.rank - b.rank, a.file - b.file) -func isValid*(a: Square): bool {.inline.} = a.rank in 0..7 and a.file in 0..7 -func isLightSquare*(a: Square): bool {.inline.} = (a.rank + a.file and 2) == 0 -func makeSquare*(rank, file: SomeInteger): Square = (rank: rank.int8, file: file.int8) +func isValid*(a: Square): bool {.inline.} = a.int8 in 0..63 +func isLightSquare*(a: Square): bool {.inline.} = (a.int8 and 2) == 0 +func `==`*(a, b: Square): bool {.inline.} = a.int8 == b.int8 +func `!=`*(a, b: Square): bool {.inline.} = a.int8 != b.int8 +func `-`*(a, b: Square): Square {.inline.} = Square(a.int8 - b.int8) +func `-`*(a: Square, b: SomeInteger): Square {.inline.} = Square(a.int8 - b.int8) +func `-`*(a: SomeInteger, b: Square): Square {.inline.} = Square(a.int8 - b.int8) -func fileToColumn*(file: int): int8 {.inline.} = - ## Converts a chess file (1-indexed) - ## into a 0-indexed column value for our - ## board. This converter is necessary because - ## chess positions are indexed differently with - ## respect to our internal representation - const indeces: array[8, int8] = [7, 6, 5, 4, 3, 2, 1, 0] - return indeces[file - 1] +func colFromSquare*(square: Square): int8 = square.int8 mod 8 + 1 +func rowFromSquare*(square: Square): int8 = square.int8 div 8 + 1 -func rowToRank*(row: int): int8 {.inline.} = - ## Converts a row into our grid into - ## a chess rank - const indeces: array[8, int8] = [8, 7, 6, 5, 4, 3, 2, 1] - return indeces[row] + +func makeSquare*(rank, file: SomeInteger): Square = Square(rank * 8 + file) -func algebraicToSquare*(s: string): Square = +proc toSquare*(s: string): Square {.discardable.} = ## Converts a square square from algebraic ## notation to its corresponding row and column ## in the chess grid (0 indexed) @@ -84,13 +62,16 @@ func algebraicToSquare*(s: string): Square = if s[1] notin '1'..'8': raise newException(ValueError, &"algebraic position has invalid second character ('{s[1]}')") - let rank = int8(uint8(s[0]) - uint8('a')) - # Convert the file character to a number - let file = fileToColumn(int8(uint8(s[1]) - uint8('0'))) - return (file, rank) + return Square((s[0].uint8 - uint8('a')) + ((s[1].uint8 - uint8('1')) xor 7) * 8) -func squareToAlgebraic*(square: Square): string {.inline.} = +proc toAlgebraic*(square: Square): string {.inline.} = ## Converts a square from our internal rank/file ## notation to a square in algebraic notation - return &"{rowToRank(square.rank)}{char(uint8(square.file) + uint8('a'))}" \ No newline at end of file + let + file = char('a'.uint8 + (square.uint64 and 7)) + rank = char('1'.uint8 + ((square.uint64 div 8) xor 7)) + return &"{file}{rank}" + + +proc `$`*(square: Square): string = square.toAlgebraic() \ No newline at end of file