CPG/Chess/nimfish/nimfishpkg/board.nim

179 lines
5.2 KiB
Nim

# Copyright 2024 Mattia Giambirtone & All Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## Implementation of a simple chessboard
import pieces
import magics
import moves
import rays
import bitboards
import position
import zobrist
export pieces, position, bitboards, moves, magics, rays, zobrist
type
Chessboard* = object
## A chessboard
# The current position
position*: Position
# List of all previously reached positions
positions*: seq[Position]
func toFEN*(self: Chessboard): string
proc newChessboard*: Chessboard =
## Returns a new, empty chessboard
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White)
for i in Square(0)..Square(63):
result.position.mailbox[i] = nullPiece()
proc newChessboardFromFEN*(fen: string): Chessboard =
## Initializes a chessboard with the
## position encoded by the given FEN string
result = newChessboard()
result.position = loadFEN(fen)
proc newDefaultChessboard*: Chessboard {.inline.} =
## Initializes a chessboard with the
## starting position
return newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
func inCheck*(self: Chessboard): bool {.inline.} =
## Returns if the current side to move is in check
return self.position.inCheck()
proc canCastle*(self: Chessboard): tuple[queen, king: bool] {.inline.} =
## Returns if the current side to move can castle
return self.position.canCastle()
func `$`*(self: Chessboard): string = $self.position
func pretty*(self: Chessboard): string =
## Returns a colored version of the
## current position for easier visualization
return self.position.pretty()
func toFEN*(self: Chessboard): string =
## Returns a FEN string of the current
## position in the chessboard
return self.position.toFEN()
func drawnByRepetition*(self: Chessboard): bool =
## Returns whether the current position is a draw
## by repetition
# TODO: Improve this
var i = self.positions.high()
var count = 0
while i >= 0:
if self.position.zobristKey == self.positions[i].zobristKey:
inc(count)
if count == 2:
return true
if self.positions[i].halfMoveClock == 0:
# Position was reached via a pawn move or
# capture: cannot repeat beyond this point!
return false
dec(i)
proc isInsufficientMaterial*(self: Chessboard): bool =
## Returns whether the current position is drawn
## due to insufficient mating material. Note that
## this is not a strict implementation of the FIDE
## rule about material draws due to the fact that
## it would be basically impossible to implement those
## efficiently
# Break out early if there's more than 4 pieces on the
# board
let occupancy = self.position.getOccupancy()
if occupancy.countSquares() > 4:
return false
# KvK is a draw
if occupancy.countSquares() == 2:
# Only the two kings are left
return true
let
sideToMove = self.position.sideToMove
nonSideToMove = sideToMove.opposite()
# Break out early if there's any pawns left on the board
if self.position.getBitboard(Pawn, sideToMove) != 0:
return false
if self.position.getBitboard(Pawn, nonSideToMove) != 0:
return false
# If there's any queens or rooks on the board, break out early too
let
friendlyQueens = self.position.getBitboard(Queen, sideToMove)
enemyQueens = self.position.getBitboard(Queen, nonSideToMove)
friendlyRooks = self.position.getBitboard(Rook, sideToMove)
enemyRooks = self.position.getBitboard(Rook, nonSideToMove)
if (friendlyQueens or enemyQueens or friendlyRooks or enemyRooks).countSquares() != 0:
return false
# KNvK is a draw
let knightCount = (self.position.getBitboard(Knight, sideToMove) or self.position.getBitboard(Knight, nonSideToMove)).countSquares()
# More than one knight (doesn't matter which side), not a draw
if knightCount > 1:
return false
# KBvK is a draw
let bishopCount = (self.position.getBitboard(Bishop, sideToMove) or self.position.getBitboard(Bishop, nonSideToMove)).countSquares()
if bishopCount + knightCount > 1:
return false
# Maybe TODO: KBBvK and KBvKB (these should be handled by search anyway)
return true
func isDrawn*(self: Chessboard): bool =
## Returns whether the given position is
## drawn
if self.position.halfMoveClock >= 100:
# Draw by 50 move rule
return true
if self.drawnByRepetition():
return true
if self.isInsufficientMaterial():
return true