diff --git a/Chess/nimfish/nimfish.nim b/Chess/nimfish/nimfish.nim index 6f259f1..7ad1d72 100644 --- a/Chess/nimfish/nimfish.nim +++ b/Chess/nimfish/nimfish.nim @@ -14,7 +14,6 @@ import std/strutils import std/strformat -import std/bitops import nimfishpkg/bitboards @@ -49,9 +48,9 @@ type # The side to move sideToMove: PieceColor # Positional bitboards for all pieces - pieces: tuple[white, black: tuple[king, queens, rooks, bishops, knights, pawns: Bitboard]] - # Pinned pieces for each side - pins: tuple[white, black: Bitboard] + pieces: array[2, array[6, Bitboard]] + # Pinned pieces for the current side to move + pins: Bitboard # Pieces checking the current side to move checkers: Bitboard @@ -135,6 +134,7 @@ func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White proc inCheck*(self: ChessBoard): bool +proc fromChar*(c: char): Piece proc newChessboard: ChessBoard = @@ -142,50 +142,25 @@ proc newChessboard: ChessBoard = new(result) for i in 0..63: result.grid[i] = nullPiece() - result.position = Position(enPassantSquare: nullSquare(), sideToMove: White) + result.position = Position(enPassantSquare: nullSquare(), sideToMove: White, pieces: [ + [Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)], + [Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)]]) + # Indexing operations 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 `[]`(self: array[2, array[6, Bitboard]], color: PieceColor): ptr array[6, Bitboard] {.inline.} = addr self[color.int] +func `[]`(self: array[6, Bitboard], kind: PieceKind): Bitboard {.inline.} = self[kind.int] +func `[]=`(self: var array[6, Bitboard], kind: PieceKind, bitboard: Bitboard) {.inline.} = self[kind.int] = bitboard + + func getBitboard*(self: ChessBoard, kind: PieceKind, color: PieceColor): Bitboard = ## Returns the positional bitboard for the given piece kind and color - case color: - of White: - case kind: - of Pawn: - return self.position.pieces.white.pawns - of Knight: - return self.position.pieces.white.knights - of Bishop: - return self.position.pieces.white.bishops - of Rook: - return self.position.pieces.white.rooks - of Queen: - return self.position.pieces.white.queens - of King: - return self.position.pieces.white.king - else: - discard - of Black: - case kind: - of Pawn: - return self.position.pieces.black.pawns - of Knight: - return self.position.pieces.black.knights - of Bishop: - return self.position.pieces.black.bishops - of Rook: - return self.position.pieces.black.rooks - of Queen: - return self.position.pieces.black.queens - of King: - return self.position.pieces.black.king - else: - discard - else: - discard + return self.position.pieces[color.int][kind.int] + func getBitboard*(self: ChessBoard, piece: Piece): Bitboard = @@ -221,52 +196,11 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = case c.toLowerAscii(): # Piece of 'r', 'n', 'b', 'q', 'k', 'p': - let - 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.} - piece = Piece(kind: PieceKind(c.toLowerAscii()), color: if c.isUpperAscii(): White else: Black) - case piece.color: - of Black: - case piece.kind: - of Pawn: - result.position.pieces.black.pawns.uint64.uint64.setBit(bitIndex) - of Bishop: - result.position.pieces.black.bishops.uint64.setBit(bitIndex) - of Knight: - result.position.pieces.black.knights.uint64.setBit(bitIndex) - of Rook: - result.position.pieces.black.rooks.uint64.setBit(bitIndex) - of Queen: - result.position.pieces.black.queens.uint64.setBit(bitIndex) - of King: - if result.position.pieces.black.king != 0: - raise newException(ValueError, "invalid position: exactly one king of each color must be present") - result.position.pieces.black.king.uint64.setBit(bitIndex) - else: - discard - of White: - case piece.kind: - of Pawn: - result.position.pieces.white.pawns.uint64.setBit(bitIndex) - of Bishop: - result.position.pieces.white.bishops.uint64.setBit(bitIndex) - of Knight: - result.position.pieces.white.knights.uint64.setBit(bitIndex) - of Rook: - result.position.pieces.white.rooks.uint64.setBit(bitIndex) - of Queen: - result.position.pieces.white.queens.uint64.setBit(bitIndex) - of King: - if result.position.pieces.white.king != 0: - raise newException(ValueError, "invalid position: exactly one king of each color must be present") - result.position.pieces.white.king.uint64.setBit(bitIndex) - else: - discard - else: - discard + let square: Square = makeSquare(row, column) + piece = c.fromChar() + var b = result.position.pieces[piece.color][piece.kind] + b.setBit(square) + result.position.pieces[piece.color][piece.kind] = b result.grid[square] = piece inc(column) of '/': @@ -340,9 +274,6 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = else: raise newException(ValueError, "invalid FEN: too many fields in FEN string") inc(index) - if result.position.pieces.white.king == 0 or result.position.pieces.black.king == 0: - # Both kings must be on the board - raise newException(ValueError, "invalid position: exactly one king of each color must be present") proc newDefaultChessboard*: ChessBoard {.inline.} = @@ -355,41 +286,8 @@ proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int = ## Returns the number of pieces with ## the given color and type in the ## current position - case color: - of White: - case kind: - of Pawn: - return self.position.pieces.white.pawns.uint64.countSetBits() - of Bishop: - return self.position.pieces.white.bishops.uint64.countSetBits() - of Knight: - return self.position.pieces.white.knights.uint64.countSetBits() - of Rook: - return self.position.pieces.white.rooks.uint64.countSetBits() - of Queen: - return self.position.pieces.white.queens.uint64.countSetBits() - of King: - return self.position.pieces.white.king.uint64.countSetBits() - else: - raise newException(ValueError, "invalid piece type") - of Black: - case kind: - of Pawn: - return self.position.pieces.black.pawns.uint64.countSetBits() - of Bishop: - return self.position.pieces.black.bishops.uint64.countSetBits() - of Knight: - return self.position.pieces.black.knights.uint64.countSetBits() - of Rook: - return self.position.pieces.black.rooks.uint64.countSetBits() - of Queen: - return self.position.pieces.black.queens.uint64.countSetBits() - of King: - return self.position.pieces.black.king.uint64.countSetBits() - else: - raise newException(ValueError, "invalid piece type") - of None: - raise newException(ValueError, "invalid piece color") + return self.position.pieces[color][kind].countSetBits() + func countPieces*(self: ChessBoard, piece: Piece): int {.inline.} = @@ -474,16 +372,9 @@ func getFlags*(move: Move): seq[MoveFlag] = proc getOccupancyFor(self: ChessBoard, color: PieceColor): Bitboard = ## Get the occupancy bitboard for every piece of the given color - case color: - of White: - 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: - 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: - # huh? - discard + result = Bitboard(0) + for b in self.position.pieces[color][]: + result = result or b proc getOccupancy(self: ChessBoard): Bitboard = @@ -738,81 +629,17 @@ proc removePieceFromBitboard(self: ChessBoard, square: Square) = ## Removes a piece at the given square in the chessboard from ## its respective bitboard let piece = self.grid[square] - case piece.color: - of White: - case piece.kind: - of Pawn: - self.position.pieces.white.pawns.uint64.clearBit(square.int8) - of Bishop: - self.position.pieces.white.bishops.uint64.clearBit(square.int8) - of Knight: - self.position.pieces.white.knights.uint64.clearBit(square.int8) - of Rook: - self.position.pieces.white.rooks.uint64.clearBit(square.int8) - of Queen: - self.position.pieces.white.queens.uint64.clearBit(square.int8) - of King: - self.position.pieces.white.king.uint64.clearBit(square.int8) - of Empty: - doAssert false, &"cannot remove empty white piece from {square}" - of Black: - case piece.kind: - of Pawn: - self.position.pieces.black.pawns.uint64.clearBit(square.int8) - of Bishop: - self.position.pieces.black.bishops.uint64.clearBit(square.int8) - of Knight: - self.position.pieces.black.knights.uint64.clearBit(square.int8) - of Rook: - self.position.pieces.black.rooks.uint64.clearBit(square.int8) - of Queen: - self.position.pieces.black.queens.uint64.clearBit(square.int8) - of King: - self.position.pieces.black.king.uint64.clearBit(square.int8) - of Empty: - doAssert false, &"cannot remove empty black piece from {square}" - else: - doAssert false, &"cannot remove empty piece from colorless square {square}" + var b = self.position.pieces[piece.color][piece.kind] + b.clearBit(square) + self.position.pieces[piece.color][piece.kind] = b proc addPieceToBitboard(self: ChessBoard, square: Square, piece: Piece) = ## Adds the given piece at the given square in the chessboard to ## its respective bitboard - case piece.color: - of White: - case piece.kind: - of Pawn: - self.position.pieces.white.pawns.uint64.setBit(square.int8) - of Bishop: - self.position.pieces.white.bishops.uint64.setBit(square.int8) - of Knight: - self.position.pieces.white.knights.uint64.setBit(square.int8) - of Rook: - self.position.pieces.white.rooks.uint64.setBit(square.int8) - of Queen: - self.position.pieces.white.queens.uint64.setBit(square.int8) - of King: - 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.int8) - of Bishop: - self.position.pieces.black.bishops.uint64.setBit(square.int8) - of Knight: - self.position.pieces.black.knights.uint64.setBit(square.int8) - of Rook: - self.position.pieces.black.rooks.uint64.setBit(square.int8) - of Queen: - self.position.pieces.black.queens.uint64.setBit(square.int8) - of King: - self.position.pieces.black.king.uint64.setBit(square.int8) - else: - discard - else: - discard + var b = self.position.pieces[piece.color][piece.kind] + b.setBit(square) + self.position.pieces[piece.color][piece.kind] = b proc removePiece(self: ChessBoard, square: Square) = @@ -924,29 +751,29 @@ proc update*(self: ChessBoard) = ## in the chessboard for i in 0..63: self.grid[i] = nullPiece() - for sq in self.position.pieces.white.pawns: + for sq in self.position.pieces[White][Pawn]: self.grid[sq] = Piece(color: White, kind: Pawn) - for sq in self.position.pieces.black.pawns: + for sq in self.position.pieces[Black][Pawn]: self.grid[sq] = Piece(color: Black, kind: Pawn) - for sq in self.position.pieces.white.bishops: + for sq in self.position.pieces[White][Bishop]: self.grid[sq] = Piece(color: White, kind: Bishop) - for sq in self.position.pieces.black.bishops: + for sq in self.position.pieces[Black][Bishop]: self.grid[sq] = Piece(color: Black, kind: Bishop) - for sq in self.position.pieces.white.knights: + for sq in self.position.pieces[White][Knight]: self.grid[sq] = Piece(color: White, kind: Knight) - for sq in self.position.pieces.black.knights: + for sq in self.position.pieces[Black][Knight]: self.grid[sq] = Piece(color: Black, kind: Knight) - for sq in self.position.pieces.white.rooks: + for sq in self.position.pieces[White][Rook]: self.grid[sq] = Piece(color: White, kind: Rook) - for sq in self.position.pieces.black.rooks: + for sq in self.position.pieces[Black][Rook]: self.grid[sq] = Piece(color: Black, kind: Rook) - for sq in self.position.pieces.white.queens: + for sq in self.position.pieces[White][Queen]: self.grid[sq] = Piece(color: White, kind: Queen) - for sq in self.position.pieces.black.queens: + for sq in self.position.pieces[Black][Queen]: self.grid[sq] = Piece(color: Black, kind: Queen) - for sq in self.position.pieces.white.king: + for sq in self.position.pieces[White][King]: self.grid[sq] = Piece(color: White, kind: King) - for sq in self.position.pieces.black.king: + for sq in self.position.pieces[Black][King]: self.grid[sq] = Piece(color: Black, kind: King) @@ -973,9 +800,47 @@ proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} = proc toChar*(piece: Piece): char = + case piece.kind: + of Bishop: + result = 'b' + of King: + result = 'k' + of Knight: + result = 'n' + of Pawn: + result = 'p' + of Queen: + result = 'q' + of Rook: + result = 'r' + else: + discard if piece.color == White: - return char(piece.kind).toUpperAscii() - return char(piece.kind) + result = result.toUpperAscii() + + +proc fromChar*(c: char): Piece = + var + kind: PieceKind + color = Black + case c.toLowerAscii(): + of 'b': + kind = Bishop + of 'k': + kind = King + of 'n': + kind = Knight + of 'p': + kind = Pawn + of 'q': + kind = Queen + of 'r': + kind = Rook + else: + discard + if c.isUpperAscii(): + color = White + result = Piece(kind: kind, color: color) proc `$`*(self: ChessBoard): string = diff --git a/Chess/nimfish/nimfishpkg/bitboards.nim b/Chess/nimfish/nimfishpkg/bitboards.nim index c51fbc4..db7c26b 100644 --- a/Chess/nimfish/nimfishpkg/bitboards.nim +++ b/Chess/nimfish/nimfishpkg/bitboards.nim @@ -45,6 +45,13 @@ 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 countSetBits*(a: Bitboard): int = a.uint64.countSetBits() +func countLeadingZeroBits*(a: Bitboard): int = a.uint64.countLeadingZeroBits() +func countTrailingZeroBits*(a: Bitboard): int = a.uint64.countTrailingZeroBits() +func clearBit*(a: var Bitboard, bit: SomeInteger) = a.uint64.clearBit(bit) +func setBit*(a: var Bitboard, bit: SomeInteger) = a.uint64.setBit(bit) +func clearBit*(a: var Bitboard, bit: Square) = a.uint64.clearBit(bit.int) +func setBit*(a: var Bitboard, bit: Square) = a.uint64.setBit(bit.int) func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64 diff --git a/Chess/nimfish/nimfishpkg/pieces.nim b/Chess/nimfish/nimfishpkg/pieces.nim index fa4c874..8b503b4 100644 --- a/Chess/nimfish/nimfishpkg/pieces.nim +++ b/Chess/nimfish/nimfishpkg/pieces.nim @@ -9,25 +9,26 @@ type PieceColor* = enum ## A piece color enumeration - None = 0'i8 - White - Black + White = 0'i8 + Black = 1 + None PieceKind* = enum ## A chess piece enumeration - Empty = 0'i8, # No piece - Bishop = 'b', - King = 'k' - Knight = 'n', - Pawn = 'p', - Queen = 'q', - Rook = 'r', + Bishop = 0'i8 + King = 1 + Knight = 2 + Pawn = 3 + Queen = 4 + Rook = 5 + Empty = 6 # No piece + Piece* = object ## A chess piece color*: PieceColor kind*: PieceKind - + func nullPiece*: Piece {.inline.} = Piece(kind: Empty, color: None) func nullSquare*: Square {.inline.} = Square(-1'i8) diff --git a/Chess/nimfish/nimfishpkg/tui.nim b/Chess/nimfish/nimfishpkg/tui.nim index 099c1f1..63a4491 100644 --- a/Chess/nimfish/nimfishpkg/tui.nim +++ b/Chess/nimfish/nimfishpkg/tui.nim @@ -421,6 +421,8 @@ proc commandLoop*: int = echo &"Castling rights for {($board.getSideToMove()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}" of "check": echo &"{board.getSideToMove()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}" + of "quit": + return 0 else: echo &"Unknown command '{cmd[0]}'. Type 'help' for more information." except IOError: