From 6792cf93a95ebe846aa51ba9ebbbd73c4ef9a506 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 11 Dec 2023 14:29:51 +0100 Subject: [PATCH] add: chess data example with Rchess --- .gitignore | 8 +- dataAnalysis/chess/Makefile | 26 + dataAnalysis/chess/Rchess/DESCRIPTION | 15 + dataAnalysis/chess/Rchess/NAMESPACE | 4 + dataAnalysis/chess/Rchess/R/RcppExports.R | 78 ++ .../chess/Rchess/inst/include/Rchess.h | 82 ++ .../inst/include/SchachHoernchen/Board.cpp | 1205 +++++++++++++++++ .../inst/include/SchachHoernchen/Board.h | 224 +++ .../inst/include/SchachHoernchen/HashTable.h | 150 ++ .../inst/include/SchachHoernchen/Move.h | 305 +++++ .../Rchess/inst/include/SchachHoernchen/bit.h | 63 + .../inst/include/SchachHoernchen/nullstream.h | 17 + .../inst/include/SchachHoernchen/search.cpp | 700 ++++++++++ .../inst/include/SchachHoernchen/search.h | 203 +++ .../inst/include/SchachHoernchen/syncstream.h | 38 + .../inst/include/SchachHoernchen/types.h | 195 +++ .../inst/include/SchachHoernchen/uci.cpp | 964 +++++++++++++ .../Rchess/inst/include/SchachHoernchen/uci.h | 103 ++ .../inst/include/SchachHoernchen/utils.h | 698 ++++++++++ dataAnalysis/chess/Rchess/src/Makevars | 4 + dataAnalysis/chess/Rchess/src/RcppExports.cpp | 196 +++ dataAnalysis/chess/Rchess/src/data_gen.cpp | 129 ++ dataAnalysis/chess/Rchess/src/fen2int.cpp | 52 + dataAnalysis/chess/Rchess/src/print.cpp | 71 + dataAnalysis/chess/Rchess/src/read_cyclic.cpp | 94 ++ dataAnalysis/chess/Rchess/src/sample.cpp | 92 ++ dataAnalysis/chess/Rchess/src/uci.cpp | 146 ++ dataAnalysis/chess/Rchess/src/zzz.cpp | 21 + dataAnalysis/chess/gmlm_chess.R | 215 +++ dataAnalysis/chess/pgn2fen.cpp | 144 ++ dataAnalysis/chess/preprocessing.sh | 31 + 31 files changed, 6271 insertions(+), 2 deletions(-) create mode 100644 dataAnalysis/chess/Makefile create mode 100644 dataAnalysis/chess/Rchess/DESCRIPTION create mode 100644 dataAnalysis/chess/Rchess/NAMESPACE create mode 100644 dataAnalysis/chess/Rchess/R/RcppExports.R create mode 100644 dataAnalysis/chess/Rchess/inst/include/Rchess.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.cpp create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/HashTable.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Move.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/bit.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/nullstream.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/search.cpp create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/search.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/syncstream.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/types.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/uci.cpp create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/uci.h create mode 100644 dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/utils.h create mode 100644 dataAnalysis/chess/Rchess/src/Makevars create mode 100644 dataAnalysis/chess/Rchess/src/RcppExports.cpp create mode 100644 dataAnalysis/chess/Rchess/src/data_gen.cpp create mode 100644 dataAnalysis/chess/Rchess/src/fen2int.cpp create mode 100644 dataAnalysis/chess/Rchess/src/print.cpp create mode 100644 dataAnalysis/chess/Rchess/src/read_cyclic.cpp create mode 100644 dataAnalysis/chess/Rchess/src/sample.cpp create mode 100644 dataAnalysis/chess/Rchess/src/uci.cpp create mode 100644 dataAnalysis/chess/Rchess/src/zzz.cpp create mode 100644 dataAnalysis/chess/gmlm_chess.R create mode 100644 dataAnalysis/chess/pgn2fen.cpp create mode 100755 dataAnalysis/chess/preprocessing.sh diff --git a/.gitignore b/.gitignore index 45c6747..1d81be5 100644 --- a/.gitignore +++ b/.gitignore @@ -43,10 +43,11 @@ *.idb *.pdb -## R environment, data and pacakge build intermediate files/folders +## R environment, data and package build intermediate files/folders # R Data Object files *.Rds *.rds +*.Rdata # Example code in package build process *-Ex.R @@ -110,7 +111,10 @@ simulations/ mlda_analysis/ References/ -dataAnalysis/ +dataAnalysis/* +!dataAnalysis/chess/ +dataAnalysis/chess/*.fen + *.csv *.csv.log diff --git a/dataAnalysis/chess/Makefile b/dataAnalysis/chess/Makefile new file mode 100644 index 0000000..b8bb4c7 --- /dev/null +++ b/dataAnalysis/chess/Makefile @@ -0,0 +1,26 @@ +# Source dependency setup +INCLUDE = Rchess/inst/include/SchachHoernchen +SRC = $(notdir $(wildcard $(INCLUDE)/*.cpp)) +OBJ = $(SRC:.cpp=.o) + +# Compiler config +CC = g++ +FLAGS = -I$(INCLUDE) -Wall -Wextra -Wpedantic -pedantic -pthread -O3 -march=native -mtune=native +CPPFLAGS = $(FLAGS) -std=c++17 +LDFLAGS = $(FLAGS) + +.PHONY: all clean + +%.o: $(INCLUDE)/%.cpp + $(CC) $(CPPFLAGS) -c $< -o $(notdir $@) + +pgn2fen.o: pgn2fen.cpp + $(CC) $(CPPFLAGS) -c $< -o $@ + +pgn2fen: pgn2fen.o $(OBJ) + $(CC) $(LDFLAGS) -o $@ $^ + +all: pgn2fen + +clean: + rm -f *.out *.o *.h.gch pgn2fen diff --git a/dataAnalysis/chess/Rchess/DESCRIPTION b/dataAnalysis/chess/Rchess/DESCRIPTION new file mode 100644 index 0000000..bda4761 --- /dev/null +++ b/dataAnalysis/chess/Rchess/DESCRIPTION @@ -0,0 +1,15 @@ +Package: Rchess +Type: Package +Title: Wrapper to the SchachHoernchen engine +Version: 1.0 +Date: 2022-06-12 +Author: loki +Maintainer: Your Name +Description: Basic wrapper to the underlying C++ code of the SchachHoernchen + engine. Primarely intended to provide chess specific data processing. +Encoding: UTF-8 +License: GPL (>= 2) +Imports: Rcpp (>= 1.0.8) +LinkingTo: Rcpp +SystemRequirements: c++17 +RoxygenNote: 7.2.0 diff --git a/dataAnalysis/chess/Rchess/NAMESPACE b/dataAnalysis/chess/Rchess/NAMESPACE new file mode 100644 index 0000000..5c2062e --- /dev/null +++ b/dataAnalysis/chess/Rchess/NAMESPACE @@ -0,0 +1,4 @@ +useDynLib(Rchess, .registration=TRUE) +importFrom(Rcpp, evalCpp) +exportPattern("^[[:alpha:]]+") +S3method(print, board) diff --git a/dataAnalysis/chess/Rchess/R/RcppExports.R b/dataAnalysis/chess/Rchess/R/RcppExports.R new file mode 100644 index 0000000..99125ab --- /dev/null +++ b/dataAnalysis/chess/Rchess/R/RcppExports.R @@ -0,0 +1,78 @@ +# Generated by using Rcpp::compileAttributes() -> do not edit by hand +# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 + +#' Specialized version of `read_cyclic.cpp` taylored to work in conjunction with +#' `gmlm_chess()` as data generator to provide random draws from a FEN data set +#' with scores filtered to be in in the range `score_min` to `score_max`. +#' +data.gen <- function(file, sample_size, score_min, score_max) { + .Call(`_Rchess_data_gen`, file, sample_size, score_min, score_max) +} + +#' Convert a legal FEN string to a 3D binary (integer with 0-1 entries) array +fen2int <- function(boards) { + .Call(`_Rchess_fen2int`, boards) +} + +#' Reads lines from a text file with recycling. +#' +read.cyclic <- function(file, nrows = 1000L, skip = 100L, start = 1L, line_len = 64L) { + .Call(`_Rchess_read_cyclic`, file, nrows, skip, start, line_len) +} + +#' Samples a legal move from a given position +sample.move <- function(pos) { + .Call(`_Rchess_sample_move`, pos) +} + +#' Samples a random FEN (position) by applying `ply` random moves to the start +#' position. +#' +#' @param nr number of positions to sample +#' @param min_depth minimum number of random ply's to generate random positions +#' @param max_depth maximum number of random ply's to generate random positions +sample.fen <- function(nr, min_depth = 4L, max_depth = 20L) { + .Call(`_Rchess_sample_fen`, nr, min_depth, max_depth) +} + +#' Converts a FEN string to a Board (position) or return the current internal state +board <- function(fen = "") { + .Call(`_Rchess_board`, fen) +} + +print.board <- function(fen = "") { + invisible(.Call(`_Rchess_print_board`, fen)) +} + +print.moves <- function(fen = "") { + invisible(.Call(`_Rchess_print_moves`, fen)) +} + +print.bitboards <- function(fen = "") { + invisible(.Call(`_Rchess_print_bitboards`, fen)) +} + +position <- function(pos, moves, san = FALSE) { + .Call(`_Rchess_position`, pos, moves, san) +} + +perft <- function(depth = 6L) { + invisible(.Call(`_Rchess_perft`, depth)) +} + +go <- function(depth = 6L) { + .Call(`_Rchess_go`, depth) +} + +ucinewgame <- function() { + invisible(.Call(`_Rchess_ucinewgame`)) +} + +.onLoad <- function(libname, pkgname) { + invisible(.Call(`_Rchess_onLoad`, libname, pkgname)) +} + +.onUnload <- function(libpath) { + invisible(.Call(`_Rchess_onUnload`, libpath)) +} + diff --git a/dataAnalysis/chess/Rchess/inst/include/Rchess.h b/dataAnalysis/chess/Rchess/inst/include/Rchess.h new file mode 100644 index 0000000..3b87508 --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/Rchess.h @@ -0,0 +1,82 @@ +#ifndef INCLUDE_GUARD_RCHESS_TYPES +#define INCLUDE_GUARD_RCHESS_TYPES + +#include + +#include "SchachHoernchen/Move.h" +#include "SchachHoernchen/Board.h" +#include "SchachHoernchen/uci.h" + +namespace Rcpp { + template <> Move as(SEXP); + template <> SEXP wrap(const Move&); + + template <> Board as(SEXP); + template <> SEXP wrap(const Board&); +} /* namespace Rcpp */ + +#include + +namespace Rcpp { + // Convert a coordinate encoded move string into a Move + template <> + Move as(SEXP obj) { + // parse (and validate) the move + bool parseError = false; + Move move = UCI::parseMove(Rcpp::as(obj), parseError); + if (parseError) { + Rcpp::stop("Error parsing move"); + } + return move; + } + + // Convert a Move into an `R` character + template <> + SEXP wrap(const Move& move) { + return Rcpp::CharacterVector::create(UCI::formatMove(move)); + } + + // Convert a FEN string to a board + template <> + Board as(SEXP obj) { + bool parseError = false; + Board board; + std::string fen = Rcpp::as(obj); + if (fen != "startpos") { + board.init(fen, parseError); + } + if (parseError) { + Rcpp::stop("Parsing FEN failed"); + } + return board; + } + + // Convert board to `R` board class (as character FEN string) + template <> + SEXP wrap(const Board& board) { + auto obj = Rcpp::CharacterVector::create(board.fen()); + obj.attr("class") = "board"; + return obj; + } + + // Convert a character vector or list to a vector of Boards + template <> + std::vector as(SEXP obj) { + // Convert SEXP to be a vector of string + auto fens = Rcpp::as>(obj); + // Try to parse every string as a Board from a FEN + std::vector boards(fens.size()); + for (int i = 0; i < fens.size(); ++i) { + bool parseError = false; + if (fens[i] != "startpos") { + boards[i].init(fens[i], parseError); + } + if (parseError) { + Rcpp::stop("Parsing FEN nr. %d failed", i + 1); + } + } + return boards; + } +} /* namespace Rcpp */ + +#endif /* INCLUDE_GUARD_RCHESS_TYPES */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.cpp b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.cpp new file mode 100644 index 0000000..d83fb56 --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.cpp @@ -0,0 +1,1205 @@ +#include +#include +#include +#include +#include + +#include "types.h" +#include "utils.h" +#include "Move.h" +#include "Board.h" + +inline enum piece Board::sqPiece(const u64 sqMask) const { + if (!(sqMask & (_bitBoard[white] | _bitBoard[black]))) { + return static_cast(0); + } + return static_cast( + ( pawn * static_cast(sqMask & _bitBoard[pawn]) ) + | (knight * static_cast(sqMask & _bitBoard[knight])) + | (bishop * static_cast(sqMask & _bitBoard[bishop])) + | ( rook * static_cast(sqMask & _bitBoard[rook]) ) + | ( queen * static_cast(sqMask & _bitBoard[queen]) ) + | ( king * static_cast(sqMask & _bitBoard[king]) ) + ); +} +enum piece Board::piece(Index sq) const { + return sqPiece(bitMask(sq)); +} +enum piece Board::color(Index sq) const { + u64 sqMask = bitMask(sq); + if (sqMask & _bitBoard[white]) { + return white; + } else if (sqMask & _bitBoard[black]) { + return black; + } + return static_cast(-1); +} + +bool Board::castleRight(enum piece color, enum location side) const { + assert((side == KingSide) || (side == QueenSide)); + assert((color == white) || (color == black)); + + if (color == white) { + if (side == KingSide) { return _castle.whiteKingSide; } + else { return _castle.whiteQueenSide; } + } else { + if (side == KingSide) { return _castle.blackKingSide; } + else { return _castle.blackQueenSide; } + } +} + +char Board::formatPiece(Index fileIndex, Index rankIndex) const { + assert(fileIndex < 8); + assert(rankIndex < 8); + + return formatPiece(8 * rankIndex + fileIndex); +} +char Board::formatPiece(Index sq) const { + assert(sq < 64); + + u64 sqMask = bitMask(sq); + + if ((_bitBoard[white] | _bitBoard[black]) & sqMask) { + enum piece type = piece(sq); + switch (type) { + case rook: return (_bitBoard[white] & sqMask) ? 'R' : 'r'; + case knight: return (_bitBoard[white] & sqMask) ? 'N' : 'n'; + case bishop: return (_bitBoard[white] & sqMask) ? 'B' : 'b'; + case queen: return (_bitBoard[white] & sqMask) ? 'Q' : 'q'; + case king: return (_bitBoard[white] & sqMask) ? 'K' : 'k'; + case pawn: return (_bitBoard[white] & sqMask) ? 'P' : 'p'; + default: + return '?'; + } + } + + return ' '; +} + +std::string Board::fen(const bool withCounts) const { + std::ostringstream out; + + // Part 1 - Piece Placement + for (Index rankIndex = 0, count = 0; rankIndex < 8; rankIndex++) { + for (Index fileIndex = 0; fileIndex < 8; fileIndex++) { + char c = formatPiece(fileIndex, rankIndex); + if (c == ' ') { + count++; + } else if (count) { + out << static_cast(count) << c; + count = 0; + } else { + out << c; + } + } + if (count) { + out << static_cast(count); + count = 0; + } + out << ((rankIndex < 7) ? '/' : ' '); + } + + // Part 2 - Side to Move + out << (isWhiteTurn() ? 'w' : 'b') << ' '; + + // Part 3 - Castling Rights + if (!(_castle.whiteKingSide | _castle.whiteQueenSide + | _castle.blackKingSide | _castle.blackQueenSide)) { + out << "- "; + } else { + if (_castle.whiteKingSide) { out << 'K'; }; + if (_castle.whiteQueenSide) { out << 'Q'; }; + if (_castle.blackKingSide) { out << 'k'; }; + if (_castle.blackQueenSide) { out << 'q'; }; + out << ' '; + } + + // Part 4 - En passant target square + if (_enPassant > 63) { + out << '-'; + } else { + out << static_cast('a' + (_enPassant % 8)) // file + << static_cast('8' - (_enPassant / 8)); // rank + } + + if (withCounts) { + // Part 5 - Half Move Clock + out << ' ' << _halfMoveClock << ' '; + + // Part 6 - Full Move counter + out << ((_plyCount + 1U) / 2U); + } + + return out.str(); +} + +/** + * The hash lookup consists of + * - 6 pieces for each color and each square -> 6 * 2 * 64 = 768 + * - en passant target file (only file) -> 8 + * - king and queen side castling rights for each color -> 2 * 2 = 4 + * - and the side to move -> 1 + * resulting in 781 table entries sum = 781 + * the castling rights are index by 776 + [ 0 | 1 | 2 | 3 ] + * and the side to move is just 780 + */ +u64 Board::rehash() const { + u64 hash = 0; + + // Hash all the pieces on the board + for (enum piece piece : {pawn, rook, knight, bishop, queen, king}) { + for (u64 sq = _bitBoard[piece]; sq; sq &= sq - 1) { + Index sqIndex = bitScanLS(sq); + hash ^= hashTable[hashIndex(this->piece(sqIndex), color(sqIndex), sqIndex)]; + } + } + + // E.p. target file hash + if (_enPassant < static_cast(64)) { + hash ^= hashTable[hashIndex(_enPassant)]; + } + + // Next, the castling rights + if (_castle.whiteKingSide) { hash ^= hashTable[776]; } + if (_castle.whiteQueenSide) { hash ^= hashTable[776 + 1]; } + if (_castle.blackKingSide) { hash ^= hashTable[776 + 2]; } + if (_castle.blackQueenSide) { hash ^= hashTable[776 + 3]; } + + // Finally, side to move (for white to move) + if (isWhiteTurn()) { + hash ^= hashTable[780]; + } + + return hash; +} + +void Board::init(const std::string& fen, bool& parseError) { + // first check length (conservative boarders, TODO: tighten) + if ((fen.length() < 10) || (200 < fen.length())) { + parseError = true; return; + } + + // split FEN into its parts + std::vector parts = split(fen); + // check if there are exactly 6 parts + if (parts.size() != 6) { + parseError = true; return; + } + + // (re)set all bit boards to zero + for (Index i = 0; i < 8; i++) { + _bitBoard[i] = static_cast(0); + } + + // Part 1 - Piece Placement + Index rankIndex = 0, fileIndex = 0; + Index whiteKing = 0, blackKing = 0; + for (char c : parts[0]) { + if (c == '/') { + if (fileIndex != 8) { parseError = true; return; } + rankIndex++; + fileIndex = 0; + continue; + } else if ('0' < c && c < '9') { + fileIndex += static_cast(c - '0'); + continue; + } + + enum piece piece_color = isUpper(c) ? white : black; + enum piece piece_type; + switch (toUpper(c)) { + case 'P': piece_type = pawn; break; + case 'R': piece_type = rook; break; + case 'B': piece_type = bishop; break; + case 'N': piece_type = knight; break; + case 'K': piece_type = king; break; + case 'Q': piece_type = queen; break; + default: parseError = true; return; + } + + if (piece_type == king) { + if (piece_color == white) whiteKing++; + if (piece_color == black) blackKing++; + } + + _bitBoard[piece_color] |= bitMask(fileIndex, rankIndex); + _bitBoard[piece_type] |= bitMask(fileIndex, rankIndex); + + fileIndex++; + } + if ((rankIndex != 7) || (whiteKing != 1) || (blackKing != 1)) { + parseError = true; return; + } + + // Part 2 - Side to Move (ply being odd/even) + if (parts[1] == "w") { _plyCount = 1; } + else if (parts[1] == "b") { _plyCount = 2; } + else { + parseError = true; return; + } + + // Part 3 - Castling ability + _castle.whiteKingSide = false; + _castle.whiteQueenSide = false; + _castle.blackKingSide = false; + _castle.blackQueenSide = false; + if (parts[2] != "-") { + for (char c : parts[2]) { + switch (c) { + case 'K': _castle.whiteKingSide = true; break; + case 'Q': _castle.whiteQueenSide = true; break; + case 'k': _castle.blackKingSide = true; break; + case 'q': _castle.blackQueenSide = true; break; + default: parseError = true; return; + } + } + } + + // Part 4 - En passant target square + if (parts[3] == "-") { + _enPassant = static_cast(64); + } else { + // _enPassant = UCI::parseSquare(parts[3], parseError); + if (parts[3].length() != 2) { + parseError = true; + return; + } + if (parts[3][0] < 'a' || 'h' < parts[3][0] + || parts[3][1] < '1' || '8' < parts[3][1]) { + parseError = true; + return; + } + + Index fileIndex = static_cast(parts[3][0] - 'a'); + Index rankIndex = static_cast('8' - parts[3][1]); + + _enPassant = 8 * rankIndex + fileIndex; + } + + // Part 5 - Half Move Clock + _halfMoveClock = parseUnsigned(parts[4], parseError); + if (parseError) { return; } + + // Part 6 - Full Move counter + unsigned moveCounter = parseUnsigned(parts[5], parseError); + if (parseError || (moveCounter < 1)) { parseError = true; return; } + _plyCount += 2U * (moveCounter - 1U); + + // Set new hash + _hash = rehash(); +} + +Move Board::isLegal(const Move move) const { + // First check the move itself (null move check) + if (move.from() == move.to()) { return Move(0); } + + // Now, simple check if the move is in the set of legal moves + MoveList allLegalMoves; + moves(allLegalMoves); + for (Move legalMove : allLegalMoves) { + if ((move.from() == legalMove.from()) + && (move.to() == legalMove.to()) + && (move.promote() == legalMove.promote())) + { + return legalMove; + } + } + + // move not found in the legal moves -> NOT legal + return Move(0); +} + + +// NOTE: Assumes a legal move, otherwise undefined behavior! +void Board::make(const Move move) { + + assert(this->piece(move.from()) == move.piece()); + assert(( + (move.piece() == pawn) + && (move.to() == _enPassant) + && (move.victim() == pawn) + ) || ( + this->piece(move.to()) == move.victim() + )); + + // move piece (and perform capture) + const u64 from = bitMask(move.from()); + const u64 to = bitMask(move.to()); + const enum piece piece = move.piece(); + const enum piece color = isWhiteTurn() ? white : black; + const enum piece enemy = (color == white) ? black : white; + bool isCapture; + + if (isWhiteTurn()) { + // check if is a capture (for half move clock, not considering e.p. + // capture since irrelevant, half move clock is reset on pawn move) + isCapture = static_cast(_bitBoard[black] & to); + // move pieces + _bitBoard[white] = (_bitBoard[white] & ~from) | to; + _bitBoard[black] &= ~to; + // finish castling (king already moved -> move rook) + if (piece == king) { + if ((from >> 1) & (to << 1)) { // queen side castling + _bitBoard[rook] ^= (bitMask(a1) | bitMask(d1)); + _bitBoard[white] ^= (bitMask(a1) | bitMask(d1)); + _hash ^= hashTable[hashIndex(rook, white, a1)]; + _hash ^= hashTable[hashIndex(rook, white, d1)]; + } + if ((from << 1) & (to >> 1)) { // king side castling + _bitBoard[rook] ^= (bitMask(f1) | bitMask(h1)); + _bitBoard[white] ^= (bitMask(f1) | bitMask(h1)); + _hash ^= hashTable[hashIndex(rook, white, f1)]; + _hash ^= hashTable[hashIndex(rook, white, h1)]; + } + } + // finish en passant capture (remove captured pawn) + if ((piece == pawn) && (move.to() == _enPassant)) { + _bitBoard[pawn] ^= bitMask(_enPassant) << 8; + _bitBoard[black] ^= bitMask(_enPassant) << 8; + _hash ^= hashTable[hashIndex(pawn, black, _enPassant + 8)]; + } + } else { // black's turn + // check if is a capture (for half move clock, not considering e.p. + // capture since irrelevant, half move clock is reset on pawn move) + isCapture = static_cast(_bitBoard[white] & to); + // move pieces + _bitBoard[black] = (_bitBoard[black] & ~from) | to; + _bitBoard[white] &= ~to; + // finish castling (king already moved -> move rook) + if (piece == king) { + if ((from >> 1) & (to << 1)) { // queen side castling + _bitBoard[rook] ^= bitMask(a8) | bitMask(d8); + _bitBoard[black] ^= bitMask(a8) | bitMask(d8); + _hash ^= hashTable[hashIndex(rook, black, a8)]; + _hash ^= hashTable[hashIndex(rook, black, d8)]; + } + if ((from << 1) & (to >> 1)) { // king side castling + _bitBoard[rook] ^= bitMask(f8) | bitMask(h8); + _bitBoard[black] ^= bitMask(f8) | bitMask(h8); + _hash ^= hashTable[hashIndex(rook, black, f8)]; + _hash ^= hashTable[hashIndex(rook, black, h8)]; + } + } + // finish en passant capture (remove captured pawn) + if ((piece == pawn) && (move.to() == _enPassant)) { + _bitBoard[pawn] ^= bitMask(_enPassant) >> 8; + _bitBoard[white] ^= bitMask(_enPassant) >> 8; + _hash ^= hashTable[hashIndex(pawn, white, _enPassant - 8)]; + } + } + // update hash + _hash ^= hashTable[hashIndex(piece, color, move.from())]; + _hash ^= hashTable[hashIndex(piece, color, move.to())]; + if (isCapture) { + _hash ^= hashTable[hashIndex(move.victim(), enemy, move.to())]; + } + // and move (including capture) the piece + _bitBoard[rook] &= ~(from | to); + _bitBoard[knight] &= ~(from | to); + _bitBoard[bishop] &= ~(from | to); + _bitBoard[queen] &= ~(from | to); + _bitBoard[king] &= ~(from | to); + _bitBoard[pawn] &= ~(from | to); + _bitBoard[piece] |= to; + + // increment half move clock (+1 iff no pawn advance or capture) + _halfMoveClock = (_halfMoveClock + 1) * (piece != pawn) * !isCapture; + + // write en passant target square (and remove previous from hash) + if (_enPassant < static_cast(64)) { + _hash ^= hashTable[hashIndex(_enPassant)]; + } + if (piece == pawn) { + u64 epMask = ((from >> 8) & (to << 8)) | ((from << 8) & (to >> 8)); + _enPassant = static_cast(epMask ? bitScanLS(epMask) : 64); + if (_enPassant < static_cast(64)) { + _hash ^= hashTable[hashIndex(_enPassant)]; + } + } else { + _enPassant = static_cast(64); + } + + // handle promotions + if (move.promote()) { + _bitBoard[pawn] &= ~to; + _bitBoard[move.promote()] |= to; + _hash ^= hashTable[hashIndex(pawn, color, move.to())]; + _hash ^= hashTable[hashIndex(move.promote(), color, move.to())]; + } + + // revoke castling rights (rook captures revoke castling rights as well) + if (_castle.whiteKingSide + && ((from | to) & (bitMask(e1) | bitMask(h1)))) { + _castle.whiteKingSide = false; + _hash ^= hashTable[776]; + } + if (_castle.whiteQueenSide + && ((from | to) & (bitMask(a1) | bitMask(e1)))) { + _castle.whiteQueenSide = false; + _hash ^= hashTable[776 + 1]; + } + if (_castle.blackKingSide + && ((from | to) & (bitMask(e8) | bitMask(h8)))) { + _castle.blackKingSide = false; + _hash ^= hashTable[776 + 2]; + } + if (_castle.blackQueenSide + && ((from | to) & (bitMask(a8) | bitMask(e8)))) { + _castle.blackQueenSide = false; + _hash ^= hashTable[776 + 3]; + } + + // (last operation, isWhiteTurn() depends on this) + _plyCount++; + // and toggle side to move bit string + _hash ^= hashTable[780]; +} + +// BitMask of all squares the king is NOT allowed to be +// +// @param enemy opponents color +// @param xRayEmpty empty bitboard with king (of moving color) removed +u64 Board::attacks(const enum piece enemy, const u64 empty) const { + u64 attackeMask = 0; + // begin with pawn attacks + u64 attacker = _bitBoard[enemy] & _bitBoard[pawn]; + if (enemy == white) { + attackeMask |= shift(attacker); + attackeMask |= shift (attacker); + } else { + attackeMask |= shift(attacker); + attackeMask |= shift(attacker); + } + // add knight attacks + attacker = _bitBoard[enemy] & _bitBoard[knight]; + for (; attacker; attacker &= attacker - 1) { + attackeMask |= knightMoveLookup[bitScanLS(attacker)]; + } + // Add left-right up-down sliding piece attacks (rooks, queens) + attacker = _bitBoard[enemy] & (_bitBoard[rook] | _bitBoard[queen]); + attackeMask |= fill7dump (attacker, empty) + | fill7dump (attacker, empty) + | fill7dump (attacker, empty) + | fill7dump(attacker, empty); + // and the diagonal sliding attacks + attacker = _bitBoard[enemy] & (_bitBoard[bishop] | _bitBoard[queen]); + attackeMask |= fill7dump (attacker, empty) + | fill7dump (attacker, empty) + | fill7dump (attacker, empty) + | fill7dump(attacker, empty); + // finally, the enemy king + attackeMask |= kingMoveLookup[bitScanLS(_bitBoard[enemy] & _bitBoard[king])]; + + return attackeMask; +} + +u64 Board::pinned(const enum piece enemy, const u64 cKing, const u64 empty) const { + const enum piece color = (enemy == white) ? black : white; + + u64 pinMask = 0; + u64 ray, attacker; + // rook & queen sliding attacks + attacker = _bitBoard[enemy] & (_bitBoard[rook] | _bitBoard[queen]); + ray = fill7dump(cKing, empty); + pinMask |= ray & _bitBoard[color] & fill7dump(attacker, empty); + ray = fill7dump(cKing, empty); + pinMask |= ray & _bitBoard[color] & fill7dump(attacker, empty); + ray = fill7dump(cKing, empty); + pinMask |= ray & _bitBoard[color] & fill7dump(attacker, empty); + ray = fill7dump(cKing, empty); + pinMask |= ray & _bitBoard[color] & fill7dump(attacker, empty); + // bishop & queen sliding attacks + attacker = _bitBoard[enemy] & (_bitBoard[bishop] | _bitBoard[queen]); + ray = fill7dump(cKing, empty); + pinMask |= ray & _bitBoard[color] & fill7dump(attacker, empty); + ray = fill7dump(cKing, empty); + pinMask |= ray & _bitBoard[color] & fill7dump(attacker, empty); + ray = fill7dump(cKing, empty); + pinMask |= ray & _bitBoard[color] & fill7dump(attacker, empty); + ray = fill7dump(cKing, empty); + pinMask |= ray & _bitBoard[color] & fill7dump(attacker, empty); + + return pinMask; +} + +u64 Board::checkers(const enum piece enemy, const u64 cKing, const u64 empty) const { + // Initialize checkers with knights giving check + u64 checkers = knightMoveLookup[bitScanLS(cKing)] & _bitBoard[enemy] & _bitBoard[knight]; + + u64 ray, attacker; + // rook & queen sliding attacks + attacker = _bitBoard[enemy] & (_bitBoard[rook] | _bitBoard[queen]); + ray = fill7dump(cKing, empty); + checkers |= ray & attacker; + ray = fill7dump(cKing, empty); + checkers |= ray & attacker; + ray = fill7dump(cKing, empty); + checkers |= ray & attacker; + ray = fill7dump(cKing, empty); + checkers |= ray & attacker; + // bishop & queen sliding attacks + attacker = _bitBoard[enemy] & (_bitBoard[bishop] | _bitBoard[queen]); + ray = fill7dump(cKing, empty); + checkers |= ray & attacker; + ray = fill7dump(cKing, empty); + checkers |= ray & attacker; + ray = fill7dump(cKing, empty); + checkers |= ray & attacker; + ray = fill7dump(cKing, empty); + checkers |= ray & attacker; + + // Finish checkers by pawns (if the king would be a pawn, he attacks + // check giving enemy pawns) + attacker = _bitBoard[enemy] & _bitBoard[pawn]; + if (enemy == black) { + checkers |= shift(cKing) & attacker; + checkers |= shift(cKing) & attacker; + } else { + checkers |= shift(cKing) & attacker; + checkers |= shift(cKing) & attacker; + } + + return checkers; +} + +void Board::moves(MoveList& moveList, bool capturesOnly) const { + moves(moveList, isWhiteTurn() ? white : black, capturesOnly); +} + +void Board::moves(MoveList& moveList, const enum piece color, + bool capturesOnly) const +{ + assert(!(_bitBoard[white] & _bitBoard[black])); + assert(bitCount(_bitBoard[black] & _bitBoard[king]) == 1); + assert(bitCount(_bitBoard[white] & _bitBoard[king]) == 1); + assert(( + _bitBoard[white] | _bitBoard[black] + ) == ( + _bitBoard[pawn] + ^ _bitBoard[rook] + ^ _bitBoard[bishop] + ^ _bitBoard[knight] + ^ _bitBoard[queen] + ^ _bitBoard[king] + )); + + // Get the opponents color + const enum piece enemy = (color == white) ? black : white; + + const u64 cKing = _bitBoard[color] & _bitBoard[king]; + const Index cKingSq = bitScanLS(cKing); + const u64 occupied = _bitBoard[white] | _bitBoard[black]; + const u64 empty = ~occupied; + + /****** Compute danger squares for the king (when moving to in check) ******/ + // All fields under attack of an enemy piece (or pawn) with a see-through king + u64 dangerMask = attacks(enemy, ~(occupied ^ cKing)); + // Restricts possible move target squares (except king movement). Also used + // in check to restrict moves to check evading moves for non-king pieces. + u64 toMask = capturesOnly ? _bitBoard[enemy] : -1; + // Pinned pieces (all pieces from the kings perspective which by see through + // discover enemy sliding pieces in that direction) + u64 pinMask = pinned(enemy, cKing, empty); + // Initialize checkers with none, iff in check compute checkers + u64 checkers = 0; + + /***** Determine (Double) Check and check giving pieces (aka checkers) *****/ + // If the king is on a danger square he is in check + bool isCheck = static_cast(cKing & dangerMask); + bool isDoubleCheck = false; + + if (isCheck) { + // Compute checkers (bitmask of check giving pieces) + checkers = this->checkers(enemy, cKing, empty); + // Check for double check (by testing for at least two bits in checkers) + isDoubleCheck = static_cast(checkers & (checkers - 1)); + // compute move target square mask + if (!isDoubleCheck) { + toMask &= checkers; // one square + + // if check giving piece is a sliding piece, it can be blocked + if (checkers & (_bitBoard[rook] | _bitBoard[bishop] | _bitBoard[queen])) { + u64 attacker = fill7dump (cKing, empty); + toMask |= attacker * static_cast(attacker & checkers); + attacker = fill7dump (cKing, empty); + toMask |= attacker * static_cast(attacker & checkers); + attacker = fill7dump (cKing, empty); + toMask |= attacker * static_cast(attacker & checkers); + attacker = fill7dump (cKing, empty); + toMask |= attacker * static_cast(attacker & checkers); + attacker = fill7dump (cKing, empty); + toMask |= attacker * static_cast(attacker & checkers); + attacker = fill7dump (cKing, empty); + toMask |= attacker * static_cast(attacker & checkers); + attacker = fill7dump (cKing, empty); + toMask |= attacker * static_cast(attacker & checkers); + attacker = fill7dump(cKing, empty); + toMask |= attacker * static_cast(attacker & checkers); + } + } + } + + /***************************** Generate moves *****************************/ + { // King Moves + u64 to = kingMoveLookup[cKingSq] &~ (dangerMask | _bitBoard[color]); + // King moves can not be handled with the toMask trick + if (capturesOnly) { + to &= _bitBoard[enemy]; + } + for (; to; to &= to - 1) { + moveList.emplace_back(color, king, cKingSq, bitScanLS(to), + sqPiece(to & -to)); + } + } + // if in double check, the king must move (-> stop move generation) + if (isDoubleCheck) { return; } + // and castling + if (!isCheck && !capturesOnly) { + // king is not allowed to move through or to an attacked square, + // but the rook is allowed to and might also land on an attacked square + // (at the b file when queen side while castling) + u64 castlingDanger = (dangerMask &~ bitMask(b1)) | occupied; + if (color == white) { + constexpr u64 kingSide = bitMask(f1) | bitMask(g1); + constexpr u64 queenSide = bitMask(b1) | bitMask(c1) + | bitMask(d1); + if (_castle.whiteKingSide && !(castlingDanger & kingSide)) { + moveList.emplace_back(white, king, e1, g1, none); + } + if (_castle.whiteQueenSide && !(castlingDanger & queenSide)) { + moveList.emplace_back(white, king, e1, c1, none); + } + } else { + constexpr u64 kingSide = bitMask(f8) | bitMask(g8); + constexpr u64 queenSide = bitMask(b8) | bitMask(c8) + | bitMask(d8); + if (_castle.blackKingSide && !(castlingDanger & kingSide)) { + moveList.emplace_back(black, king, e8, g8, none); + } + if (_castle.blackQueenSide && !(castlingDanger & queenSide)) { + moveList.emplace_back(black, king, e8, c8, none); + } + } + } + + { // Rook Moves + u64 rooks = _bitBoard[color] & _bitBoard[rook]; + for (; rooks; rooks &= rooks - 1) { + Index fromSq = bitScanLS(rooks); + u64 from = rooks & -rooks; + u64 to = fill7dump (from, empty) + | fill7dump(from, empty) + | fill7dump (from, empty) + | fill7dump (from, empty); + // check if pinned and restrict `to` accordingly + if (from & pinMask) { + to &= lineMask(cKingSq, fromSq); + } + to &= toMask & ~_bitBoard[color]; + for (; to; to &= to - 1) { + moveList.emplace_back(color, rook, fromSq, bitScanLS(to), + sqPiece(to & -to)); + } + } + } + + { // Bishop Moves + u64 bishops = _bitBoard[color] & _bitBoard[bishop]; + for (; bishops; bishops &= bishops - 1) { + Index fromSq = bitScanLS(bishops); + u64 from = bishops & -bishops; + u64 to = fill7dump (from, empty) + | fill7dump (from, empty) + | fill7dump (from, empty) + | fill7dump(from, empty); + // check if pinned and restrict `to` accordingly + if (from & pinMask) { + to &= lineMask(cKingSq, fromSq); + } + to &= toMask & ~_bitBoard[color]; + for (; to; to &= to - 1) { + moveList.emplace_back(color, bishop, fromSq, bitScanLS(to), + sqPiece(to & -to)); + } + } + } + + { // Queen Moves + u64 queens = _bitBoard[color] & _bitBoard[queen]; + for (; queens; queens &= queens - 1) { + Index fromSq = bitScanLS(queens); + u64 from = queens & -queens; + u64 to = fill7dump (from, empty) + | fill7dump (from, empty) + | fill7dump (from, empty) + | fill7dump (from, empty) + | fill7dump (from, empty) + | fill7dump (from, empty) + | fill7dump (from, empty) + | fill7dump(from, empty); + // check if pinned and restrict `to` accordingly + if (from & pinMask) { + to &= lineMask(cKingSq, fromSq); + } + to &= toMask & ~_bitBoard[color]; + for (; to; to &= to - 1) { + moveList.emplace_back(color, queen, fromSq, bitScanLS(to), + sqPiece(to & -to)); + } + } + } + + { // Knight Moves + u64 knights = _bitBoard[color] & _bitBoard[knight]; + for (; knights; knights &= knights - 1) { + u64 from = knights & -knights; + // check if pinned (pinned knights can't move) + if (from & pinMask) { continue; } + Index fromSq = bitScanLS(knights); + u64 to = toMask & knightMoveLookup[fromSq] & ~_bitBoard[color]; + for (; to; to &= to - 1) { + moveList.emplace_back(color, knight, fromSq, bitScanLS(to), + sqPiece(to & -to)); + } + } + } + + { // Pawn moves + u64 pawns = _bitBoard[color] & _bitBoard[pawn]; // for all pawns + u64 targets = _bitBoard[enemy]; // capture targets + for (; pawns; pawns &= pawns - 1) { + Index fromSq = bitScanLS(pawns); + u64 from = pawns & -pawns; + // Compute pawn push, double pawn push as well as left and right + // capture target squares. Also set a promotion flag if reaching + // the final rank (rank 8 for white and rank 1 for black) + u64 to = 0; + bool isPromotion = false; + if (color == white) { + to = (from >> 8) & empty; + to |= ( (to & bitMask(a3)) >> 8) & empty; + to |= ((from &~ bitMask(a1)) >> 9) & targets; + to |= ((from &~ bitMask(h1)) >> 7) & targets; + isPromotion = static_cast(from & bitMask(a7)); + } else { + to = (from << 8) & empty; + to |= ( (to & bitMask(a6)) << 8) & empty; + to |= ((from &~ bitMask(a1)) << 7) & targets; + to |= ((from &~ bitMask(h1)) << 9) & targets; + isPromotion = static_cast(from & bitMask(a2)); + } + to &= toMask; + if (from & pinMask) { + to &= lineMask(cKingSq, fromSq); + } + if (isPromotion) { + for (; to; to &= to - 1) { + Index toSq = bitScanLS(to); + enum piece victim = sqPiece(to & -to); + moveList.emplace_back(color, pawn, fromSq, toSq, victim, rook); + moveList.emplace_back(color, pawn, fromSq, toSq, victim, knight); + moveList.emplace_back(color, pawn, fromSq, toSq, victim, bishop); + moveList.emplace_back(color, pawn, fromSq, toSq, victim, queen); + } + } else { + for (; to; to &= to - 1) { + moveList.emplace_back(color, pawn, fromSq, bitScanLS(to), + sqPiece(to & -to)); + } + } + } + // and en passant captures (handled separately, cause of special cases. + // Most of them are simply handled by adding the e.p. target square to + // targets, but it misinterprets some situations) + if (_enPassant < 64) { + u64 to = bitMask(_enPassant); + u64 target; + pawns = 0; + if (color == white) { + pawns |= shift (to); + pawns |= shift(to); + target = shift(to); + } else { + pawns |= shift(to); + pawns |= shift(to); + target = shift(to); + } + pawns &= _bitBoard[color] & _bitBoard[pawn]; + + // now a few situations can occur + // 1. No check + // a. legal e.p. capture + // b. illegal due to discovered check + // 2. In check (case of double check already handled) + // a. e.p. capture resolved check (e.p. target pawn gives check) + // but can still lead to a discovered check + // b. illegal, e.p. does not resolve check + // In ether case, we perform an check test if the move is legal + // which should be OK with the hope that e.p. captures are rare + // enough (does not slows down computation too much^^) + if (!isCheck || (isCheck && (target & checkers))) { + for (; pawns; pawns &= pawns - 1) { // max twice + Index fromSq = bitScanLS(pawns); + u64 from = pawns & -pawns; + // perform reduced check test, through an e.p. capture, only + // sliding pieces could are involved in discovered check (and + // double check is was handled already) + u64 futureOccupied = occupied ^ (from | target | to); + bool discoveredCheck = false; + + u64 attacker = _bitBoard[enemy] & (_bitBoard[rook] | _bitBoard[queen]); + discoveredCheck |= static_cast(attacker & fill7dump (cKing, ~futureOccupied)); + discoveredCheck |= static_cast(attacker & fill7dump (cKing, ~futureOccupied)); + discoveredCheck |= static_cast(attacker & fill7dump (cKing, ~futureOccupied)); + discoveredCheck |= static_cast(attacker & fill7dump(cKing, ~futureOccupied)); + + attacker = _bitBoard[enemy] & (_bitBoard[bishop] | _bitBoard[queen]); + discoveredCheck |= static_cast(attacker & fill7dump (cKing, ~futureOccupied)); + discoveredCheck |= static_cast(attacker & fill7dump (cKing, ~futureOccupied)); + discoveredCheck |= static_cast(attacker & fill7dump (cKing, ~futureOccupied)); + discoveredCheck |= static_cast(attacker & fill7dump(cKing, ~futureOccupied)); + + if (!discoveredCheck) { + moveList.emplace_back(color, pawn, fromSq, _enPassant, pawn); + } + } + } + } + } +} + +bool Board::isCheck() const { + const enum piece color = isWhiteTurn() ? white : black; + const enum piece enemy = (color == white) ? black : white; + const u64 cKing = _bitBoard[color] & _bitBoard[king]; + + u64 empty = ~(_bitBoard[white] | _bitBoard[black]); + return static_cast(attacks(enemy, empty) & cKing); +} + +bool Board::isQuiet() const { + const enum piece color = sideToMove(); + const enum piece enemy = static_cast(!color); + const u64 empty = ~(_bitBoard[white] | _bitBoard[black]); + + return !static_cast(attacks(color, empty) & _bitBoard[enemy]); +} + +bool Board::isTerminal() const { + // check 50 move rule + if (_halfMoveClock >= 100) { + return true; + } + // check if there are any moves, then the game is NOT over + MoveList moveList; + moves(moveList); + return moveList.empty(); +} + +bool Board::isInsufficient() const { + switch (bitCount(_bitBoard[white] | _bitBoard[black])) { + case 2: return true; + case 3: return _bitBoard[knight] | _bitBoard[bishop]; + case 4: + // Two opposite bishops of the same color (ignoring promoted bishops) + return ( + (_bitBoard[bishop] == (_bitBoard[bishop] & bitMask())) + || + (_bitBoard[bishop] == (_bitBoard[bishop] & bitMask())) + ); + default: break; + }; + return false; +} + +Score Board::evalMaterial(enum piece color) const { + Score material = 0; + for (enum piece piece : { pawn, rook, knight, bishop, queen }) { + material += bitCount(_bitBoard[color] & _bitBoard[piece]) * pieceValues[piece]; + } + return material; +} +Score Board::evalPawns(enum piece color) const { + // Penalties/Bonuses + constexpr Score doubledPenalty = 10; + constexpr Score isolatedPenalty = 20; + constexpr Score backwardsPenalty = 8; + constexpr Score passedBonus = 20; + + // Pawn position and structure score + Score score = 0; + + // Pawn of color BitBoard + u64 pawns = _bitBoard[color] & _bitBoard[pawn]; + + // Compute doubled pawns (all pawns behind another pawn) + // For white count less advanced and for black more advanced pawns -> equiv + u64 doubled = pawns & shift(fill(pawns)); + score -= bitCount(doubled) * doubledPenalty; + // Isolated pawns (pawns without any friendly pawns on adjacent files) + u64 isolated = pawns; + isolated &= shift( ~fill(pawns)) | bitMask(h1); + isolated &= shift(~fill(pawns)) | bitMask(a1); + score -= bitCount(isolated) * isolatedPenalty; + + // Sum piece square table and evaluate pawn structure + if (color == white) { + for (u64 sq = pawns; sq; sq &= sq - 1) { + Index i = bitScanLS(sq); + score += pieceSquareTables[pawn][i]; + } + // Backwards pawns (not isolated but behind all adjacent friendly pawns) + u64 backwards = pawns & ~isolated; + backwards &= ~fill(shift(pawns) | shift(pawns)); + score -= bitCount(backwards) * backwardsPenalty; + // Passed Pawns (Pawns without enemy pawns to stop it from advancing) + u64 enemy = _bitBoard[black] & _bitBoard[pawn]; + u64 passed = pawns & ~fill(shift(enemy) + | enemy + | shift(enemy)); + for (u64 sq = passed; sq; sq &= sq - 1) { + score += (7 - rankIndex(bitScanLS(sq))) * passedBonus; + } + } else { // color == black + for (u64 sq = pawns; sq; sq &= sq - 1) { + Index i = bitScanLS(sq); + score += pieceSquareTables[pawn][63 - i]; + } + // Backwards pawns (not isolated but behind all adjacent friendly pawns) + u64 backwards = pawns & ~isolated; + backwards &= ~fill(shift(pawns) | shift(pawns)); + score -= bitCount(backwards) * backwardsPenalty; + // Passed Pawns (Pawns without enemy pawns to stop it from advancing) + u64 enemy = _bitBoard[white] & _bitBoard[pawn]; + u64 passed = pawns & ~fill(shift(enemy) + | enemy + | shift(enemy)); + for (u64 sq = passed; sq; sq &= sq - 1) { + score += rankIndex(bitScanLS(sq)) * passedBonus; + } + } + + return score; +} + +Score Board::evalKingSafety(enum piece color) const { + // Kings position + Index kingSq = (color == white) + ? bitScanLS( _bitBoard[white] & _bitBoard[king]) + : bitScanLS(bitFlip(_bitBoard[black] & _bitBoard[king])); + + Score score = pieceSquareTables[king][kingSq]; + + if ((fileIndex(kingSq) < 3) || (4 < fileIndex(kingSq))) { // King is castled + // Pawn shields are the least advanced pawns per file + u64 pawnShield = (color == white) + ? _bitBoard[white] & _bitBoard[pawn] + : bitFlip(_bitBoard[black] & _bitBoard[pawn]); + pawnShield &= fill7dump(bitMask(a1), ~pawnShield); + // hack to sub-select the three king/queen side border files + Index kingSide = (kingSq < 3) ? 1 : 6; // ether b1 or g1 + pawnShield &= fill(kingMoveLookup[kingSide]); + + for (u64 mask : { bitMask(a1) | bitMask(h1), + bitMask(b1) | bitMask(g1), + bitMask(c1) | bitMask(f1) }) { + u64 pawn = pawnShield & mask; + if (pawn) { + switch (rankIndex(bitScanLS(pawn))) { + case 6: break; // intact shield + case 5: score -= 10; break; // pawn moved once + default: score -= 20; // moved more than once + } + } else { + score -= 25; // no shield (no pawn) + } + } + } else { // not castled, penalize open files + // Select squares of open files + u64 openFiles = ~fill(_bitBoard[pawn]); + // hack to sub select the kings and its adjacent files + openFiles &= fill(kingMoveLookup[kingSq]); + // Penalty for number of open files + score -= 10 * bitCount(openFiles & bitMask(a1)); + } + + return score; +} + +Score Board::evalRooks(enum piece color) const { + constexpr Score rookOpenFileBonus = 15; + constexpr Score rookSemiOpenFileBonus = 10; + + u64 rooks = (color == white) + ? (_bitBoard[white] & _bitBoard[rook]) + : bitFlip(_bitBoard[black] & _bitBoard[rook]); + + // Open and semi-open files + u64 openFiles = ~fill(_bitBoard[pawn]); + u64 semiOpenFiles = ~fill(_bitBoard[color] & _bitBoard[pawn]); + + Score score = 0; + + for (u64 sq = rooks; sq; sq &= sq - 1) { + Index sqIndex = bitScanLS(sq); + // Piece square table (accounts for rook on seventh bonus) + score += pieceSquareTables[rook][sqIndex]; + + // Add bonuses for semi-open and open files + if (bitMask(sqIndex) & openFiles) { + score += rookOpenFileBonus; + } else if (bitMask(sqIndex) & semiOpenFiles) { + score += rookSemiOpenFileBonus; + } + } + + return score; +} + +// position fen 3rr1k1/pp3pp1/1qn2np1/8/3p4/PP1R1P2/2P1NQPP/R1B3K1 b - - 0 1 +// position fen 1k3b1r/ppqn1p2/2p1r1pp/4P3/8/1PN2NQ1/1PP3PP/1K1RR3 w - - 0 1 + +// position fen r1bq1rk1/pp2p1bp/2np4/5p2/nP3P2/N1P2N2/2B3PP/R1B1QRK1 w - - 0 4 +// position fen r1bq1rk1/pp2p1bp/2np4/5B2/nP3P2/N1P2N2/6PP/R1B1QRK1 b - - 0 4 +Score Board::evaluate() const { + + constexpr Score pstKingEndgame[64] = { // TODO: Proper parameters file, ... + 0, 10, 20, 30, 30, 20, 10, 0, + 10, 20, 30, 40, 40, 30, 20, 10, + 20, 30, 40, 50, 50, 40, 30, 20, + 30, 40, 50, 60, 60, 50, 40, 30, + 30, 40, 50, 60, 60, 50, 40, 30, + 20, 30, 40, 50, 50, 40, 30, 20, + 10, 20, 30, 40, 40, 30, 20, 10, + 0, 10, 20, 30, 30, 20, 10, 0 + }; + + constexpr Score maxMaterial = 8 * pieceValues[pawn] + + 2 * (pieceValues[rook] + pieceValues[knight] + pieceValues[bishop]) + + pieceValues[queen]; + + + // Start score with material values + Score whiteMaterial = evalMaterial(white); + Score blackMaterial = evalMaterial(black); + Score score = whiteMaterial - blackMaterial; + + // Add piece square table values of pieces without specialized eval function + // Note: assumes left/right symmetry of the piece square tables + for (enum piece type : { queen, bishop, knight }) { + // White pieces + for (u64 sq = _bitBoard[white] & _bitBoard[type]; sq; sq &= sq - 1) { + score += pieceSquareTables[type][bitScanLS(sq)]; + } + // and black pieces + for (u64 sq = _bitBoard[black] & _bitBoard[type]; sq; sq &= sq - 1) { + score -= pieceSquareTables[type][63 - bitScanLS(sq)]; + } + } + + // Pawn structure + score += evalPawns(white) - evalPawns(black); + + // Rooks + score += evalRooks(white) - evalRooks(black); + + // First the white king + if (blackMaterial <= 1200) { + // Endgame: + // No king safety, but more king mobility in the center + score += pstKingEndgame[bitScanLS(_bitBoard[white] & _bitBoard[king])]; + } else { + // Middle Game: + // King safety weighted by opponents material (The less pieces the enemy + // possesses, the less important the king safety. In other words, with + // fewer pieces, its harder to attack.) + score += (5 * blackMaterial * evalKingSafety(white)) / (4 * maxMaterial); + } + // and the same for the black king with opposite sign + if (whiteMaterial <= 1200) { + score -= pstKingEndgame[bitScanLS(_bitBoard[black] & _bitBoard[king])]; + } else { + score -= (5 * whiteMaterial * evalKingSafety(black)) / (4 * maxMaterial); + } + + return score; +} + +// Static Exchange Evaluation +Score Board::see(const Move move) const { + // Only apply to captures + assert(!!move && move.victim()); + + // Target square + Index sq = move.to(); + u64 to = bitMask(sq); + u64 from = bitMask(move.from()); + // Set of all empty squares (without initial attack) + u64 empty = from | ~(_bitBoard[white] | _bitBoard[black]); + + // Compute all attackers (of the target square) + u64 attacker = (knightMoveLookup[sq] & _bitBoard[knight]) + | ( kingMoveLookup[sq] & _bitBoard[king]); + // add sliding pieces + attacker |= fill7dump (to, empty) & (_bitBoard[queen] | _bitBoard[rook]); + attacker |= fill7dump(to, empty) & (_bitBoard[queen] | _bitBoard[bishop]); + // and pawns + attacker |= _bitBoard[pawn] & + ( ((shift (to) | shift (to)) & _bitBoard[black]) + | ((shift(to) | shift(to)) & _bitBoard[white])); + // Remove initial attacker (perform given move) + attacker &= ~empty; + + // Setup swap list (there are max 32 pieces) + Score swapList[32]; + // move count in the capture sequence + int ply = 1; + + // Setup initial capture (aka move to evaluate) + swapList[0] = pieceValues[move.victim()]; + swapList[1] = pieceValues[move.piece()] - swapList[0]; + + // Side to move piece set + u64 side = _bitBoard[move.color()]; + // Perform alternating sequence of captures untill there are no attackers + // of the current side to move left + while (u64 candidate = (attacker & (side = ~side))) { + // Get least valuable attacker piece from candidates + enum piece piece = none; + for (enum piece pieceType : {pawn, knight, bishop, rook, queen, king}) { + // Skip untill the first candidate (garantied to exist) + if ((from = (candidate & _bitBoard[pieceType]))) { + piece = pieceType; + from = from & -from; // pick arbitrary first (TODO: find a better + break; // solution! For now, quick and durty) + } + } + + // If a king attempts a capture, check for check + if ((piece == king) && (attacker &~ side)) { + break; // King would move into check -> no pieces left + } + + // Append speculative score under the assumption beeing defended + ply++; + swapList[ply] = pieceValues[piece] - swapList[ply - 1]; + + // perform piece movement + attacker ^= from; + empty ^= from; + + // Update attackers with discovered sliding pieces + attacker |= fill7dump (to, empty) & (_bitBoard[queen] | _bitBoard[rook]); + attacker |= fill7dump(to, empty) & (_bitBoard[queen] | _bitBoard[bishop]); + attacker &= ~empty; + } + + // Final SEE evaluation by dropping loosing sub-sequences (allow stand pat) + // Note: Ignore last entry in swapList since the last entry corresponds to + // the piece at the end of the capture sequence (not captures) + while (--ply) { + swapList[ply - 1] = -std::max(-swapList[ply - 1], swapList[ply]); + } + + return swapList[0]; +} diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.h new file mode 100644 index 0000000..eaa69c5 --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.h @@ -0,0 +1,224 @@ +#ifndef INCLUDE_GUARD_BOARD_H +#define INCLUDE_GUARD_BOARD_H + +#include +#include +#include // for std::hash + +#include "types.h" + +// Forward declarations +class Move; +class MoveList; + +// pseudo-random-number-generator to fill Zobrist lookup hash table +static constexpr u64 rot(u64 val, int shift) { + return (val << shift) | (val >> (64 - shift)); +} +static constexpr u64 random(u64& a, u64& b, u64& c, u64& d) { + u64 e = a - rot(b, 7); + a = b ^ rot(b, 13); + b = c + rot(d, 37); + c = d + e; + d = e + a; + return d; +} + + +/** + * BitBoard indexing; + * @verbatim + * rank rankIndex + * | index | + * v +-------------------------+ v + * 8 | 0 1 2 3 4 5 6 7 | 0 + * 7 | 8 9 10 11 12 13 14 15 | 1 + * 6 | 16 17 18 19 20 21 22 23 | 2 + * 5 | 24 25 26 27 28 29 30 31 | 3 + * 4 | 32 33 34 35 36 37 38 39 | 4 + * 3 | 40 41 42 43 44 45 46 47 | 5 + * 2 | 48 49 50 51 52 53 54 55 | 6 + * 1 | 56 57 58 59 60 61 62 63 | 7 + * +-------------------------+ + * a b c d e f g h <- file + * 0 1 2 3 4 5 6 7 <- fileIndex + * @endverbatim + */ +class Board { +public: + Board() + : _castle{true, true, true, true} + , _enPassant{static_cast(64)} + , _halfMoveClock{0} + , _plyCount{1} + , _bitBoard{ + 0xFFFF000000000000, // white + 0x000000000000FFFF, // black + 0x00FF00000000FF00, // pawns + 0x4200000000000042, // knights + 0x2400000000000024, // bishops + 0x8100000000000081, // rooks + 0x0800000000000008, // queens + 0x1000000000000010, // kings + } { + _hash = rehash(); // This makes it non default constructible + }; // but the main performance requirement is in the + // copy constructor and assignment operator + + // copy constructor + Board(const Board& board) + : _castle{board._castle} + , _enPassant{board._enPassant} + , _halfMoveClock{board._halfMoveClock} + , _plyCount{board._plyCount} + , _hash{board._hash} + { + for (int i = 0; i < 8; i++) + _bitBoard[i] = board._bitBoard[i]; + }; + // copy assignment operator + Board& operator=(const Board& board) = default; + + // accessor functions for not directly given values + enum piece piece(const Index) const; + enum piece color(const Index) const; + // accessor functions for internal states (private, only altered by Board) + u64 bb(enum piece piece) const { return _bitBoard[piece]; } + bool castleRight(enum piece color, enum location side) const; + Index enPassant() const { return _enPassant; } + unsigned halfMoveClock() const { return _halfMoveClock; } + unsigned plyCount() const { return _plyCount; } + u64 knightMoves(Index from) const { return knightMoveLookup[from]; } + u64 kingMoves(Index from) const { return kingMoveLookup[from]; } + + // Ply Count interpretation of who's move it is + bool isWhiteTurn() const { return static_cast(_plyCount % 2); } + enum piece sideToMove() const { + return static_cast(!(_plyCount % 2)); + } + + // Formatting functions of pieces, squares, board, ...? + char formatPiece(Index fileIndex, Index rankIndex) const; + char formatPiece(Index squareIndex) const; + std::string format() const; + + // Get board hash (Zobrist-hash), for complete computation see `rehash` + u64 hash() const { return _hash; }; + // Get FEN string from board + std::string fen(const bool withCounts = true) const; + // Set board state given by FEN string + void init(const std::string& fen, bool& parseError); + // Complete (new) calculation of Zobrist hash for the current board position + u64 rehash() const; + + // Validate a move (all moves from input checked before passed to `make`) + // and returns a legal (extended with all move info's) or non-move if case of + // illegal moves. + Move isLegal(const Move) const; + // Make a given move (assumed legal, otherwise undefined behavior) + void make(const Move); // passing move by value + + // Generate all legal moves + void moves(MoveList&, bool = false) const; + void moves(MoveList&, const enum piece, bool = false) const; + + // Check if current side to move is in check + bool isCheck() const; + // Check if the current position is a quiet position (no piece is attacked) + bool isQuiet() const; + // Checks if the position is a terminal position, meaning if the game ended + // by mate, stale mate or the 50 modes rule. Three-Fold repetition is NOT + // checked, therefore a seperate game history is required which the board + // does NOT track. + bool isTerminal() const; + // Checks if there is sufficient mating material on the board, meaning if it + // possible for any side to deliver a check mate. More specifically, it + // checks if the pieces on the board are KK, KNK or KBK. + bool isInsufficient() const; + // TODO: validate further possibilities like KBKB for two same color bishobs?! + + // Board evaluation, gives a heuristic score of the current board position + // viewed from whites side + Score evalMaterial(enum piece) const; + Score evalPawns(enum piece) const; + Score evalKingSafety(enum piece) const; + Score evalRooks(enum piece) const; + Score evaluate() const; + // Static Exchange Evaluation + Score see(const Move) const; + + // Subroutines for move generation, ... + u64 attacks(const enum piece, const u64) const; + u64 pinned(const enum piece, const u64, const u64) const; + u64 checkers(const enum piece, const u64, const u64) const; + + // Equality operator used for comparing boards (exact equality in contrast + // to hash equality) + friend bool operator==(const Board& lhs, const Board& rhs) { + bool isEqual = true; + for (int i = 0; i < 8; i++) { + isEqual &= (lhs._bitBoard[i] == rhs._bitBoard[i]); + } + + return isEqual + && (lhs._castle.whiteKingSide == rhs._castle.whiteKingSide) + && (lhs._castle.whiteQueenSide == rhs._castle.whiteQueenSide) + && (lhs._castle.blackKingSide == rhs._castle.blackKingSide) + && (lhs._castle.blackQueenSide == rhs._castle.blackQueenSide) + && (lhs._enPassant == rhs._enPassant); + } + friend bool operator!=(const Board& lhs, const Board& rhs) { + return !(lhs == rhs); + } + +private: + struct { + bool whiteKingSide : 1; + bool whiteQueenSide : 1; + bool blackKingSide : 1; + bool blackQueenSide : 1; + } _castle; + Index _enPassant; + unsigned _halfMoveClock; + unsigned _plyCount; + u64 _hash; + u64 _bitBoard[8]; + + // Lookup table for "random" hash value for piece-square, ... used in + // Zobrist board hashing. + static constexpr std::array hashTable = []() { + std::array table{}; + + u64 a = 0xABCD1729, b = 0, c = 0, d = 0; + for (int i = -11; i < 781; i++) { + u64 rand = random(a, b, c, d); + if (i >= 0) table[i] = rand; + } + return table; + }(); + // Lookup helper for Zobrist hash lookup table + // hash index for 6 pieces of both colors for all squares + constexpr Index hashIndex(enum piece piece, enum piece color, Index sq) const { + return 12 * sq + (6 * (color == black)) + piece - 2; + } + // hash index the e.p. target square (file) + constexpr Index hashIndex(Index sq) const { + return 768 + (sq % 8); + } + + enum piece sqPiece(const u64) const; +}; + +// Inject specialization hash into standard name space +namespace std { + template <> + struct hash { + using result_type = u64; + + u64 operator()(const Board& board) const noexcept { + return board.hash(); + }; + }; +} + +#endif /* INCLUDE_GUARD_BOARD_H */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/HashTable.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/HashTable.h new file mode 100644 index 0000000..0b0ec53 --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/HashTable.h @@ -0,0 +1,150 @@ +#ifndef UCI_GUARD_HASHTABLE_H +#define UCI_GUARD_HASHTABLE_H + +#include "types.h" +#include // std::pair +#include // std::hash +#include +#include + +// Default policy; replace everything with the newest entry +template +struct policy { + constexpr bool operator()(const T&, const T&) { + return true; + } +}; + +template < + typename Key, + typename Entry, + typename Policy = policy, + typename Hashing = std::hash +> +class HashTable { +public: + using Hash = typename Hashing::result_type; + using Line = typename std::pair; + + HashTable() : _size{0}, _occupied{0}, _table{nullptr}, _hash{}, _policy{} { }; + HashTable(Index size) + : _size{size} + , _occupied{0} + , _table{new (std::nothrow) Line[size]} + , _hash{} + , _policy{} + { + if (_table) { + for (Index i = 0; i < _size; i++) { + _table[i].first = static_cast(0); + } + } else { + _size = 0; + } + }; + + ~HashTable() { if (_table) { delete[] _table; } } + + Index size() const { return _size; } + Index used() const { return 1000 * _occupied / _size; } + + bool reserve(Index size) noexcept { + _occupied = 0; + if (size != _size) { + if (_table) { delete[] _table; } + _table = new (std::nothrow) Line[size]; + } + if (_table) { + _size = size; + for (Index i = 0; i < _size; i++) { + _table[i].first = static_cast(0); + } + return false; + } else { + _size = 0; + return true; + } + } + + void clear() noexcept { + _occupied = 0; + for (Index i = 0; i < _size; i++) { + _table[i].first = static_cast(0); + } + } + + void erase() { + _occupied = 0; + _size = 0; + if (_table) { delete[] _table; } + _table = nullptr; + } + + void insert(const Key& key, const Entry& entry) { + assert(0 < _size); + + Hash hash = _hash(key); + Index index = static_cast(hash) % _size; + if (_table[index].first == static_cast(0)) { + _occupied++; + _table[index] = Line(hash, entry); + } + if (_policy(_table[index].second, entry)) { + _table[index] = Line(hash, entry); + } + } + + struct Iter { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Line; + using pointer = value_type*; + using reference = Entry&; + + Iter(pointer ptr) : _ptr{ptr} { }; + + const Hash& hash() const { return _ptr->first; }; + const Entry& value() const { return _ptr->second; }; + + // dereference operators + reference operator*() { return _ptr->second; }; + pointer operator->() { return _ptr; }; + + // comparison operators + friend bool operator==(const Iter& lhs, const Iter& rhs) { + return lhs._ptr == rhs._ptr; + }; + friend bool operator!=(const Iter& lhs, const Iter& rhs) { + return lhs._ptr != rhs._ptr; + }; + + private: + pointer _ptr; + }; + + Iter begin() noexcept { return Iter(_table); } + Iter end() noexcept { return Iter(_table + _size); } + + Iter find(const Key& key) noexcept { + if (_size) { + Hash hash = _hash(key); + if (hash == static_cast(0)) { + return Iter(_table + _size); + } + Index index = static_cast(hash) % _size; + if (_table[index].first == hash) { + return Iter(_table + index); + } + } + return Iter(_table + _size); + } + +private: + Index _size; + Index _occupied; + Line* _table; + Hashing _hash; + Policy _policy; +}; + +#endif /* UCI_GUARD_HASHTABLE_H */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Move.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Move.h new file mode 100644 index 0000000..b8e09b2 --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Move.h @@ -0,0 +1,305 @@ +#ifndef INCLUDE_GUARD_MOVE_H +#define INCLUDE_GUARD_MOVE_H + +#include +#include +#include + +#include "utils.h" +#include "types.h" + +class Move { +public: + using base_type = uint64_t; + +private: + base_type _bits; + +public: + Move() = default; // required by MoveList + // Reverse of cast to base_type + Move(base_type bits) : _bits{bits} { }; + + explicit Move(Index from, Index to) + : Move(from, to, static_cast(0)) { }; + explicit Move(Index from, Index to, enum piece promotion) + : Move(static_cast(0), static_cast(0), from, to, + static_cast(0), promotion) { }; + explicit Move(enum piece color, enum piece piece, Index from, Index to, + enum piece victim) + : Move(color, piece, from, to, victim, static_cast(0)) { }; + // General Move constructor + explicit Move(enum piece color, enum piece piece, Index from, Index to, + enum piece victim, enum piece promotion) + : _bits{static_cast( + ( color << 21) + | ( victim << 18) + | ( piece << 15) + | (promotion << 12) + | ( to << 6) + | from + )} + { + assert(((color == white) + || (color == black))); + assert(((victim == knight) + || (victim == bishop) + || (victim == rook) + || (victim == queen) + || (victim == pawn) + || !victim)); + assert(((piece == knight) + || (piece == bishop) + || (piece == rook) + || (piece == queen) + || (piece == pawn) + || (piece == king) + || !piece)); + assert(((promotion == knight) + || (promotion == bishop) + || (promotion == rook) + || (promotion == queen) + || !promotion)); + assert(( to < 64)); + assert((from < 64)); + }; + + Index from() const { return _bits & 63U; } + Index to() const { return (_bits >> 6) & 63U; } + enum piece promote() const { + return static_cast((_bits >> 12) & 7U); + } + enum piece piece() const { + return static_cast((_bits >> 15) & 7U); + } + enum piece victim() const { + return static_cast((_bits >> 18) & 7U); + } + enum piece color() const { + return static_cast((_bits >> 21) & 7U); + } + + uint32_t score() const { + return static_cast(_bits >> 32); + } + + static constexpr uint32_t killerScore[2] = { 4096U, 4095U }; + void setScore(const uint32_t val) { + constexpr base_type mask = (1ULL << 22) - 1ULL; + _bits = (_bits & mask) | (static_cast(val) << 32); + } + uint32_t calcScore() const { + const uint32_t mvv_lva = ((victim() << 3) + (7U - piece())); + const bool winning = victim() > piece(); + // add 128 to ensure the PST values are positive + const Index t = color() == white ? to() : 63 - to(); + const Index f = color() == white ? from() : 63 - from(); + const uint32_t pst = pieceSquareTables[piece()][t] - pieceSquareTables[piece()][f] + 128; + + return ((static_cast(victim()) * mvv_lva) << (14 + winning * 6)) + pst; + } + // Allows to be cast to the base type (as number) + operator base_type() { return _bits; } + + bool operator!() const { return this->from() == this->to(); } + // Comparison of [] part of the move. Everything else + // is redundent and allows simple parse move routine (use Board::isLegal + // for move legality test and augmentation) + bool operator==(const Move& rhs) const { + constexpr base_type mask = (1ULL << 22) - 1ULL; + return (_bits & mask) == (rhs._bits & mask); + } + bool operator!=(const Move& rhs) const { + constexpr base_type mask = (1ULL << 22) - 1ULL; + return (_bits & mask) != (rhs._bits & mask); + } + + // default sort order is decreasing (for scored moves the best move first) + friend bool operator<(const Move& lhs, const Move& rhs) { + return lhs._bits > rhs._bits; + } +}; + +class MoveList { +private: + static constexpr unsigned _max_size = 256; + unsigned _size; + Move _moves[_max_size]; // apparently, 218 are the max nr. of moves + +public: + MoveList() : _size{0} { }; + + // Copy Constructor + MoveList(const MoveList& moveList) : _size{moveList._size} { + assert(_size <= _max_size); + + std::copy(moveList._moves, moveList._moves + _size, _moves); + } + // Copy Assignement Operator + MoveList& operator=(const MoveList&) = default; + + unsigned size() const { return _size; }; + bool empty() const { return !static_cast(_size); }; + static constexpr unsigned max_size() { return _max_size; }; + void clear() { _size = 0; }; + Move* data() { return _moves; }; + const Move* data() const { return _moves; }; + + template + void emplace_back(Args&&... args) { + assert(_size < _max_size); + + _moves[_size++] = Move(std::forward(args)...); + } + + void push_back(const Move& move) { + assert(_size < _max_size); + + _moves[_size++] = move; + } + + void append(const MoveList& list) { + assert((_size + list._size) < _max_size); + + for (unsigned i = 0; i < list._size; i++) { + _moves[_size++] = list._moves[i]; + } + } + + bool contains(const Move& move) const { + for (unsigned i = 0; i < _size; i++) { + if (_moves[i] == move) { + return true; + } + } + return false; + } + + /** + * Searches for an occurence of `move` in the list and places the `move` as + * the first element in the list. If the provided `move` is not an element + * no opperation is performed and `false` will be returned. + * + * @param move a move to be searched for and placed as first element. + * @return `true` if `move` is found and placed as first element, + * `false` otherwise. + */ + bool move_front(const Move& move) { + for (unsigned i = 0; i < _size; i++) { + if (_moves[i] == move) { + std::swap(_moves[0], _moves[i]); + return true; + } + } + return false; + } + + Move& operator[](unsigned i) { return _moves[i]; } + const Move& operator[](unsigned i) const { return _moves[i]; } + + struct MoveIter { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Move; + using pointer = value_type*; + using reference = value_type&; + + MoveIter(pointer ptr) : _ptr{ptr} { }; + + // Dereference operators + reference operator*() { return *_ptr; }; + pointer operator->() { return _ptr; }; + + // Arithmetic operators + MoveIter& operator++() { _ptr++; return *this; }; + MoveIter operator++(int) { + MoveIter copy = *this; ++(*this); return copy; + }; + MoveIter& operator--() { _ptr--; return *this; }; + MoveIter operator--(int) { + MoveIter copy = *this; --(*this); return copy; + }; + MoveIter& operator+=(int i) { + _ptr += i; return *this; + }; + friend MoveIter operator+(MoveIter it, int i) { return (it += i); }; + MoveIter& operator-=(int i) { + _ptr -= i; return *this; + }; + friend MoveIter operator-(MoveIter it, int i) { return (it -= i); }; + + friend difference_type operator-(const MoveIter& lhs, const MoveIter& rhs) { + return lhs._ptr - rhs._ptr; + }; + + // Comparison operators + friend bool operator==(const MoveIter& lhs, const MoveIter& rhs) { + return lhs._ptr == rhs._ptr; + }; + friend bool operator<=(const MoveIter& lhs, const MoveIter& rhs) { + return lhs._ptr <= rhs._ptr; + }; + friend bool operator>=(const MoveIter& lhs, const MoveIter& rhs) { + return lhs._ptr >= rhs._ptr; + }; + friend bool operator!=(const MoveIter& lhs, const MoveIter& rhs) { + return lhs._ptr != rhs._ptr; + }; + friend bool operator<(const MoveIter& lhs, const MoveIter& rhs) { + return lhs._ptr < rhs._ptr; + }; + friend bool operator>(const MoveIter& lhs, const MoveIter& rhs) { + return lhs._ptr > rhs._ptr; + }; + + + private: + pointer _ptr; + }; + + MoveIter begin() { return MoveIter(_moves); }; + MoveIter end() { return MoveIter(_moves + _size); }; + + struct ConstMoveIter { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = const Move; + using pointer = const value_type*; + using reference = const value_type&; + + ConstMoveIter(pointer ptr) : _ptr{ptr} { }; + + // Dereference operators + reference operator*() { return *_ptr; }; + pointer operator->() { return _ptr; }; + + // Arithmetic operators + ConstMoveIter& operator++() { _ptr++; return *this; }; + ConstMoveIter operator++(int) { + ConstMoveIter copy = *this; ++(*this); return copy; + }; + ConstMoveIter& operator--() { _ptr--; return *this; }; + ConstMoveIter operator--(int) { + ConstMoveIter copy = *this; --(*this); return copy; + }; + ConstMoveIter& operator+=(int i) { + _ptr += i; return *this; + }; + + // Comparison operators + friend bool operator==(const ConstMoveIter& lhs, const ConstMoveIter& rhs) { + return lhs._ptr == rhs._ptr; + }; + friend bool operator!=(const ConstMoveIter& lhs, const ConstMoveIter& rhs) { + return lhs._ptr != rhs._ptr; + }; + + private: + pointer _ptr; + }; + + ConstMoveIter begin() const { return ConstMoveIter(_moves); }; + ConstMoveIter end() const { return ConstMoveIter(_moves + _size); }; +}; + +#endif /* INCLUDE_GUARD_MOVE_H */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/bit.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/bit.h new file mode 100644 index 0000000..dd59985 --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/bit.h @@ -0,0 +1,63 @@ +#ifndef INCLUDE_GUARD_BIT_H +#define INCLUDE_GUARD_BIT_H + +#include + +// Polyfill for ``, it compiled with proper standard this falls back to +// the STL implementation. Only provides functionality if not supported. + +// TODO: extend to others than gcc (and maybe clang?) +// Inject polyfill for `std::endian` (since C++20) +namespace std { + enum class endian { + little = __ORDER_LITTLE_ENDIAN__, + big = __ORDER_BIG_ENDIAN__, + native = __BYTE_ORDER__ + }; + + // Polyfill `std::byteswap` (since C++23) + template + constexpr T byteswap(T x) noexcept { +#ifdef __GNUC__ + if constexpr (sizeof(T) == 8) { + uint64_t swapped = __builtin_bswap64(*reinterpret_cast(&x)); + return *reinterpret_cast(&swapped); + } else if constexpr (sizeof(T) == 4) { + uint32_t swapped = __builtin_bswap32(*reinterpret_cast(&x)); + return *reinterpret_cast(&swapped); + } else if constexpr (sizeof(T) == 2) { + uint16_t swapped = __builtin_bswap16(*reinterpret_cast(&x)); + return *reinterpret_cast(&swapped); + } else { + static_assert(sizeof(T) == 1); + return x; + } +#else + if constexpr (sizeof(T) == 8) { + uint64_t swapped = *reinterpret_cast(&x); + swapped = (((swapped & static_cast(0xFF00FF00FF00FF00ULL)) >> 8) + | ((swapped & static_cast(0x00FF00FF00FF00FFULL)) << 8)); + swapped = (((swapped & static_cast(0xFFFF0000FFFF0000ULL)) >> 16) + | ((swapped & static_cast(0x0000FFFF0000FFFFULL)) << 16)); + swapped = ((swapped >> 32) | (swapped << 32)); + return *reinterpret_cast(&swapped); + } else if constexpr (sizeof(T) == 4) { + uint32_t swapped = *reinterpret_cast(&x); + swapped = (((swapped & static_cast(0xFF00FF00)) >> 8) + | ((swapped & static_cast(0x00FF00FF)) << 8)); + swapped = ((swapped >> 16) | (swapped << 16)); + return *reinterpret_cast(&swapped); + } else if constexpr (sizeof(T) == 2) { + uint16_t swapped = *reinterpret_cast(&x); + swapped = ((swapped << 8) | (swapped >> 8)); + return *reinterpret_cast(&swapped); + } else { + static_assert(sizeof(T) == 1); + return x; + } +#endif + } + +} /* namespace std */ + +#endif /* INCLUDE_GUARD_BIT_H */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/nullstream.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/nullstream.h new file mode 100644 index 0000000..a88ffca --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/nullstream.h @@ -0,0 +1,17 @@ +#ifndef INCLUDE_GUARD_NULLSTREAM_H +#define INCLUDE_GUARD_NULLSTREAM_H + +#include + +class nullstream : public std::ostream { +public: + nullstream() : std::ostream(nullptr) { } + nullstream(const nullstream&) : std::ostream(nullptr) { } + + template + nullstream& operator<<(const T& rhs) { return *this; } + + nullstream& operator<<(std::ostream& (*fn)(std::ostream&)) { return *this; } +}; + +#endif /* INCLUDE_GUARD_NULLSTREAM_H */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/search.cpp b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/search.cpp new file mode 100644 index 0000000..8ae609f --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/search.cpp @@ -0,0 +1,700 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "Board.h" +#include "Move.h" +#include "uci.h" +#include "syncstream.h" +#include "search.h" +#include "HashTable.h" + +namespace Search { + +// Node of a transposition table and its type +enum TTType { exact, upper_bound, lower_bound, unknown }; +struct TTEntry { + enum TTType type; + unsigned age; + int depth; + Score score; + Move move; +}; +// Transposition Table collision policy +struct TTPolicy { + bool operator()(const TTEntry& entry, const TTEntry& candidate) { + return (entry.age < candidate.age) + || ((entry.type == candidate.type) && (entry.depth <= candidate.depth)) + || (candidate.type == exact); + } +}; + +// Implementation specific global search state, workers and concurrency controls +State state; +HashTable tt; +std::atomic isRunning; +std::vector workers; + +// Set initial static age of search +template +unsigned PVS::_age = 0; + +void init(unsigned ttSize) { + // Init internal state + state.depth = 16; // default max search depth + state.wtime = 24 * 60 * 60 * 1000; // 1 day in milliseconds + state.btime = 24 * 60 * 60 * 1000; // 1 day in milliseconds + state.winc = 0; + state.binc = 0; + state.movetime = 0; + + // Init Transposition Table + constexpr unsigned ttLineSize = sizeof(HashTable::Line); + // Reserve/resize tt to ttSize in MB of memory + if (tt.reserve(ttSize * 1024U * 1024U / ttLineSize)) { + // Report error of reserve failed + osyncstream(cout) << "info string error Unable to resize " + "Transposition Table to " << ttSize << "MB" << std::endl; + } +} + +double ttSize() { + // Init Transposition Table + constexpr unsigned ttLineSize = sizeof(HashTable::Line); + // compute size in MB from number of lines (size) + return static_cast(tt.size() * ttLineSize) / 1048576.0; +} + +void newgame() { + // Reset internal state + state.depth = 16; // default max search depth + state.wtime = 24 * 60 * 60 * 1000; // 1 day in milliseconds + state.btime = 24 * 60 * 60 * 1000; // 1 day in milliseconds + state.winc = 0; + state.binc = 0; + state.movetime = 0; + + // Delete all data from TT table + tt.clear(); +} + +// Perft implementation +unsigned perft_subroutine(Board& board, int depth) { + if (depth <= 0) { return 1; } + + Index nodeCount = 0; + MoveList searchmoves; + board.moves(searchmoves); + + if (depth == 1) { + nodeCount = searchmoves.size(); + } else { + Board boardCopy = board; + for (Move move : searchmoves) { + board.make(move); + nodeCount += perft_subroutine(board, depth - 1); + board = boardCopy; // unmake move + } + } + + return nodeCount; +} +/** + * Prints for all moves in passed move list (assuming legal moves) number of + * `depth - 1` performance test node count of this child node count. + * + * @param board Reference root node board position. + * @param depth search depth. + * @param moveList List of legal moves at current board position to search. + * If moveList is empty, search all legal moves (Note: In this case, the + * moveList is filled with all legal moves manipulating the list in place). + * + * @example Output generated from start position for depth `5` with moveList + * containing the moves "d4", "Nf3" and "Nc3". + * position startpos + * go perft 5 searchmoves d2d4 g1f3 b1c3 + * d2d4: 361790 + * g1f3: 233491 + * b1c3: 234656 + * + * Nodes searched: 829937 + */ +void perft(Board& board, int depth, MoveList& searchmoves) { + if (depth <= 0) { + osyncstream(cout) << "Nodes searched: 0" << std::endl; + return; + } + + if (!searchmoves.size()) { + board.moves(searchmoves); + } + + Index totalCount = 0; + + Board boardCopy = board; + for (Move move : searchmoves) { + // check if shutdown/stopping is requested + if (!isRunning.load(std::memory_order_relaxed)) { break; } + // continue traversing + board.make(move); + Index nodeCount; + nodeCount = perft_subroutine(board, depth - 1); + totalCount += nodeCount; + board = boardCopy; // unmake move + + // report moves of node + osyncstream(cout) << move << ": " << nodeCount << std::endl; + } + + osyncstream(cout) + << std::endl << "Nodes searched: " << totalCount << std::endl; +} +/** + * Specialization of `perft(Board&, int, MoveList&)` for all legal moves. + */ +void perft(Board& board, int depth) { + MoveList moveList; // init empty move list -> perft for all legal moves + perft(board, depth, moveList); +} + +/** + * Search initialization + */ +template +PVS::PVS(const std::vector& game, const State& config) + : _root(game.back()) + // Restrict max search depth by the principal variation capacity + // Upper depth bound allows more than 200 plys which is enough. + , _max_depth{std::min(config.depth, static_cast(MoveList::max_size()))} + // Init selective depth (max reached pvs + qsearch depth, max search ply) + , _seldepth{0} + // Set visited nodes to zero + , _nodes{0} + // break condition flag + , _isStopped{false} + // search start time for time dependend break condition and reporting + , _start_time{clock::now()} + , _searchmoves(config.searchmoves) +{ + // Fill search moves with all legal moves if no moves are specified + if (_searchmoves.empty()) { + _root.moves(_searchmoves); + } + + // Increment age (for TT entries) + ++_age; + + // Time control; set time to stop searching (ignoring potental loss of 2 ms) + unsigned half_move_time; + if (config.movetime) { + half_move_time = config.movetime / 2U; + } else { + if (_root.isWhiteTurn()) { + half_move_time = std::max(config.wtime / 60U, 50U); + } else { + half_move_time = std::max(config.btime / 60U, 50U); + } + } + _mid_time = _start_time + milliseconds( half_move_time); + _end_time = _start_time + milliseconds(2U * half_move_time); + + // fill repitition detection history + _historySize = _root.halfMoveClock() + 1 < game.size() + ? _root.halfMoveClock() + 1 + : game.size(); + auto gameIter = game.rbegin(); + for (size_t i = _historySize; i-- > 0; ) { + _history[i] = (*gameIter++).hash(); + } +} + +/** + * Principal Variation Search at the Root Position + */ +template +Score PVS::operator()() { + using std::chrono::duration_cast; + + // Color keeps track of static evaluation sign for the current player + // in the negated maximization (negaMax) framework + Score color = _root.isWhiteTurn() ? +1 : -1; + + // Tracks principal variation + MoveList pv_line; + + // set killers all to !move (TODO: is this required?) + std::fill_n(_killers[0], MoveList::max_size(), Move(0)); + std::fill_n(_killers[1], MoveList::max_size(), Move(0)); + + // set history heuristic to zero // TODO: can I replace that by zero-initialization?! + for (Index p = 0; p < 6; ++p) { + std::fill_n(_historyTable[0][p], 64, 0); + std::fill_n(_historyTable[1][p], 64, 0); + } + + // Iterative deepening + Score prevScore = 0; + for (int depth = 1; depth <= _max_depth; depth++) { + // Clear temporary pv line + pv_line.clear(); + + // Aspiration window search + Score alpha, score, beta; + Score delta = 34; + // Set initial aspriation window bounds (shallow searches -> full width) + if (depth < 3) { + alpha = limits::lower(); + beta = limits::upper(); + } else { + alpha = prevScore - delta; + beta = prevScore + delta; + } + while (true) { + // search current depth and aspiration window + score = pvs(_root, depth, 0, alpha, beta, color, pv_line); + + // Increase aspiration window + delta += 2 * delta + 5; + + // Increase window (iff fail high/low) + if (score <= alpha) { + alpha = std::max(prevScore - delta, limits::lower() - 1); + } else if (beta <= score) { + beta = std::max(prevScore + delta, limits::upper() + 1); + } else { + break; + } + } + + // Getting deeper, set previous iteration score in iterative deepening + prevScore = score; + + // Check search break condition (before updating the pv and reporting + // partial results) + if (_isStopped + || !isRunning.load(std::memory_order_relaxed) + || (_end_time < clock::now())) { + break; + } + + // Copy principal variation + _pv.clear(); + _pv.append(pv_line); + + auto now = clock::now(); + // Time spend in search till now (only for reporting) + auto duration = duration_cast(now - _start_time); + // Check if found mate and report mate instead of centi pawn score + if (limits::upper() <= (std::abs(score) + depth)) { + // Note: +-1 for converting 0-indexed ply count to a 1-indexed + // move count + if (score < 0) { + score = limits::lower() - score - 1; + } else { + score = limits::upper() - score + 1; + } + // ply to move count + score /= 2; + // Report search stats and mate in score moves + osyncstream(cout) + << "info depth " << depth + << " seldepth " << _seldepth + << " score mate " << score + << " time " << duration.count() + << " nodes " << _nodes + << " pv " << _pv + << std::endl; + // stop iterative deepening, found check mate -> no need to continue + break; + } else { + // Report search progress + osyncstream(cout) + << "info depth " << depth + << " seldepth " << _seldepth + << " score cp " << score + << " time " << duration.count() + << " nodes " << _nodes + << " pv " << _pv + << std::endl; + } + + // Check current time againt search mid time, iff passed the mid point + // a deeper iteration in the iterative deepening will most likely not + // finish. Therefore, stop and save the time for later. + if (_mid_time <= now) { break; } + } + + if (_pv.size()) { + auto out = osyncstream(cout); + out << "bestmove " << _pv[0]; + if (1 < _pv.size()) { + out << " ponder " << _pv[1]; + } + out << std::endl; + } else { + // If there is no move in _pv (can happen in extreme short time control) + // just make a random (first) move, better than failing. + _root.moves(_pv); // _pv is empty + if (_pv.size()) { + osyncstream(cout) << "bestmove " << _pv[0] << std::endl; + } else { + osyncstream(cout) << "bestmove !move" << std::endl; + } + } + + // Before leaving search, update search state (remaining time) + auto duration = duration_cast(clock::now() - _start_time); + if (_root.isWhiteTurn()) { + state.wtime += state.winc - (duration.count() + 1); + } else { + state.btime += state.binc - (duration.count() + 1); + } + + // Reset running flag + isRunning.store(false, std::memory_order_relaxed); + + return (_root.isWhiteTurn() ? 1 : -1) * prevScore; +} + +template +Score PVS::qSearch(const PVS::Position& pos, int ply, + Score alpha, Score beta, Score color +) { + // Track selective search depth counter (reported while searching) + _seldepth = std::max(_seldepth, ply); + + // Static evaluation + Score score = color * pos.eval(); + + bool capturesOnly = true; + // Check if in check, if in check generate all moves (check evations). + // Otherwise, check if not moving at all (stand pat) is already too good. + if (pos.isCheck()) { + capturesOnly = false; + } else if (beta <= score) { + ++_nodes; + return beta; + } + + // Generate all captures (captures only, if not in check) + MoveList moveList; + pos.moves(moveList, capturesOnly); + + // Check for terminal node + if (moveList.empty()) { + ++_nodes; + // If there are no captures left, return static evaluation score + if (capturesOnly) { + return score; + // When generated all moves (in case of check evations), the fact that + // there are no moves means mate. + } else { + return color * (pos.isWhiteTurn() ? +1 : -1) + * (limits::lower() + ply); + } + } + + // Ensure capture is better than stand pat + alpha = std::max(alpha, score); + + // if (capturesOnly) { + // for (Move& move : moveList) { + // // add 65538 to ensure the set move score is positive + // move.setScore(pos.see(move) + 65538); + // } + // } else { + for (Move& move : moveList) { + // MVV-LVA capture move order + move.setScore(move.calcScore()); + } + // } + std::sort(moveList.begin(), moveList.end()); + + // Recursively capture pieces + Position board = pos; + for (Move move : moveList) { + // SEE pruning (drop losing captures) + if (capturesOnly && (pos.see(move) < 0)) { + continue; + } + + board.make(move); + + score = -qSearch(board, ply + 1, -beta, -alpha, -color); + + if (beta <= score) { + return beta; // fail-hard beta-cutoff + } + alpha = std::max(alpha, score); + + board = pos; // unmake + } + + return alpha; +} + +/** + * Principal Variation Search routine (all non-root) positions + */ +template +Score PVS::pvs(const PVS::Position& pos, int depth, int ply, + Score alpha, Score beta, Score color, MoveList& pv_line +) { + // Check for search shutdown and in case of a repetition return draw score + if (_isStopped || isRepetition(pos)) { return 0; } + // Check break condition every few thousend nodes + if (!(_nodes % (32 * 1024))) { + // Just return 0, partial search will be discarded + if (!isRunning.load(std::memory_order_relaxed) + || (_end_time < clock::now())) { + _isStopped = true; + return 0; + } + } + + // Save original alpha bound before TT entry update + Score original_alpha = alpha; + + // Move list containing all moves to be searched (usually all legal moves) + MoveList moveList; + if (!ply) { + moveList.append(_searchmoves); + } + + // Transposition table lookup + auto tt_line = tt.find(pos); + if ((tt_line != tt.end()) && ((*tt_line).depth >= depth)) { + // Need to create all moves here to check if the TT move is legal + // (or restricted to searchmoves) + if (moveList.empty()) { + pos.moves(moveList); + } + // Only use the TT entry if TT move is legal + if (moveList.contains((*tt_line).move)) { + switch ((*tt_line).type) { + case lower_bound: + alpha = std::max(alpha, (*tt_line).score); + break; + case exact: + ++_nodes; + pv_line.clear(); + pv_line.push_back((*tt_line).move); + return (*tt_line).score; + case upper_bound: + beta = std::min(beta, (*tt_line).score); + break; + default: + assert((false && "Unknown TT node type entry in TT lookup")); + } + // Check altered alpha/beta bounds + if (beta <= alpha) { + ++_nodes; + return (*tt_line).score; + } + } else { + // Reset TT entry to "not-found" + tt_line = tt.end(); + } + } + + // Max search depth reached + if (depth <= 0) { + return qSearch(pos, ply, alpha, beta, color); + } + + // Generate all moves (if not already generated or constraint) + if (moveList.empty()) { + pos.moves(moveList); + } + + // Check terminal node + if (moveList.empty()) { + // Increment node count + ++_nodes; + // Evaluate terminal node score (check/stale mate) + if (pos.isCheck()) { + return color * (pos.isWhiteTurn() ? +1 : -1) + * (limits::lower() + ply); + } else { + return 0; + } + } + + // Sort moves, iff hash move its the first, then captures in MVV-LVA + // followed by the rest + auto moveOrder = moveList.begin(); + if (tt_line != tt.end()) { + if (moveList.move_front((*tt_line).move)) { + ++moveOrder; + } + } + + // score moves + for (Move& move : moveList) { + auto mScore = move.calcScore(); + // Add killer move score -> killers after captures + mScore += (move == _killers[0][ply]) * Move::killerScore[0]; + mScore += (move == _killers[1][ply]) * Move::killerScore[1]; + // add history heuristic score -> sort of non-captures + mScore += static_cast(move.victim()) + * _historyTable[move.color()][move.piece() - 2][move.to()]; + move.setScore(mScore); + } + + // TODO: best move selection instead of sort + std::sort(moveOrder, moveList.end(), + [](Move::base_type lhs, Move::base_type rhs) { return lhs > rhs; }); + + + // New child pv line + MoveList cur_line; + + // New TT entry (tracks "best" move) + TTEntry tt_entry{unknown, _age, depth, 0, Move{}}; + + // Current board + Position board = pos; + + Index moveCount = 0; + Score score = limits::lower(); + for (Move move : moveList) { + board.make(move); + pushHistory(board); + + Score value; + if (!moveCount) { + value = -pvs(board, depth - 1, ply + 1, + -beta, -alpha, -color, cur_line); + } else { + // LMR (Late Move Reduction), reduce search depth of later moves + int R = 0; // TODO: disabled!!! + // int R = (moveCount > 3) & (ply > 2) & !move.victim() & !pos.isCheck(); + + // Zero-window search (with possible reduction) + value = -pvs(board, depth - 1 - R, ply + 1, + -(alpha + 1), -alpha, -color, cur_line); + if (alpha < value) { + // re-search (with clean current pv line and without reduction) + cur_line.clear(); + value = -pvs(board, depth - 1, ply + 1, + -beta, -alpha, -color, cur_line); + } + } + ++moveCount; + score = std::max(score, value); + + // Check for beta cutoff + if (beta <= score) { + // Set possible lower bound hash hash move + tt_entry.move = move; + // Store killer move which is a quiet move causing a beta cut-off + if (!move.victim()) { + // If move isn't the first killer move we insert which ensures + // (assuming killers are different or !move) that two different + // killers are stored + if (_killers[0][ply] != move) { + _killers[1][ply] = _killers[0][ply]; + _killers[0][ply] = move; + } + // Increment history heuristic + _historyTable[move.color()][move.piece() - 2][move.to()] += depth * depth; + } + popHistory(); + break; + } + // Check if alpha raised; iff add new pv/best move + if (alpha < score) { + alpha = score; + // Track pv (implicitly the best move) + pv_line.clear(); + pv_line.push_back(move); + pv_line.append(cur_line); + // Track best move as hash move + tt_entry.move = move; + } + + board = pos; // unmake move + popHistory(); + } + + // Add TT entry + tt_entry.score = score; + if (score <= original_alpha) { + tt_entry.type = upper_bound; + } else if (beta <= score) { + tt_entry.type = lower_bound; + } else { + tt_entry.type = exact; + } + tt.insert(pos, tt_entry); + + return score; +} + + +// Starts searching the given board position by dispatching a worker thread +void start(std::vector& game, State& config) { + if (isRunning.load(std::memory_order_consume)) { + // Ignore further search start attempts if allready searching, requires + // a stop first! + return; + } else { + // Dispose of finished/stopped workers + for (std::thread& worker : workers) { + if (worker.joinable()) { + worker.join(); + } + } + workers.clear(); + } + // sets worker thread stop condition to false (before dispatch) + isRunning.store(true, std::memory_order_release); + + // Dispatch search worker + switch (config.search) { + case State::Type::perft: + state.search = State::Type::perft; + workers.emplace_back([&]() { + // Copy working variables, not subject to change + Board pos(game.back()); + MoveList searchmoves = config.searchmoves; + // Start perft + perft(pos, config.depth, searchmoves); + // Reset running flag + isRunning.store(false, std::memory_order_relaxed); + }); + break; + case State::Type::search: + std::cout << "info string HCE evaluation" << std::endl; + workers.emplace_back(PVS(game, config)); + break; + case State::Type::ponder: + std::cerr << "info string error pondering not implemented!" << std::endl; + break; + default: + assert((false && "Search::start got request for unknown search type.")); + } +} + +void stop() { + // revoke isRunning flag -> workers stop as soon as possible + isRunning.store(false, std::memory_order_relaxed); + + // then join and dispose all workers + for (std::thread& worker : workers) { + if (worker.joinable()) { + worker.join(); + } + } + workers.clear(); +} + +// Explicit instantiations of PVS types using different evaluators +template class PVS; + +} /* namespace Search */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/search.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/search.h new file mode 100644 index 0000000..4020ad5 --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/search.h @@ -0,0 +1,203 @@ +#ifndef INCLUDE_GUARD_SEARCH_H +#define INCLUDE_GUARD_SEARCH_H + +#include +#include +#include +#include +#include + +#include "types.h" +#include "Move.h" +#include "Board.h" + +namespace Search { + +struct State { + enum class Type { perft, search, ponder }; + + enum Type search; + int depth; + unsigned wtime; + unsigned btime; + unsigned winc; + unsigned binc; + unsigned movetime; + MoveList searchmoves; + + State() = default; + State(const State& state) + : search{Type::search} + , depth{state.depth} + , wtime{state.wtime} + , btime{state.btime} + , winc{state.winc} + , binc{state.binc} + , movetime{state.movetime} + , searchmoves() { }; +}; + +extern State state; +extern std::atomic isRunning; + +/** + * Initialization of search specific (global) variables + */ +void init(unsigned ttSize = 32); + +/** + * Diagnostic routine to get the allocated TT size in MB + */ +double ttSize(); + +/** + * Resets all search internal variables to initial configuration state + */ +void newgame(); + +/** + * Performance Test Subroutine, simple perft function without any I/O. + * + * Used in `perft.cpp` testing utility (therefore accessable). + * + * @param board Reference root node board position. + * @param depth search depth. + * + * @return number of moves/nodes from root node of search depth. + */ +unsigned perft_subroutine(Board& board, int depth); + +/** + * Board wrapper in combination with evaluation to abstract the different + * position evaluation options (HCE Board evaluate and NNUE evaluate) + * + * @note The NNUE evaluation is removed from this code base! + */ +template +class GameState : public Board { +public: + GameState(const Board& pos) : Board(pos) { }; + + Score eval() { return static_cast(this)->eval(); } +}; + +/** + * Specialized CRTP derived HCE Board evaluation position + */ +class BoardState : public GameState { +public: + BoardState(const Board& pos) : GameState(pos) { }; + + Score eval() const { return this->evaluate(); } + +}; + +/** + * Search routine class handling all search relevant data and executes the search + * + * This is a Principal Variabtion Search with alpha-beta pruning and a + * transposition table implemented in an iterative deepening framework. + * + * There are two Evaluation types, ether the HCE Board evaluation `PVS` + * or the NNUE evaluation `PVS`. + * + * @note The NNUE evaluation is removed from this code base! + */ +template +class PVS { +public: + PVS(const std::vector&, const State&); + + Score operator()(); + + Move bestMove() const { return _pv[0]; } + + // TODO: there is a BUG collecting the PV! + const MoveList& pv() const { return _pv; } + + // Analytic routine to get the number of searched leave nodes + unsigned nodes() const { return _nodes; } + +private: + using clock = std::chrono::high_resolution_clock; + using milliseconds = std::chrono::milliseconds; + using time_point = decltype(clock::now()); + using Position = BoardState; + + // Root search position (HCE Board state) + Position _root; + // Max search depth (excluding QSearch) + int _max_depth; + + // Selective search counter (needs tracking) + int _seldepth; + // Number of nodes visited (leave nodes) + unsigned _nodes; + // Set to exit the search as soon as possible (breaks out of all recursions) + bool _isStopped; + // Start time of the search (for break condition check) + time_point _start_time; + // Middle time between _start_time and _end_time used to check if an + time_point _mid_time; + // Time till the search should stop (if now() is bigger, stop the search) + time_point _end_time; + // Moves to be searched, if empty all legal moves are searched + MoveList _searchmoves; + // Principal Variation + MoveList _pv; + // Killer Moves (initialized to contain !move entries) + Move _killers[2][MoveList::max_size()]; + // History Heuristic Table (for move ordering) + Score _historyTable[2][6][64]; // [color][piece][to Square] + // Number of elements in `_history` + size_t _historySize; + // Game history list storing previous board hashes for repetition detection + u64 _history[100 + MoveList::max_size()]; + // Increasing age for TT entry + static unsigned _age; + + // adds a position to the history + void pushHistory(const Position& pos) { + _history[_historySize++] = pos.hash(); + } + // and removes the last position from the history + void popHistory() { --_historySize; } + + // two-fold repetition detection + bool isRepetition(const Position& pos) const { + u64 hash = pos.hash(); + + for (size_t i = _historySize - 1; i-- > 0; ) { + if (_history[i] == hash) { + return true; + } + } + return false; + } + + // Principal Variation Search routine (for not root nodes) + Score pvs(const Position& pos, int depth, int ply, + Score alpha, Score beta, Score color, MoveList& pv_line); + // Quiesence Search + Score qSearch(const Position& pos, int ply, + Score alpha, Score beta, Score color); +}; + +/** + * Starts searching on the given board position. + */ +void start(std::vector& game, struct State& config); + +/** + * Stops all workers. + * + * Tells all workers to stop as soon as possible and closes working threads. + * + * @note: This function blocks + */ +void stop(); + + +} /* namespace Search */ + +#endif /* INCLUDE_GUARD_SEARCH_H */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/syncstream.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/syncstream.h new file mode 100644 index 0000000..27cdc41 --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/syncstream.h @@ -0,0 +1,38 @@ +#ifndef INCLUDE_GUARD_SYNCSTREAM_H +#define INCLUDE_GUARD_SYNCSTREAM_H + +#include +#include + +class osyncstream { +public: + osyncstream(std::ostream& base_ostream) + : _base_ostream(base_ostream) + { + mtx().lock(); + } + + ~osyncstream() { + mtx().unlock(); + } + + template + osyncstream& operator<<(const T& rhs) { + _base_ostream << rhs; + return *this; + } + + osyncstream& operator<<(std::ostream& (*fn)(std::ostream&)) { + _base_ostream << fn; + return *this; + } + +private: + std::mutex& mtx() { + static std::mutex _mtx; + return _mtx; + }; + std::ostream& _base_ostream; +}; + +#endif /* INCLUDE_GUARD_SYNCSTREAM_H */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/types.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/types.h new file mode 100644 index 0000000..50122dc --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/types.h @@ -0,0 +1,195 @@ +#ifndef INCLUDE_GUARD_TYPES_H +#define INCLUDE_GUARD_TYPES_H + +#include // uint64_t +#include // std::numeric_limits + +/** square, file and rank index (index > 63 indicates illegal or off board) */ +using Index = unsigned; +/** Bit board, exactly 64 bits (one bit per square) */ +using u64 = uint64_t; // easy on the eyes (well, my eyes) +/** + * Board position score from white point of view in centipawns. + * (1 pawn ~ 100 centipawns) + */ +using Score = int; + +template +struct limits { + static constexpr T upper(); + static constexpr T lower(); +}; + +template <> +struct limits { + static constexpr Score upper() { return static_cast(+32768); }; + static constexpr Score lower() { return static_cast(-32768); }; + static constexpr bool isMate(const Score score) { + constexpr Score mateBound = upper() - 512; + return (score < -mateBound) || (mateBound < score); + } +}; + +enum piece { + none = 0, + white = 0, + black = 1, + pawn = 2, + knight = 3, + bishop = 4, + rook = 5, + queen = 6, + king = 7 +}; + +enum square : Index { + a8, b8, c8, d8, e8, f8, g8, h8, + a7, b7, c7, d7, e7, f7, g7, h7, + a6, b6, c6, d6, e6, f6, g6, h6, + a5, b5, c5, d5, e5, f5, g5, h5, + a4, b4, c4, d4, e4, f4, g4, h4, + a3, b3, c3, d3, e3, f3, g3, h3, + a2, b2, c2, d2, e2, f2, g2, h2, + a1, b1, c1, d1, e1, f1, g1, h1 +}; + +enum location { + Square, + Up, Down, Left, Right, + QueenSide = Left, KingSide = Right, + File, Rank, + Diag, AntiDiag, + RightUp, RightDown, + LeftUp, LeftDown, + Plus, Cross, Star, + WhiteSquares, BlackSquares +}; + +// Material weighting per piece +constexpr Score pieceValues[8] = { + 0, 0, // white, black (irrelevant) + 100, // pawn + 300, // knight + 300, // bishop + 500, // rook + 900, // queen + 0 // king (irrelevant, always 2 opposite kings) +}; + +// Move lookup tables for knights and kings +constexpr u64 knightMoveLookup[64] = { + 0x0000000000020400, 0x0000000000050800, 0x00000000000a1100, 0x0000000000142200, + 0x0000000000284400, 0x0000000000508800, 0x0000000000a01000, 0x0000000000402000, + 0x0000000002040004, 0x0000000005080008, 0x000000000a110011, 0x0000000014220022, + 0x0000000028440044, 0x0000000050880088, 0x00000000a0100010, 0x0000000040200020, + 0x0000000204000402, 0x0000000508000805, 0x0000000a1100110a, 0x0000001422002214, + 0x0000002844004428, 0x0000005088008850, 0x000000a0100010a0, 0x0000004020002040, + 0x0000020400040200, 0x0000050800080500, 0x00000a1100110a00, 0x0000142200221400, + 0x0000284400442800, 0x0000508800885000, 0x0000a0100010a000, 0x0000402000204000, + 0x0002040004020000, 0x0005080008050000, 0x000a1100110a0000, 0x0014220022140000, + 0x0028440044280000, 0x0050880088500000, 0x00a0100010a00000, 0x0040200020400000, + 0x0204000402000000, 0x0508000805000000, 0x0a1100110a000000, 0x1422002214000000, + 0x2844004428000000, 0x5088008850000000, 0xa0100010a0000000, 0x4020002040000000, + 0x0400040200000000, 0x0800080500000000, 0x1100110a00000000, 0x2200221400000000, + 0x4400442800000000, 0x8800885000000000, 0x100010a000000000, 0x2000204000000000, + 0x0004020000000000, 0x0008050000000000, 0x00110a0000000000, 0x0022140000000000, + 0x0044280000000000, 0x0088500000000000, 0x0010a00000000000, 0x0020400000000000 +}; +constexpr u64 kingMoveLookup[64] = { + 0x0000000000000302, 0x0000000000000705, 0x0000000000000E0A, 0x0000000000001C14, + 0x0000000000003828, 0x0000000000007050, 0x000000000000E0A0, 0x000000000000C040, + 0x0000000000030203, 0x0000000000070507, 0x00000000000E0A0E, 0x00000000001C141C, + 0x0000000000382838, 0x0000000000705070, 0x0000000000E0A0E0, 0x0000000000C040C0, + 0x0000000003020300, 0x0000000007050700, 0x000000000E0A0E00, 0x000000001C141C00, + 0x0000000038283800, 0x0000000070507000, 0x00000000E0A0E000, 0x00000000C040C000, + 0x0000000302030000, 0x0000000705070000, 0x0000000E0A0E0000, 0x0000001C141C0000, + 0x0000003828380000, 0x0000007050700000, 0x000000E0A0E00000, 0x000000C040C00000, + 0x0000030203000000, 0x0000070507000000, 0x00000E0A0E000000, 0x00001C141C000000, + 0x0000382838000000, 0x0000705070000000, 0x0000E0A0E0000000, 0x0000C040C0000000, + 0x0003020300000000, 0x0007050700000000, 0x000E0A0E00000000, 0x001C141C00000000, + 0x0038283800000000, 0x0070507000000000, 0x00E0A0E000000000, 0x00C040C000000000, + 0x0302030000000000, 0x0705070000000000, 0x0E0A0E0000000000, 0x1C141C0000000000, + 0x3828380000000000, 0x7050700000000000, 0xE0A0E00000000000, 0xC040C00000000000, + 0x0203000000000000, 0x0507000000000000, 0x0A0E000000000000, 0x141C000000000000, + 0x2838000000000000, 0x5070000000000000, 0xA0E0000000000000, 0x40C0000000000000 +}; + +// Declare I/O streams (allows to globaly replace the I/O streams) +#ifdef RCPP_RCOUT + #include + // Set I/O streams to Rcpp I/O streams + static Rcpp::Rostream cout; + static Rcpp::Rostream cerr; +#elif NULLSTREAM + #include "nullstream.h" + // Set I/O streams to "null" + static nullstream cout; + static nullstream cerr; +#else + #include + // Default STL I/O streams + using std::cout; + using std::cerr; +#endif + +// Piece Square tables (from TSCP) +// see: https://www.chessprogramming.org/Simplified_Evaluation_Function +constexpr Score pieceSquareTables[8][64] = { + { }, { }, // white, black (empty) + { // pawn (white) + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 10, 15, 20, 20, 15, 10, 5, + 4, 8, 12, 16, 16, 12, 8, 4, + 3, 6, 9, 12, 12, 9, 6, 3, + 2, 4, 6, 8, 8, 6, 4, 2, + 1, 2, 3, -10, -10, 3, 2, 1, + 0, 0, 0, -40, -40, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }, + { // knight (white) + -10, -10, -10, -10, -10, -10, -10, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 5, 5, 5, 5, 0, -10, + -10, 0, 5, 10, 10, 5, 0, -10, + -10, 0, 5, 10, 10, 5, 0, -10, + -10, 0, 5, 5, 5, 5, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, -30, -10, -10, -10, -10, -30, -10 }, + { // bishop (white) + -10, -10, -10, -10, -10, -10, -10, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 5, 5, 5, 5, 0, -10, + -10, 0, 5, 10, 10, 5, 0, -10, + -10, 0, 5, 10, 10, 5, 0, -10, + -10, 0, 5, 5, 5, 5, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, -10, -20, -10, -10, -20, -10, -10 }, + { // rook (white) + 0, 0, 0, 0, 0, 0, 0, 0, + 20, 20, 20, 20, 20, 20, 20, 20, // rook on seventh bonus + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }, + { // queen (white) + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }, + { // king middle game (white) + -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, + -20, -20, -20, -20, -20, -20, -20, -20, + 0, 20, 40, -20, 0, -20, 40, 20 } +}; + +#endif /* INCLUDE_GUARD_TYPES_H */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/uci.cpp b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/uci.cpp new file mode 100644 index 0000000..c13505c --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/uci.cpp @@ -0,0 +1,964 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "Move.h" +#include "Board.h" +#include "uci.h" +#include "search.h" +#if __cplusplus > 201703L + #include + using osyncstream = std::osyncstream; +#else + #include "syncstream.h" +#endif + +// Inject into std namespace +namespace std { + ostream& operator<<(ostream& out, const Move& move) { + if (!move) { + out << "!move"; + return out; + } + + if (UCI::writeLAN) { + switch (move.piece()) { + case pawn: break; + case knight: out << 'N'; break; + case bishop: out << 'B'; break; + case rook: out << 'R'; break; + case queen: out << 'Q'; break; + case king: out << 'K'; break; + default: out << '?'; + } + } + + out << static_cast('a' + (move.from() % 8)) + << static_cast('8' - (move.from() / 8)); + + if (UCI::writeLAN) { + switch (move.victim()) { + case pawn: out << 'x'; break; + case knight: out << "xN"; break; + case bishop: out << "xB"; break; + case rook: out << "xR"; break; + case queen: out << "xQ"; break; + default: out << '-'; + } + } + + out << static_cast('a' + (move.to() % 8)) + << static_cast('8' - (move.to() / 8)); + + if (UCI::writeLAN) { + switch (move.promote()) { + case rook: out << 'R'; break; + case knight: out << 'N'; break; + case bishop: out << 'B'; break; + case queen: out << 'Q'; break; + default: break; + } + } else { + switch (move.promote()) { + case rook: out << 'r'; break; + case knight: out << 'n'; break; + case bishop: out << 'b'; break; + case queen: out << 'q'; break; + default: break; + } + } + + return out; + } + + ostream& operator<<(ostream& out, const MoveList& moveList) { + if (moveList.size()) { + out << moveList[0]; + } + + for (unsigned i = 1; i < moveList.size(); i++) { + out << ' ' << moveList[i]; + } + + return out; + } +} + + +namespace UCI { + +// General UCI protocol configuration +bool readSAN = false; // if moves should be read in SAN +bool writeLAN = false; // moves written in Long Algebraic Notation + +Index parseSquare(const std::string& str, bool& parseError) { + if (str.length() != 2) { + parseError = true; + return static_cast(64); + } + if (str[0] < 'a' || 'h' < str[0] || str[1] < '1' || '8' < str[1]) { + parseError = true; + return static_cast(64); + } + + Index fileIndex = static_cast(str[0] - 'a'); + Index rankIndex = static_cast('8' - str[1]); + + return 8 * rankIndex + fileIndex; +} + +Move parseMove(const std::string& str, bool& parseError) { + // check length = 4 plus 1 if promotion + if ((str.length() < 4) || (5 < str.length())) { + parseError = true; + return Move(0); + } + + // parse squares + Index fromSq = parseSquare(str.substr(0, 2), parseError); + if (parseError) { return Move(0); } + Index toSq = parseSquare(str.substr(2, 2), parseError); + if (parseError) { return Move(0); } + + // handle promotions + if (str.length() == 5) { + switch (str[4]) { + case 'r': return Move(fromSq, toSq, rook); + case 'n': return Move(fromSq, toSq, knight); + case 'b': return Move(fromSq, toSq, bishop); + case 'q': return Move(fromSq, toSq, queen); + default: parseError = true; break; + } + } + + return Move(fromSq, toSq); +} + +const std::string formatMove(const Move move) { + // redirect parsing to `<<` overload + std::ostringstream out; + out << move; + // return as a string + return out.str(); +} + +// ::= [||]['x'] +// ::= [] 'x' [] +// ::= [] +Move parseSAN(const std::string& str, const Board& pos, bool& parseError) { + // check length, at least 2 characters for pawn pushes and max 6 plus 2 + // additional characters as move annotations (for example '!?', '+' or '#'). + if ((str.length() < 2) || (9 < str.length())) { + parseError = true; + return Move(0); + } + + // Start with extracting the piece type. + // The first letter (strictly speaking upper case) gives the moving + // piece type or a pawn if not given. + enum piece color = pos.isWhiteTurn() ? white : black; + enum piece piece = pawn; + switch(str[0]) { + // Piece moves (all normal piece moves) + case 'R': piece = rook; break; + case 'N': piece = knight; break; + case 'B': piece = bishop; break; + case 'Q': piece = queen; break; + case 'K': piece = king; break; + // Castling + case 'O': case '0': + if (startsWith(str, "O-O-O") || startsWith(str, "0-0-0")) { + if (color == white) { + return Move(e1, c1); + } else { + return Move(e8, c8); + } + } else if (startsWith(str, "O-O") || startsWith(str, "0-0")) { + if (color == white) { + return Move(e1, g1); + } else { + return Move(e8, g8); + } + } else { + parseError = true; + return Move(0); + } + break; + // Pawn moves always begin with from/to file + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + break; + default: + parseError = true; + return Move(0); + } + + // Normalize moves. + // Drop annotation symbols '?', '+', ... as well as '=', '/' for promotions. + // Thats quite generes but as long as the reminder leads to pseudo legal + // moves, I'm happy. + char part[6]; + int part_length = 0; + for (char c : str) { + switch (c) { + case '+': case '#': case '!': case '?': case '=': case '/': + continue; + default: + part[part_length++] = c; + } + if (part_length >= 6) { + break; + } + } + + if (piece == pawn) { + // Pawn push + if ((part_length == 2) || (part_length == 3)) { + Index toSq = parseSquare(std::string{part[0], part[1]}, parseError); + if (parseError) { return Move(0); } + u64 from; + if (color == white) { + from = fill7dump(bitMask(toSq), ~pos.bb(pawn)); + from &= pos.bb(white); + } else { + from = fill7dump(bitMask(toSq), ~pos.bb(pawn)); + from &= pos.bb(black); + } + if (bitCount(from) != 1) { + parseError = true; + return Move(0); + } + Index fromSq = bitScanLS(from); + if (part_length == 3) { + switch(toLower(part[2])) { + case 'q': return Move(fromSq, toSq, queen); + case 'r': return Move(fromSq, toSq, rook); + case 'b': return Move(fromSq, toSq, bishop); + case 'n': return Move(fromSq, toSq, knight); + default: + parseError = true; + return Move(0); + } + } + return Move(fromSq, toSq); + // Pawn Capture + } else if ((part_length == 4) || (part_length == 5)) { + if (part[1] != 'x') { + parseError = true; + return Move(0); + } + Index toSq = parseSquare(std::string{part[2], part[3]}, parseError); + if (parseError) { return Move(0); } + u64 from; + if (('a' <= part[0]) && (part[0] <= 'h')) { + from = bitMask(static_cast(str[0] - 'a')); + } else { + parseError = true; + return Move(0); + } + if (color == white) { + from &= shift(bitMask(toSq)); + from &= pos.bb(white) & pos.bb(pawn); + } else { + from &= shift(bitMask(toSq)); + from &= pos.bb(black) & pos.bb(pawn); + } + if (bitCount(from) != 1) { + parseError = true; + return Move(0); + } + Index fromSq = bitScanLS(from); + if (part_length == 5) { + switch(toLower(part[4])) { + case 'q': return Move(fromSq, toSq, queen); + case 'r': return Move(fromSq, toSq, rook); + case 'b': return Move(fromSq, toSq, bishop); + case 'n': return Move(fromSq, toSq, knight); + default: + parseError = true; + return Move(0); + } + } + return Move(fromSq, toSq); + } + // Illegal length, ether 2, 3 for pawn push or 4, 5 for pawn captures + parseError = true; + return Move(0); + } + + // Only normal piece moves remain (castling and pawn moves handled above) + if ((part_length < 3) || (6 < part_length)) { + parseError = true; + return Move(0); + } + Index toSq = parseSquare( + std::string{part[part_length - 2], part[part_length - 1]}, parseError); + if (parseError) { return Move(0); } + u64 to = bitMask(toSq); + u64 from = pos.bb(color) & pos.bb(piece); + u64 empty = ~(pos.bb(white) | pos.bb(black)); + switch(piece) { + case queen: + from &= fill7dump (to, empty) + | fill7dump (to, empty) + | fill7dump (to, empty) + | fill7dump (to, empty) + | fill7dump (to, empty) + | fill7dump (to, empty) + | fill7dump (to, empty) + | fill7dump(to, empty); + break; + case rook: + from &= fill7dump (to, empty) + | fill7dump(to, empty) + | fill7dump (to, empty) + | fill7dump (to, empty); + break; + case bishop: + from &= fill7dump (to, empty) + | fill7dump (to, empty) + | fill7dump (to, empty) + | fill7dump(to, empty); + break; + case knight: + from &= pos.knightMoves(toSq); + break; + case king: + from &= pos.kingMoves(toSq); + break; + default: + assert(false && "Unreachable!"); + } + // Capture Move + if (part[part_length - 3] == 'x') { + Index fromSq; + switch (part_length) { + case 4: + break; + case 5: + if (('1' <= part[1]) && (part[1] <= '8')) { + from &= bitMask(8 * static_cast('8' - part[1])); + } else if (('a' <= part[1]) && (part[1] <= 'h')) { + from &= bitMask(static_cast(part[1] - 'a')); + } else { + parseError = true; + return Move(0); + } + break; + case 6: + fromSq = parseSquare(std::string{part[1], part[2]}, parseError); + if (parseError) { return Move(0); } + from &= bitMask(fromSq); + break; + default: + parseError = true; + return Move(0); + } + // Non Capture / Quiet Move + } else { + Index fromSq; + switch (part_length) { + case 3: + break; + case 4: + if (('1' <= part[1]) && (part[1] <= '8')) { + from &= bitMask(8 * static_cast('8' - part[1])); + } else if (('a' <= part[1]) && (part[1] <= 'h')) { + from &= bitMask(static_cast(part[1] - 'a')); + } else { + parseError = true; + return Move(0); + } + break; + case 5: + fromSq = parseSquare(std::string{part[1], part[2]}, parseError); + if (parseError) { return Move(0); } + from &= bitMask(fromSq); + break; + default: + parseError = true; + return Move(0); + } + } + if (bitCount(from) != 1) { + // If there is still an umbiguity, some candidates might be pinned. + // Remove pinned candidates unable to reach the target square. + u64 cKing = pos.bb(color) & pos.bb(king); + Index cKingSq = bitScanLS(cKing); + u64 pinMask = pos.pinned((color == white) ? black : white, cKing, empty); + for (u64 can = from; can; can &= can - 1) { // can ... candidates + Index canSq = bitScanLS(can); + // check if current candidate is pinned + if ((can & -can) & pinMask) { + // validate if tha candidate is able to reach the target square + if (!(lineMask(cKingSq, canSq) & to)) { + // Remove candidate + from ^= can & -can; + } + } + } + // If there are still multiple candidates or none -> parse error + if (bitCount(from) != 1) { + parseError = true; + return Move(0); + } + } + // Finaly, create the move + return Move(bitScanLS(from), toSq); +} + +void parseMoves(std::istream& istream, const Board& pos, MoveList& moveList, + bool& parseError +) { + // setup movelist of all legal moves to validate move lagality + MoveList legalMoves; + pos.moves(legalMoves); + // holds input tokens to be parsed as moves + std::string word; + istream >> std::skipws; + // For each input token + while (istream >> word) { + // parse token as a move (depending on the move format) + Move move; + if (readSAN) { + move = parseSAN(word, pos, parseError); + } else { + move = parseMove(word, parseError); + } + // stop in case of a parse error + if (parseError) { return; } + // validate legality of the (successfully parsed) move and add the + // generated (not the parsed move) to the move list containing all the + // additional move information + bool isLegal = false; + for (Move legal : legalMoves) { + if ((move.from() == legal.from()) + && (move.to() == legal.to()) + && (move.promote() == legal.promote())) + { + isLegal = true; + moveList.push_back(legal); + break; + } + } + // if the parse move was not found in the legal moves list stop and + // report a parse error + if (!isLegal) { + parseError = true; + return; + } + } +} + + +char formatPiece(enum piece piece) { + switch (piece) { + case rook: return 'R'; + case knight: return 'N'; + case bishop: return 'B'; + case queen: return 'Q'; + case king: return 'K'; + case pawn: return 'P'; + default: return '?'; + } +} + +void printBoard(const Board& board) { + osyncstream out(cout); + + const char* rankNames = "87654321"; + const char* fileNames = "abcdefgh"; + + for (Index line = 0; line < 17; line++) { + if (line % 2) { + Index rankIndex = line / 2; + out << rankNames[rankIndex]; + for (Index fileIndex = 0; fileIndex < 8; fileIndex++) { + Index squareIndex = 8 * rankIndex + fileIndex; + if (board.color(squareIndex) == black) { + out << " | \033[1m\033[94m" + << formatPiece(board.piece(squareIndex)) << "\033[0m"; + } else if (board.color(squareIndex) == white) { + out << " | \033[1m\033[97m" + << formatPiece(board.piece(squareIndex)) << "\033[0m"; + } else { + out << " | "; + } + } + out << " |"; + } else { + out << " +---+---+---+---+---+---+---+---+"; + } + out << " "; + + switch (line) { + case 1: + out << "How's turn: " + << (board.isWhiteTurn() ? "white" : "black"); + break; + case 2: + out << "Move Count: " << ((board.plyCount() + 1U) / 2U); + break; + case 3: + out << "Half move clock: " << board.halfMoveClock(); + break; + case 4: + out << "Castling Rights: "; + if (board.castleRight(white, KingSide)) { out << 'K'; }; + if (board.castleRight(white, QueenSide)) { out << 'Q'; }; + if (board.castleRight(black, KingSide)) { out << 'k'; }; + if (board.castleRight(black, QueenSide)) { out << 'q'; }; + break; + case 5: + out << "en-passange target: "; + if (board.enPassant() < 64) { + out << static_cast('a' + (board.enPassant() % 8)) + << static_cast('8' - (board.enPassant() / 8)); + } + else { out << "-"; }; + break; + case 6: + out << "evaluate: " << std::dec << board.evaluate(); + break; + case 7: + out << "hash: " << std::hex << board.hash() << std::dec; + break; + default: + break; + } + out << "\033[0K\n"; // clear rest of line (remove potential leftovers) + } + out << " "; + for (Index fileIndex = 0; fileIndex < 8; fileIndex++) { + out << " " << fileNames[fileIndex]; + } + out << std::endl; +} +void printBitBoards(const Board& board) { + osyncstream out(cout); + + std::string lRanks(" 8\n 7\n 6\n 5\n 4\n 3\n 2\n 1"); + std::string rRanks("8\n7\n6\n5\n4\n3\n2\n1"); + + out << std::endl << " " + << std::left << std::setw(18) << "white" + << std::left << std::setw(18) << "black" + << std::left << std::setw(18) << "pawns" + << std::left << std::setw(18) << "kings" + << std::endl << " " << std::setfill('0') << std::hex + << std::right << std::setw(16) << board.bb(white) << " " + << std::right << std::setw(16) << board.bb(black) << " " + << std::right << std::setw(16) << board.bb(pawn) << " " + << std::right << std::setw(16) << board.bb(king) << " " + << std::left << std::setfill(' ') << std::dec << std::endl + << aside( + lRanks, + rbits(board.bb(white), '\n', ' '), + rbits(board.bb(black), '\n', ' '), + rbits(board.bb(pawn), '\n', ' '), + rbits(board.bb(king), '\n', ' '), + rRanks + ) << std::endl + << " a b c d e f g h a b c d e f g h" + << " a b c d e f g h a b c d e f g h" << std::endl; + out << std::endl << " " + << std::left << std::setw(18) << "rooks" + << std::left << std::setw(18) << "knights" + << std::left << std::setw(18) << "bishops" + << std::left << std::setw(18) << "queens" + << std::endl << " " << std::setfill('0') << std::hex + << std::right << std::setw(16) << board.bb(rook) << " " + << std::right << std::setw(16) << board.bb(knight) << " " + << std::right << std::setw(16) << board.bb(bishop) << " " + << std::right << std::setw(16) << board.bb(queen) << " " + << std::left << std::setfill(' ') << std::dec << std::endl + << aside( + lRanks, + rbits(board.bb(rook), '\n', ' '), + rbits(board.bb(knight), '\n', ' '), + rbits(board.bb(bishop), '\n', ' '), + rbits(board.bb(queen), '\n', ' '), + rRanks + ) << std::endl + << " a b c d e f g h a b c d e f g h" + << " a b c d e f g h a b c d e f g h" << std::endl; + + const enum piece color = board.isWhiteTurn() ? white : black; + const enum piece enemy = (color == white) ? black : white; + const u64 empty = ~(board.bb(white) | board.bb(black)); + const u64 cKing = board.bb(color) & board.bb(king); + + out << std::endl << " " + << std::left << std::setw(18) << "attacks" + << std::left << std::setw(18) << "pinned" + << std::left << std::setw(18) << "checkers" + << std::endl + << aside( + lRanks, + rbits(board.attacks(enemy, empty), '\n', ' '), + rbits(board.pinned(enemy, cKing, empty), '\n', ' '), + rbits(board.checkers(enemy, cKing, empty), '\n', ' '), + rRanks + ) << std::endl + << " a b c d e f g h a b c d e f g h" + << " a b c d e f g h" << std::endl; +} + +void printEval(const Board& board) { + osyncstream out(cout); + + // Partial Scores + Score pScoreWhite, pScoreBlack; + + out << " | White | Black | Total\n" + << "-------------+---------+---------+---------\n" + << " Material | "; + pScoreWhite = board.evalMaterial(white); + pScoreBlack = board.evalMaterial(black); + out << std::setw(7) << std::right << pScoreWhite << " | " + << std::setw(7) << std::right << pScoreBlack << " | " + << std::setw(7) << std::right << pScoreWhite - pScoreBlack << "\n" + << " Pawns | "; + pScoreWhite = board.evalPawns(white); + pScoreBlack = board.evalPawns(black); + out << std::setw(7) << std::right << pScoreWhite << " | " + << std::setw(7) << std::right << pScoreBlack << " | " + << std::setw(7) << std::right << pScoreWhite - pScoreBlack << "\n" + << " King Safety | "; + pScoreWhite = board.evalKingSafety(white); + pScoreBlack = board.evalKingSafety(black); + out << std::setw(7) << std::right << pScoreWhite << " | " + << std::setw(7) << std::right << pScoreBlack << " | " + << std::setw(7) << std::right << pScoreWhite - pScoreBlack << "\n" + << " Rooks | "; + pScoreWhite = board.evalRooks(white); + pScoreBlack = board.evalRooks(black); + out << std::setw(7) << std::right << pScoreWhite << " | " + << std::setw(7) << std::right << pScoreBlack << " | " + << std::setw(7) << std::right << pScoreWhite - pScoreBlack << "\n"; + + out << "\nTotal: " << board.evaluate() << std::endl; +} + +void printMoves(const Board& board, bool capturesOnly) { + MoveList moveList; + board.moves(moveList, capturesOnly); + for (Move& move : moveList) { + move.setScore(move.calcScore()); + } + std::sort(moveList.begin(), moveList.end()); + osyncstream out(cout); + if (capturesOnly) { + out << "info string captures"; + } else { + out << "info string moves"; + } + for (Move& move : moveList) { + out << ' ' << move; + } + out << std::endl; +} + + +// position [fen | startpos ] moves .... +// \______________________ remainder in cmd ______________________/ +void position(std::vector& game, std::istream& cmd, bool& parseError) { + std::string word; + + // setup a new game in case of a parse error (no changes in case of an error) + std::vector newGame; + + // Setup position (and consume moves cmd) + Board pos; + cmd >> word; + if (word == "startpos") { + // Consume and check "moves" word + if (cmd >> word && word != "moves") { + parseError = true; + return; + } + // set position to start position + pos = Board(); + newGame.push_back(pos); + } else if (word == "fen") { + std::string fen; + while (cmd >> word && word != "moves") { + fen += word + " "; + } + pos.init(fen, parseError); + if (parseError) { return; } + newGame.push_back(pos); + } else if (word == "this") { // (UCI extention) + if (game.empty()) { + parseError = true; + return; + } + pos = game.back(); + // Consume and check "moves" word + if (cmd >> word && word != "moves") { + parseError = true; + return; + } + // keep the game + newGame = game; + } else { + parseError = true; + return; + } + + // Apply (legal) moves (if any) and append new positions + while (cmd >> word) { + Move move; + if (readSAN) { + move = UCI::parseSAN(word, pos, parseError); + } else { + move = UCI::parseMove(word, parseError); + } + // validate legality (and extend the move with piece, victim, ... info) + move = pos.isLegal(move); // validate move legality and extend move info + if (parseError || !move) { + parseError = true; + return; + } + pos.make(move); + newGame.push_back(pos); + } + + // Finaly replace game with new game (no errors occured) + game = newGame; +} + +void setoption(std::istream& cmd, bool& parseError) { + std::string word; + + // Consume "name" + if ((!(cmd >> word)) || (word != "name")) { + parseError = true; + return; + } + + // read option id + std::string id = ""; + while ((cmd >> word) && (word != "value")) { + id += word; + } + + // set option value + if (id == "Hash") { + Index ttSize = 0; + cmd >> ttSize; + if ((0 < ttSize) && (ttSize < 1025)) { + Search::init(ttSize); + } else { + parseError = true; + } + } else if (id == "readSAN") { + cmd >> word; + if (word == "true") { + readSAN = true; + } else if (word == "false") { + readSAN = false; + } else { + parseError = true; + } + } else if (id == "writeLAN") { + cmd >> word; + if (word == "true") { + writeLAN = true; + } else if (word == "false") { + writeLAN = false; + } else { + parseError = true; + } + } else { + parseError = true; + } +} + +// go +// with ; +// ... +// TODO: implement +// +void go(std::vector& game, std::istream& cmd, bool& parseError) { + std::string word; + Search::State config(Search::state); + + std::string IGNORED; // TODO: complete UCI implementation + + while ((cmd >> word) && !parseError) { + if (word == "perft") { + config.search = Search::State::Type::perft; + if (!(cmd >> config.depth)) { + parseError = true; + } + } + else if (word == "searchmoves") { + parseMoves(cmd, game.back(), config.searchmoves, parseError); + } + else if (word == "depth") { cmd >> config.depth; } + else if (word == "wtime") { cmd >> config.wtime; } + else if (word == "btime") { cmd >> config.btime; } + + else if (word == "winc") { cmd >> config.winc; } + else if (word == "binc") { cmd >> config.binc; } + else if (word == "movestogo") { cmd >> IGNORED; } + else if (word == "nodes") { cmd >> IGNORED; } + else if (word == "mate") { cmd >> IGNORED; } + else if (word == "movetime") { cmd >> config.movetime; } + else if (word == "ponder") { cmd >> IGNORED; } + else if (word == "infinite") { + config.depth = MoveList::max_size(); // max PV line length + config.wtime = 24 * 60 * 60 * 1000; // 1 day in milliseconds + config.btime = 24 * 60 * 60 * 1000; // 1 day in milliseconds + } + else { + parseError = true; + } + } + if (parseError) { + return; + } + + // Write new global settings to Search state + Search::state.wtime = config.wtime; + Search::state.btime = config.btime; + Search::state.winc = config.winc; + Search::state.binc = config.binc; + + Search::start(game, config); +} + +void print(std::vector& game, std::istream& cmd, bool& parseError) { + Board& board = game.back(); + std::string word; + + // Get print command (or set default) + if (!(cmd >> word)) { + word = "board"; + } + + if (word == "board") { printBoard(board); } + else if (word == "game") { for (Board& pos : game) { printBoard(pos); } } + else if (word == "moves") { printMoves(board); } + else if (word == "eval") { printEval(board); } + else if (word == "captures") { printMoves(board, true); } + else if (word == "bits") { printBitBoards(board); } + else if (word == "fen") { + osyncstream(cout) << "info string fen " + << board.fen() << std::endl; } + else { parseError = true; } +} + +void stdin_listen(std::vector& game) { + // read line by line from stdin and dispatch appropriate command handler + std::string line; + while (getline(std::cin, line)) { + // a game consists of at least one position + assert(game.size()); + + std::istringstream cmd(line); + std::string cmdName; + + // Extract command name (skip white spaces) + cmd >> std::skipws >> cmdName; + + // Dispatch commands (or handle directly) + bool parseError = false; // tracks comand argument parse status + if (cmdName == "quit") { Search::stop(); break; } // stop stdin loop -> shutdown + else if (cmdName == "exit") { Search::stop(); break; } // quit alias + else if (cmdName == "stop") { Search::stop(); } + else if (cmdName == "ucinewgame") { Search::newgame(); } + else if (cmdName == "uci") { + osyncstream(cout) + << "id name Schach Hoernchen" + "\nid author Daniel Kapla" + "\noption name Hash type spin default 32 min 1 max 1024" + "\noption name readSAN type check default false" + "\noption name writeLAN type check default false" + "\nuciok" // Ready (there are no options yet) + << std::endl; + } + else if (cmdName == "isready") { cout << "readyok" << std::endl; } + else if (cmdName == "setoption") { setoption(cmd, parseError); } + else if (cmdName == "go") { go(game, cmd, parseError); } + else if (cmdName == "position") { position(game, cmd, parseError); } + // UCI Extention (not part of the UCI protocol) + else if (cmdName == "d") { print(game, cmd, parseError); } // print alias (as in stockfish) + else if (cmdName == "print") { print(game, cmd, parseError); } + else if (cmdName == "getoptions") { + osyncstream(cout) + << "option name Hash value " << std::setprecision(2) + << Search::ttSize() << " MB" + << "\noption name readSAN value " << std::boolalpha << readSAN + << "\noption name writeLAN value " << std::boolalpha << writeLAN + << std::endl; + } + else if (cmdName == "help" || cmdName == "?") { + osyncstream(std::cerr) + << "Commands:\n" + " uci\n\033[2m" + " responds with self identification/options and finishes " + "with uciok\033[0m\n" + " ucinewgame\n\033[2m" + " starts a new game; resets all internal game states\033[0m\n" + " isready\n\033[2m" + " Should be responding 'readyok' immediately\033[0m\n" + " position\n" + " position startpos [moves [ ...]]\n" + " position fen [moves [ ...]]\n" + " position this moves [ ...]\n" + " go\n" + " go perft \n" + " go [depth ] [searchmoves [ ...]]\n" + " stop\n\033[2m" + " Stops what ever the engine is doing right now as " + "soon as possible\033[0m\n" + " print\n" + " d\n" + " print [board]\033[2m\n" + " Prity prints the board with state information\033[0m\n" + " print game\033[2m\n" + " Prity prints entire game history\033[0m\n" + " print moves\033[2m\n" + " Gives a list of all legal moves sorted by move ordering " + "heuristic\033[0m\n" + " print captures\033[2m\n" + " Same as print moves but captures only\033[0m\n" + " print bits\033[2m\n" + " Prints the internal bit-boards plus attacks, pinnned and " + "checkers bit-boards\033[0m\n" + " print fen\033[2m\n" + " Print FEN string of current internal state\033[0m\n" + " quit\n" + " exit\033[2m\n" + " Shutdown the engine as soon as possible\033[0m\n" + " setoption\n" + " setoption name Hash value \n" + " setoption name readSAN value [true | false]\n" + " setoption name writeLAN value [true | false]\n" + " getoptions\n\033[2m" + " Prints set values of all options\033[0m\n" + << std::endl; // TODO: complete help!!! + } else { + osyncstream(std::cerr) + << "info string error Unknown command!" << std::endl; + } + + if (parseError) { + osyncstream(std::cerr) + << "info string error Missformed or illegal command!" + << std::endl; + } + } + + osyncstream(cout) << "info string shutdown" << std::endl; +} + +} /* namespace UCI */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/uci.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/uci.h new file mode 100644 index 0000000..0ca56ef --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/uci.h @@ -0,0 +1,103 @@ +#ifndef UCI_GUARD_MOVE_H +#define UCI_GUARD_MOVE_H + +#include +#include + +#include "types.h" + +// Forward declarations +class Board; +class Move; +class MoveList; + +namespace std { + ostream& operator<<(ostream& out, const Move& move); + ostream& operator<<(ostream& out, const MoveList& moveList); +} + +/** + * UCI ... Universal Chess Interface + * + * Implements the UCI interface for interaction between the internal engine + * and an GUI using the UCI standard. + */ +namespace UCI { + +// General UCI protocol configuration +extern bool readSAN; // if moves should be read in SAN +extern bool writeLAN; // moves written in Long Algebraic Notation + +/** + * Parses square in algebraic notation as internal square index. + * + * @param str string representation of a square /[a-h][1-8]/. + * @param parseError output variable set to true if illegal square + * representation is encountered (aka. parse error occured). + * + * @returns index between 0 and 63 if legal, 64 otherwise. + */ +Index parseSquare(const std::string& str, bool& parseError); + +/** + * Parses a move given in pure coordinate notation + * + * @param str string representation of a move in `[]` format + * @param parseError output variable set to false if successfully parsed, true + * otherwise. + */ +Move parseMove(const std::string& str, bool& parseError); + +/** + * Formats a move in coordinate notation (Unary Function equiv to the pipe + * operator for Move) + * + * @param move move to be formated as a string + * + * @return string representation of the move in coordinate notation + */ +const std::string formatMove(const Move move); + +/** + * Parses a move given in standard algebraic notation (SAN) + * + * @param str string representation of a move in SAN format + * @param pos current position, needed for move interpretation + * @param parseError output variable set to false if successfully parsed, true + * otherwise. + */ +Move parseSAN(const std::string& str, const Board& pos, bool& parseError); + +/** + * Parses multiple moves from an input stream into a `MoveList` + */ +void parseMoves(std::istream&, MoveList&, bool&); + +char formatPiece(enum piece piece); +void printBoard(const Board&); +void printMoves(const Board&, bool = false); +void printBitBoards(const Board&); + +/** + * Handles `position ...` command by setting given board (including moves). + * + * @param board internal board representation to be manipulated. + * @param cmd argument `...` of the position command. For example; given the + * command `position startpos moves e2e4`, str should contain `startpos moves e2e4`. + * @param parseError output, true if a parse error occured, false otherwise. + */ +void position(std::vector&, std::istream&, bool&); + +/** + * handles `go ...` commands. Starts searches, performance tests, ... + */ +void go(std::vector&, std::istream&, bool&); + +/** + * Processes/Parses input from stdin and dispatches appropriate command handler. + */ +void stdin_listen(std::vector& game); + +} /* namespace UCI */ + +#endif /* UCI_GUARD_MOVE_H */ diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/utils.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/utils.h new file mode 100644 index 0000000..259dbfe --- /dev/null +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/utils.h @@ -0,0 +1,698 @@ +#ifndef INCLUDE_GUARD_UTILS_H +#define INCLUDE_GUARD_UTILS_H + +#include +#include // for strto* (instead of std::strto*) +#include +#include +#include +#include +#include +#include + +#include "types.h" + +// Helpfull in debugging +template +inline std::string rbits(T mask, char sepByte = 0, char sep = 0, + Index mark = -1, char marker = 'X' +) { + std::ostringstream out; + + for (unsigned i = 0; i < sizeof(T); i++) { + out << sep; + for (unsigned j = 0; j < 8; j++) { + Index index = 8 * i + j; + if (index == mark) { + out << marker; + } else { + out << ((static_cast(1) << index) & mask ? '1' : '.'); + } + if (sep) out << sep; + } + if (sepByte && (i < (sizeof(T) - 1))) out << sepByte; + } + + return out.str(); +} + +template +inline std::string bits(T mask, char sepByte = 0, char sep = 0, + Index mark = -1, char marker = 'X' +) { + std::string str(rbits(mask, sepByte, sep, mark, marker)); + return std::string(str.rbegin(), str.rend()); +} + +inline std::vector split(const std::string& str, char delim = ' ') { + std::vector parts; + std::istringstream iss(str); + std::string part; + while (std::getline(iss, part, delim)) { + if (!part.empty()) { + parts.push_back(part); + } + } + return parts; +} +inline std::vector parse(const std::string& str, const std::string& format) { + std::vector parts; + std::string part; + unsigned start = 0, end = 0, len = 0; + for (const std::string& delim : split(format, '*')) { + while (end + delim.length() < str.length()) { + for (len = 0; len < delim.length(); len++) { + if (str[end + len] != delim[len]) { + break; + } + } + if (len == delim.length()) { + parts.push_back(str.substr(start, end - start)); + end += len; + start = end; + break; + } else { + end++; + } + } + } + parts.push_back(str.substr(end)); + return parts; +} + +inline std::string aside(std::string lhs, std::string rhs) { + std::ostringstream out; + + std::vector lLines = split(lhs, '\n'); + std::vector rLines = split(rhs, '\n'); + + std::size_t lMax = 0; + for (std::size_t i = 0; i < lLines.size(); i++) { + lMax = std::max(lMax, lLines[i].length()); + } + + for (std::size_t i = 0, n = std::min(lLines.size(), rLines.size()); i < n; i++) { + out << lLines[i] + << std::string(lMax - lLines[i].length() + 1, ' ') + << rLines[i] << '\n'; + } + + if (lLines.size() < rLines.size()) { + for (std::size_t i = lLines.size(); i < rLines.size(); i++) { + out << std::string(lMax + 1, ' ') + << rLines[i] << '\n'; + } + } else { + for (std::size_t i = rLines.size(); i < lLines.size(); i++) { + out << lLines[i] << '\n'; + } + } + + return out.str(); +} + +template +inline std::string aside(std::string col1, std::string col2, Args... cols) { + return aside( + col1, + aside(col2, cols...) + ); +} + + +inline void fourBitBoards( + const std::string& title1, const u64 bb1, + const std::string& title2, const u64 bb2, + const std::string& title3, const u64 bb3, + const std::string& title4, const u64 bb4, + Index mark = -1, char marker = 'X' +) { + std::string lRanks(" 8\n 7\n 6\n 5\n 4\n 3\n 2\n 1"); + std::string rRanks("8\n7\n6\n5\n4\n3\n2\n1"); + + cout << std::endl << " " + << std::left << std::setw(18) << title1 + << std::left << std::setw(18) << title2 + << std::left << std::setw(18) << title3 + << std::left << std::setw(18) << title4 + << std::endl + << aside( + lRanks, + rbits(bb1, '\n', ' ', mark, marker), + rbits(bb2, '\n', ' ', mark, marker), + rbits(bb3, '\n', ' ', mark, marker), + rbits(bb4, '\n', ' ', mark, marker), + rRanks + ) << std::endl + << " a b c d e f g h a b c d e f g h" + << " a b c d e f g h a b c d e f g h" << std::endl; +} + +inline char toLower(const char c) { + if ('A' <= c && c <= 'Z') { + return c + ' '; + } + return c; +} +inline char toUpper(const char c) { + if ('a' <= c && c <= 'z') { + return c - ' '; + } + return c; +} +inline bool isUpper(const char c) { + return ('A' <= c && c <= 'Z'); +} +inline bool isLower(const char c) { + return ('a' <= c && c <= 'z'); +} +inline bool startsWith(const std::string& str, const std::string& prefix) { + if (str.length() < prefix.length()) { + return false; + } + for (unsigned i = 0; i < prefix.length(); i++) { + if (str[i] != prefix[i]) { + return false; + } + } + return true; +} + +// see: https://stackoverflow.com/questions/194465/how-to-parse-a-string-to-an-int-in-c +// see: https://en.cppreference.com/w/cpp/string/byte/strtol +// Note: T must be a unsigned type +// Only applicable for types smaller/equal to unsigned long long. +// (only used for uint8_t, uint16_t, uint32_t and uint64_t) +template +T parseUnsigned(const std::string& str, bool& parseError) { + if (str.empty()) { + parseError = true; + return static_cast(-1); + } + + char* end; + errno = 0; + unsigned long long num = strtoull(str.c_str(), &end, 10); + + if (errno == ERANGE) { + errno = 0; + parseError = true; + return static_cast(-1); + } + if (*end != '\0') { + parseError = true; + return static_cast(-1); + } + // check overflow (using complement trick for unsigned integer types) + if (num > static_cast(static_cast(-1))) { + parseError = true; + return static_cast(-1); + } + + return static_cast(num); +} + +template +constexpr u64 bitMask(); + +template +constexpr u64 bitMask(Index); + +template +inline constexpr u64 bitMask(Index file, Index rank) { + return bitMask(8 * rank + file); +} + +template <> +inline constexpr u64 bitMask(Index sq) { + return static_cast(1) << sq; +} + +template <> +inline constexpr u64 bitMask(Index sq) { + return static_cast(0x0101010101010101) << (sq & 7); +} +template <> +inline constexpr u64 bitMask(Index sq) { + return static_cast(0xFF) << (sq & 56); +} + +template <> +inline constexpr u64 bitMask(Index sq) { + return bitMask(sq) & (bitMask(sq) - 1); +} +template <> +inline constexpr u64 bitMask(Index sq) { + return bitMask(sq) & (static_cast(-2) << sq); +} +template <> +inline constexpr u64 bitMask(Index sq) { + return bitMask(sq) & (bitMask(sq) - 1); +} +template <> +inline constexpr u64 bitMask(Index sq) { + return bitMask(sq) & (static_cast(-2) << sq); +} + +template <> +inline constexpr u64 bitMask(Index sq) { + const u64 diag = static_cast(0x8040201008040201); + int offset = 8 * static_cast(sq & 7) - static_cast(sq & 56); + int nort = -offset & ( offset >> 31); + int sout = offset & (-offset >> 31); + return (diag >> sout) << nort; +} +template <> +inline constexpr u64 bitMask(Index sq) { + const u64 diag = static_cast(0x0102040810204080); + int offset = 56 - 8 * static_cast(sq & 7) - static_cast(sq & 56); + int nort = -offset & ( offset >> 31); + int sout = offset & (-offset >> 31); + return (diag >> sout) << nort; +} + +template <> +inline constexpr u64 bitMask() { + return 0x55AA55AA55AA55AA; +} +template <> +inline constexpr u64 bitMask() { + return 0xAA55AA55AA55AA55; +} + +/** + * Shifts with respect to "off board" shifting + * + * bits shift(bits) shift(bits) + * 8 . . . 1 . . . . . . 1 . . . . . . . . . . . . . 8 + * 7 . . . . . . . . . . . . . . . . . . . . . . . . 7 + * 6 . . . . . . . . . . . . . . . . 1 . . . . 1 . . 6 + * 5 1 . . . . 1 . . . . . . 1 . . . . . . . . . . . 5 + * 4 . . . . . . . . . . . . . . . . . . 1 . . . . . 4 + * 3 . . 1 . . . . . . 1 . . . . . . . . . . . . . . 3 + * 2 . . . . . . . . . . . . . . . . . . . . . . . . 2 + * 1 . . . . . . . . . . . . . . . . . . . . . . . . 1 + * a b c d e f g h a b c d e f g h a b c d e f g h + */ +template +constexpr u64 shift(u64); + +template <> +inline constexpr u64 shift(u64 bits) { + return (bits >> 1) & ~bitMask(h1); +} +template <> +inline constexpr u64 shift(u64 bits) { + return (bits << 1) & ~bitMask(a1); +} +template <> +inline constexpr u64 shift(u64 bits) { + return bits << 8; +} +template <> +inline constexpr u64 shift(u64 bits) { + return bits >> 8; +} +template <> +inline constexpr u64 shift(u64 bits) { + return (bits >> 7) & ~bitMask(a1); +} +template <> +inline constexpr u64 shift(u64 bits) { + return (bits << 9) & ~bitMask(a1); +} +template <> +inline constexpr u64 shift(u64 bits) { + return (bits >> 9) & ~bitMask(h1); +} +template <> +inline constexpr u64 shift(u64 bits) { + return (bits << 7) & ~bitMask(h1); +} + +/** + * Fills (including) start square till the end of the board. + */ +template +constexpr u64 fill(u64); + +/** + * bits fill(bits) fill(bits) + * 8 . . . . . . . . . . 1 . . 1 . . . . . . . . . . 8 + * 7 . . . . . . . . . . 1 . . 1 . . . . . . . . . . 7 + * 6 . . . . . . . . . . 1 . . 1 . . . . . . . . . . 6 + * 5 . . 1 . . 1 . . . . 1 . . 1 . . 1 1 1 1 1 1 1 1 5 + * 4 . . . . . . . . . . 1 . . 1 . . . . . . . . . . 4 + * 3 . . 1 . . . . . . . 1 . . 1 . . 1 1 1 1 1 1 1 1 3 + * 2 . . . . . . . . . . 1 . . 1 . . . . . . . . . . 2 + * 1 . . . . . . . . . . 1 . . 1 . . . . . . . . . . 1 + * a b c d e f g h a b c d e f g h a b c d e f g h + */ +template <> +inline constexpr u64 fill(u64 bits) { + bits |= bits >> 8; + bits |= bits >> 16; + return bits | (bits >> 32); +} +template <> +inline constexpr u64 fill(u64 bits) { + bits |= bits << 8; + bits |= bits << 16; + return bits | (bits << 32); +} +template <> +inline constexpr u64 fill(u64 bits) { + bits |= ((bits & 0xFEFEFEFEFEFEFEFE) >> 1); + bits |= ((bits & 0xFCFCFCFCFCFCFCFC) >> 2); + return bits | ((bits & 0xF0F0F0F0F0F0F0F0) >> 4); +} +template <> +inline constexpr u64 fill(u64 bits) { + bits |= ((bits & 0x7F7F7F7F7F7F7F7F) << 1); + bits |= ((bits & 0x3F3F3F3F3F3F3F3F) << 2); + return bits | ((bits & 0x0F0F0F0F0F0F0F0F) << 4); +} + +template <> +inline constexpr u64 fill(u64 bits) { + return fill(bits) | fill(bits); +} +template <> +inline constexpr u64 fill(u64 bits) { + return fill(bits) | fill(bits); +} + +/** + * An attack fill (excludes the attacker but includes blocking pieces) + * see: https://www.chessprogramming.org/Dumb7Fill + */ +template +inline u64 fill7dump(u64, u64); + +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + u64 flood = attacker; + empty &= ~bitMask(h1); // block fill (avoid wrap) + flood |= attacker = (attacker >> 1) & empty; + flood |= attacker = (attacker >> 1) & empty; + flood |= attacker = (attacker >> 1) & empty; + flood |= attacker = (attacker >> 1) & empty; + flood |= attacker = (attacker >> 1) & empty; + flood |= attacker = (attacker >> 1) & empty; + return (flood >> 1) & ~bitMask(h1); +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + u64 flood = attacker; + empty &= ~bitMask(a1); // block fill (avoid wrap) + flood |= attacker = (attacker << 1) & empty; + flood |= attacker = (attacker << 1) & empty; + flood |= attacker = (attacker << 1) & empty; + flood |= attacker = (attacker << 1) & empty; + flood |= attacker = (attacker << 1) & empty; + flood |= attacker = (attacker << 1) & empty; + return (flood << 1) & ~bitMask(a1); +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + u64 flood = attacker; + flood |= attacker = (attacker << 8) & empty; + flood |= attacker = (attacker << 8) & empty; + flood |= attacker = (attacker << 8) & empty; + flood |= attacker = (attacker << 8) & empty; + flood |= attacker = (attacker << 8) & empty; + flood |= attacker = (attacker << 8) & empty; + return flood << 8; +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + u64 flood = attacker; + flood |= attacker = (attacker >> 8) & empty; + flood |= attacker = (attacker >> 8) & empty; + flood |= attacker = (attacker >> 8) & empty; + flood |= attacker = (attacker >> 8) & empty; + flood |= attacker = (attacker >> 8) & empty; + flood |= attacker = (attacker >> 8) & empty; + return flood >> 8; +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + u64 flood = attacker; + empty &= ~bitMask(a1); // block fill (avoid wrap) + flood |= attacker = (attacker >> 7) & empty; + flood |= attacker = (attacker >> 7) & empty; + flood |= attacker = (attacker >> 7) & empty; + flood |= attacker = (attacker >> 7) & empty; + flood |= attacker = (attacker >> 7) & empty; + flood |= attacker = (attacker >> 7) & empty; + return (flood >> 7) & ~bitMask(a1); +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + u64 flood = attacker; + empty &= ~bitMask(a1); // block fill (avoid wrap) + flood |= attacker = (attacker << 9) & empty; + flood |= attacker = (attacker << 9) & empty; + flood |= attacker = (attacker << 9) & empty; + flood |= attacker = (attacker << 9) & empty; + flood |= attacker = (attacker << 9) & empty; + flood |= attacker = (attacker << 9) & empty; + return (flood << 9) & ~bitMask(a1); +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + u64 flood = attacker; + empty &= ~bitMask(h1); // block fill (avoid wrap) + flood |= attacker = (attacker >> 9) & empty; + flood |= attacker = (attacker >> 9) & empty; + flood |= attacker = (attacker >> 9) & empty; + flood |= attacker = (attacker >> 9) & empty; + flood |= attacker = (attacker >> 9) & empty; + flood |= attacker = (attacker >> 9) & empty; + return (flood >> 9) & ~bitMask(h1); +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + u64 flood = attacker; + empty &= ~bitMask(h1); // block fill (avoid wrap) + flood |= attacker = (attacker << 7) & empty; + flood |= attacker = (attacker << 7) & empty; + flood |= attacker = (attacker << 7) & empty; + flood |= attacker = (attacker << 7) & empty; + flood |= attacker = (attacker << 7) & empty; + flood |= attacker = (attacker << 7) & empty; + return (flood << 7) & ~bitMask(h1); +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + return fill7dump (attacker, empty) + | fill7dump (attacker, empty) + | fill7dump (attacker, empty) + | fill7dump(attacker, empty); +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + return fill7dump (attacker, empty) + | fill7dump (attacker, empty) + | fill7dump (attacker, empty) + | fill7dump(attacker, empty); +} +template <> +inline u64 fill7dump(u64 attacker, u64 empty) { + return fill7dump (attacker, empty) + | fill7dump(attacker, empty); +} + +inline Index fileIndex(const Index squareIndex) { + assert(squareIndex < 64); + + return squareIndex & 7; +} +inline Index rankIndex(const Index squareIndex) { + assert(squareIndex < 64); + + return squareIndex >> 3; +} +inline Index squareIndex(const Index fileIndex, const Index rankIndex) { + assert(fileIndex < 8); + assert(rankIndex < 8); + + return 8 * rankIndex + fileIndex; +} + +inline u64 lineMask(Index sq1, Index sq2) { + int r1 = static_cast(rankIndex(sq1)); + int r2 = static_cast(rankIndex(sq2)); + + int f1 = static_cast(fileIndex(sq1)); + int f2 = static_cast(fileIndex(sq2)); + + assert((-1 < r1) && (r1 < 8)); + assert((-1 < r2) && (r2 < 8)); + assert((-1 < f1) && (f1 < 8)); + assert((-1 < f2) && (f2 < 8)); + + if (r1 == r2) { return bitMask(sq1); } + if (f1 == f2) { return bitMask(sq1); } + if ((r1 - r2) == (f1 - f2)) { return bitMask(sq1); } + if ((r1 - r2) == (f2 - f1)) { return bitMask(sq1); } + + assert(false); + + return bitMask(sq1) | bitMask(sq2); +} + +inline u64 bitReverse(u64 x) { + x = (((x & static_cast(0xAAAAAAAAAAAAAAAA)) >> 1) + | ((x & static_cast(0x5555555555555555)) << 1)); + x = (((x & static_cast(0xCCCCCCCCCCCCCCCC)) >> 2) + | ((x & static_cast(0x3333333333333333)) << 2)); + x = (((x & static_cast(0xF0F0F0F0F0F0F0F0)) >> 4) + | ((x & static_cast(0x0F0F0F0F0F0F0F0F)) << 4)); +#ifdef __GNUC__ + return __builtin_bswap64(x); +#else + x = (((x & static_cast(0xFF00FF00FF00FF00)) >> 8) + | ((x & static_cast(0x00FF00FF00FF00FF)) << 8)); + x = (((x & static_cast(0xFFFF0000FFFF0000)) >> 16) + | ((x & static_cast(0x0000FFFF0000FFFF)) << 16)); + return((x >> 32) | (x << 32)); +#endif +} + +template +inline u64 bitFlip(u64 x); + +// Reverses byte order, aka rank 8 <-> rank 1, rank 7 <-> rank 2, ... +template <> +inline u64 bitFlip(u64 x) { +#ifdef __GNUC__ + return __builtin_bswap64(x); +#elif + x = (((x & static_cast(0xFF00FF00FF00FF00)) >> 8) + | ((x & static_cast(0x00FF00FF00FF00FF)) << 8)); + x = (((x & static_cast(0xFFFF0000FFFF0000)) >> 16) + | ((x & static_cast(0x0000FFFF0000FFFF)) << 16)); + return((x >> 32) | (x << 32)); +#endif +} + +// Reverses bits in bytes, aka file a <-> file h, file b <-> file g, ... +template <> +inline u64 bitFlip (u64 x) { + constexpr u64 a = 0x5555555555555555; + constexpr u64 b = 0x3333333333333333; + constexpr u64 c = 0x0F0F0F0F0F0F0F0F; + + x = ((x >> 1) & a) | ((x & a) << 1); + x = ((x >> 2) & b) | ((x & b) << 2); + x = ((x >> 4) & c) | ((x & c) << 4); + + return x; +} + +// see: https://chessprogramming.org/Flipping_Mirroring_and_Rotating + +// Flips bits about the diagonal (transposition of the board) +template <> +inline u64 bitFlip(u64 x) { + u64 t; // Temporary Value + constexpr u64 a = 0x5500550055005500; + constexpr u64 b = 0x3333000033330000; + constexpr u64 c = 0x0F0F0F0F00000000; + + t = c & (x ^ (x << 28)); + x ^= t ^ (t >> 28); + t = b & (x ^ (x << 14)); + x ^= t ^ (t >> 14); + t = a & (x ^ (x << 7)); + x ^= t ^ (t >> 7); + + return x; +} + +// Flips bits about the anti-diagonal +template <> +inline u64 bitFlip(u64 x) { + u64 t; // Temporary Value + constexpr u64 a = 0xAA00AA00AA00AA00; + constexpr u64 b = 0xCCCC0000CCCC0000; + constexpr u64 c = 0xF0F0F0F00F0F0F0F; + + t = x ^ (x << 36); + x ^= c & (t ^ (x >> 36)); + t = b & (x ^ (x << 18)); + x ^= t ^ (t >> 18); + t = a & (x ^ (x << 9)); + x ^= t ^ (t >> 9); + + return x; +} + +inline Index bitCount(u64 x) { +#ifdef __GNUC__ + return __builtin_popcountll(x); // `POPulation COUNT` (Long Long) +#elif + Index count = 0; // counts set bits + + // increment count until there are no bits set in x + for (; x; count++) { + x &= x - 1; // unset least significant bit + } + + return count; +#endif +} + +#ifdef __GNUC__ +inline Index bitScanLS(u64 bb) { + return __builtin_ctzll(bb); // Count Trailing Zeros (Long Long) +} +#elif +/** + * `de Brujin` sequence and lookup for bitScanLS and bitScanMS. + */ +constexpr u64 debruijn64Seq = static_cast(0x03f79d71b4cb0a89); +constexpr Index debruijn64Lookup[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, + 57, 49, 41, 37, 28, 16, 3, 61, + 54, 58, 35, 52, 50, 42, 21, 44, + 38, 32, 29, 23, 17, 11, 4, 62, + 46, 55, 26, 59, 40, 36, 15, 53, + 34, 51, 20, 43, 31, 22, 10, 45, + 25, 39, 14, 33, 19, 30, 9, 24, + 13, 18, 8, 12, 7, 6, 5, 63 +}; +/** + * Gets the least significant 1 bit `bitScanLS` and most significant + * `bitScanMS` index on a 64-bit board. + * + * Using a `de Brujin` sequence to index a 1 in a 64-bit word. + * + * @param `bb` 64-bit word (a bit board) + * @condition `bb` != 0 + * @return index 0 to 63 of least significant one bit + * + * @see Original authors: `Martin Läuter (1997), Charles E. Leiserson,` + * `Harald Prokop, Keith H. Randall` + * @see https://www.chessprogramming.org/BitScan + */ +inline Index bitScanLS(u64 bb) { + assert(bb != 0); + return debruijn64Lookup[((bb ^ (bb - 1)) * debruijn64Seq) >> 58]; +} +inline Index bitScanMS(u64 bb) { + assert(bb != 0); + + bb |= bb >> 1; + bb |= bb >> 2; + bb |= bb >> 4; + bb |= bb >> 8; + bb |= bb >> 16; + bb |= bb >> 32; + + return debruijn64Lookup[(bb * debruijn64Seq) >> 58]; +} +#endif + +#endif /* INCLUDE_GUARD_UTILS_H */ diff --git a/dataAnalysis/chess/Rchess/src/Makevars b/dataAnalysis/chess/Rchess/src/Makevars new file mode 100644 index 0000000..adaaeae --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/Makevars @@ -0,0 +1,4 @@ +PKG_CXXFLAGS += -I'../inst/include' -pthread -DRCPP_RCOUT + +SOURCES = $(wildcard *.cpp) $(wildcard ../inst/include/SchachHoernchen/*.cpp) +OBJECTS = $(SOURCES:.cpp=.o) diff --git a/dataAnalysis/chess/Rchess/src/RcppExports.cpp b/dataAnalysis/chess/Rchess/src/RcppExports.cpp new file mode 100644 index 0000000..827067e --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/RcppExports.cpp @@ -0,0 +1,196 @@ +// Generated by using Rcpp::compileAttributes() -> do not edit by hand +// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 + +#include "../inst/include/Rchess.h" +#include + +using namespace Rcpp; + +#ifdef RCPP_USE_GLOBAL_ROSTREAM +Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); +Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); +#endif + +// data_gen +Rcpp::CharacterVector data_gen(const std::string& file, const int sample_size, const float score_min, const float score_max); +RcppExport SEXP _Rchess_data_gen(SEXP fileSEXP, SEXP sample_sizeSEXP, SEXP score_minSEXP, SEXP score_maxSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const std::string& >::type file(fileSEXP); + Rcpp::traits::input_parameter< const int >::type sample_size(sample_sizeSEXP); + Rcpp::traits::input_parameter< const float >::type score_min(score_minSEXP); + Rcpp::traits::input_parameter< const float >::type score_max(score_maxSEXP); + rcpp_result_gen = Rcpp::wrap(data_gen(file, sample_size, score_min, score_max)); + return rcpp_result_gen; +END_RCPP +} +// fen2int +Rcpp::IntegerVector fen2int(const std::vector& boards); +RcppExport SEXP _Rchess_fen2int(SEXP boardsSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const std::vector& >::type boards(boardsSEXP); + rcpp_result_gen = Rcpp::wrap(fen2int(boards)); + return rcpp_result_gen; +END_RCPP +} +// read_cyclic +Rcpp::CharacterVector read_cyclic(const std::string& file, const int nrows, const int skip, const int start, const int line_len); +RcppExport SEXP _Rchess_read_cyclic(SEXP fileSEXP, SEXP nrowsSEXP, SEXP skipSEXP, SEXP startSEXP, SEXP line_lenSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const std::string& >::type file(fileSEXP); + Rcpp::traits::input_parameter< const int >::type nrows(nrowsSEXP); + Rcpp::traits::input_parameter< const int >::type skip(skipSEXP); + Rcpp::traits::input_parameter< const int >::type start(startSEXP); + Rcpp::traits::input_parameter< const int >::type line_len(line_lenSEXP); + rcpp_result_gen = Rcpp::wrap(read_cyclic(file, nrows, skip, start, line_len)); + return rcpp_result_gen; +END_RCPP +} +// sample_move +Move sample_move(const Board& pos); +RcppExport SEXP _Rchess_sample_move(SEXP posSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const Board& >::type pos(posSEXP); + rcpp_result_gen = Rcpp::wrap(sample_move(pos)); + return rcpp_result_gen; +END_RCPP +} +// sample_fen +std::vector sample_fen(const unsigned nr, const unsigned min_depth, const unsigned max_depth); +RcppExport SEXP _Rchess_sample_fen(SEXP nrSEXP, SEXP min_depthSEXP, SEXP max_depthSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const unsigned >::type nr(nrSEXP); + Rcpp::traits::input_parameter< const unsigned >::type min_depth(min_depthSEXP); + Rcpp::traits::input_parameter< const unsigned >::type max_depth(max_depthSEXP); + rcpp_result_gen = Rcpp::wrap(sample_fen(nr, min_depth, max_depth)); + return rcpp_result_gen; +END_RCPP +} +// board +Board board(const std::string& fen); +RcppExport SEXP _Rchess_board(SEXP fenSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const std::string& >::type fen(fenSEXP); + rcpp_result_gen = Rcpp::wrap(board(fen)); + return rcpp_result_gen; +END_RCPP +} +// print_board +void print_board(const std::string& fen); +RcppExport SEXP _Rchess_print_board(SEXP fenSEXP) { +BEGIN_RCPP + Rcpp::traits::input_parameter< const std::string& >::type fen(fenSEXP); + print_board(fen); + return R_NilValue; +END_RCPP +} +// print_moves +void print_moves(const std::string& fen); +RcppExport SEXP _Rchess_print_moves(SEXP fenSEXP) { +BEGIN_RCPP + Rcpp::traits::input_parameter< const std::string& >::type fen(fenSEXP); + print_moves(fen); + return R_NilValue; +END_RCPP +} +// print_bitboards +void print_bitboards(const std::string& fen); +RcppExport SEXP _Rchess_print_bitboards(SEXP fenSEXP) { +BEGIN_RCPP + Rcpp::traits::input_parameter< const std::string& >::type fen(fenSEXP); + print_bitboards(fen); + return R_NilValue; +END_RCPP +} +// position +Board position(const Board& pos, const std::vector& moves, const bool san); +RcppExport SEXP _Rchess_position(SEXP posSEXP, SEXP movesSEXP, SEXP sanSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const Board& >::type pos(posSEXP); + Rcpp::traits::input_parameter< const std::vector& >::type moves(movesSEXP); + Rcpp::traits::input_parameter< const bool >::type san(sanSEXP); + rcpp_result_gen = Rcpp::wrap(position(pos, moves, san)); + return rcpp_result_gen; +END_RCPP +} +// perft +void perft(const int depth); +RcppExport SEXP _Rchess_perft(SEXP depthSEXP) { +BEGIN_RCPP + Rcpp::traits::input_parameter< const int >::type depth(depthSEXP); + perft(depth); + return R_NilValue; +END_RCPP +} +// go +Move go(const int depth); +RcppExport SEXP _Rchess_go(SEXP depthSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const int >::type depth(depthSEXP); + rcpp_result_gen = Rcpp::wrap(go(depth)); + return rcpp_result_gen; +END_RCPP +} +// ucinewgame +void ucinewgame(); +RcppExport SEXP _Rchess_ucinewgame() { +BEGIN_RCPP + ucinewgame(); + return R_NilValue; +END_RCPP +} +// onLoad +void onLoad(const std::string& libname, const std::string& pkgname); +RcppExport SEXP _Rchess_onLoad(SEXP libnameSEXP, SEXP pkgnameSEXP) { +BEGIN_RCPP + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const std::string& >::type libname(libnameSEXP); + Rcpp::traits::input_parameter< const std::string& >::type pkgname(pkgnameSEXP); + onLoad(libname, pkgname); + return R_NilValue; +END_RCPP +} +// onUnload +void onUnload(const std::string& libpath); +RcppExport SEXP _Rchess_onUnload(SEXP libpathSEXP) { +BEGIN_RCPP + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const std::string& >::type libpath(libpathSEXP); + onUnload(libpath); + return R_NilValue; +END_RCPP +} + +static const R_CallMethodDef CallEntries[] = { + {"_Rchess_data_gen", (DL_FUNC) &_Rchess_data_gen, 4}, + {"_Rchess_fen2int", (DL_FUNC) &_Rchess_fen2int, 1}, + {"_Rchess_read_cyclic", (DL_FUNC) &_Rchess_read_cyclic, 5}, + {"_Rchess_sample_move", (DL_FUNC) &_Rchess_sample_move, 1}, + {"_Rchess_sample_fen", (DL_FUNC) &_Rchess_sample_fen, 3}, + {"_Rchess_board", (DL_FUNC) &_Rchess_board, 1}, + {"_Rchess_print_board", (DL_FUNC) &_Rchess_print_board, 1}, + {"_Rchess_print_moves", (DL_FUNC) &_Rchess_print_moves, 1}, + {"_Rchess_print_bitboards", (DL_FUNC) &_Rchess_print_bitboards, 1}, + {"_Rchess_position", (DL_FUNC) &_Rchess_position, 3}, + {"_Rchess_perft", (DL_FUNC) &_Rchess_perft, 1}, + {"_Rchess_go", (DL_FUNC) &_Rchess_go, 1}, + {"_Rchess_ucinewgame", (DL_FUNC) &_Rchess_ucinewgame, 0}, + {"_Rchess_onLoad", (DL_FUNC) &_Rchess_onLoad, 2}, + {"_Rchess_onUnload", (DL_FUNC) &_Rchess_onUnload, 1}, + {NULL, NULL, 0} +}; + +RcppExport void R_init_Rchess(DllInfo *dll) { + R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); + R_useDynamicSymbols(dll, FALSE); +} diff --git a/dataAnalysis/chess/Rchess/src/data_gen.cpp b/dataAnalysis/chess/Rchess/src/data_gen.cpp new file mode 100644 index 0000000..5ecec49 --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/data_gen.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "SchachHoernchen/Board.h" + +//' Specialized version of `read_cyclic.cpp` taylored to work in conjunction with +//' `gmlm_chess()` as data generator to provide random draws from a FEN data set +//' with scores filtered to be in in the range `score_min` to `score_max`. +//' +// [[Rcpp::export(name = "data.gen", rng = true)]] +Rcpp::CharacterVector data_gen( + const std::string& file, + const int sample_size, + const float score_min, + const float score_max +) { + // Check parames + if (sample_size < 1) { + Rcpp::stop("`sample_size` must be positive"); + } + if (score_min >= score_max) { + Rcpp::stop("`score_min` must be strictly smaller than `score_max`"); + } + + // open FEN data set file + std::ifstream input(file); + if (!input) { + Rcpp::stop("Opening file '%s' failed", file); + } + + // set the read from stream position to a random line + input.seekg(0, std::ios::end); + unsigned long seek = unif_rand() * input.tellg(); + input.seekg(seek); + // from random position set stream position to line start (if not over shot) + if (!input.eof()) { + input.ignore(std::numeric_limits::max(), '\n'); + } + // Ensure (in any case) we are at a legal position (recycle) + if (input.eof()) { + input.seekg(0); + } + + // Allocate output sample + Rcpp::CharacterVector sample(sample_size); + + // Read and filter lines from FEN data base file + std::string line, fen; + float score; + Board pos; + int sample_count = 0, retry_count = 0, reject_count = 0; + while (sample_count < sample_size) { + // Check for user interupt (that is, allows from `R` to interupt execution) + R_CheckUserInterrupt(); + + // Read line, in case of failure retry from start of file (recycling) + if (!std::getline(input, line)) { + input.clear(); + input.seekg(0); + if (!std::getline(input, line)) { + // another failur is fatal + Rcpp::stop("Recycline lines in file '%s' failed", file); + } + } + + // Check for empty line, treated as a partial error which we retry a few times + if (line.empty()) { + if (++retry_count > 10) { + Rcpp::stop("Retry count exceeded after reading empty line in '%s'", file); + } else { + continue; + } + } + + // Split candidat line into FEN and score + std::stringstream candidat(line); + std::getline(candidat, fen, ';'); + candidat >> score; + if (candidat.fail()) { + // If this failes, the FEN data base is ill formed! + Rcpp::stop("Ill formated FEN data base file '%s'", file); + } + + // parse FEN to filter only positions with white to move + bool parseError = false; + pos.init(fen, parseError); + if (parseError) { + Rcpp::stop("Retry count exceeded after illegal FEN '%s'", fen); + } + + // Filter white to move positions + if (pos.sideToMove() == piece::black) { + reject_count++; + continue; + } + // filter scores out of slice + if (score < score_min || score_max <= score) { + reject_count++; + continue; + } + + // Avoid infinite loop + if (reject_count > 1000 * sample_size) { + Rcpp::stop("Too many rejections, stop to avoid infinite loop"); + } + + // Everythings succeeded and ge got an appropriate sample in requested range + sample[sample_count++] = fen; + + // skip lines (ensures independent draws based on games being independent) + if (input.eof()) { + input.seekg(0); + } + for (int s = 0; s < 256; ++s) { + input.ignore(std::numeric_limits::max(), '\n'); + if (input.eof()) { + input.seekg(0); + } + } + } + + return sample; +} diff --git a/dataAnalysis/chess/Rchess/src/fen2int.cpp b/dataAnalysis/chess/Rchess/src/fen2int.cpp new file mode 100644 index 0000000..475bb8e --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/fen2int.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include "SchachHoernchen/types.h" +#include "SchachHoernchen/utils.h" +#include "SchachHoernchen/Board.h" + +//' Convert a legal FEN string to a 3D binary (integer with 0-1 entries) array +// [[Rcpp::export(rng = false)]] +Rcpp::IntegerVector fen2int(const std::vector& boards) { + // Initialize empty chess board as a 3D binary tensor + Rcpp::IntegerVector bitboards(8 * 8 * 12 * (int)boards.size()); + // Set dimension and dimension names (required this way since `Rcpp::Dimension` + // does _not_ support 4D arrays) + auto dims = Rcpp::IntegerVector({ 8, 8, 12, (int)boards.size() }); + bitboards.attr("dim") = dims; + bitboards.attr("dimnames") = Rcpp::List::create( + Rcpp::Named("rank") = Rcpp::CharacterVector::create("8", "7", "6", "5", "4", "3", "2", "1"), + Rcpp::Named("file") = Rcpp::CharacterVector::create("a", "b", "c", "d", "e", "f", "g", "h"), + Rcpp::Named("piece") = Rcpp::CharacterVector::create( + "P", "N", "B", "R", "Q", "K", // White Pieces (Upper Case) + "p", "n", "b", "r", "q", "k" // Black Pieces (Lower Case) + ), + R_NilValue + ); + + // Index to color/piece mapping (more robust) + enum piece colorLoopup[2] = { white, black }; + enum piece pieceLookup[6] = { pawn, knight, bishop, rook, queen, king }; + + // Set for every piece the corresponding pit positions, note the + // "transposition" of the indexing from SchachHoernchen's binary indexing + // scheme to the 3D array indexing of ranks/files. + for (int i = 0; i < boards.size(); ++i) { + const Board& pos = boards[i]; + for (int color = 0; color < 2; ++color) { + for (int piece = 0; piece < 6; ++piece) { + int slice = 6 * color + piece; + u64 bb = pos.bb(colorLoopup[color]) & pos.bb(pieceLookup[piece]); + for (; bb; bb &= bb - 1) { + // Get BitBoard index + int index = bitScanLS(bb); + // Transpose to align with printing as a Chess Board + index = ((index & 7) << 3) | ((index & 56) >> 3); + bitboards[768 * i + 64 * slice + index] = 1; + } + } + } + } + + return bitboards; +} diff --git a/dataAnalysis/chess/Rchess/src/print.cpp b/dataAnalysis/chess/Rchess/src/print.cpp new file mode 100644 index 0000000..2f445d3 --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/print.cpp @@ -0,0 +1,71 @@ +// #include + +// #include "SchachHoernchen/utils.h" +// #include "SchachHoernchen/Board.h" +// #include "SchachHoernchen/uci.h" + +// // [[Rcpp::export(name = "print.board", rng = false)]] +// void print_board( +// const Board& board, +// const bool check = true, +// const bool attacked = false, +// const bool pinned = false, +// const bool checkers = false +// ) { +// using Rcpp::Rcout; + +// // Extract some properties +// piece color = board.sideToMove(); +// piece enemy = static_cast(!color); +// u64 empty = ~(board.bb(piece::white) | board.bb(piece::black)); +// u64 cKing = board.bb(king) & board.bb(color); +// // Construct highlight mask +// u64 attackMask = board.attacks(enemy, empty); +// u64 mask = 0; +// mask |= check ? attackMask & board.bb(color) & board.bb(king) : 0; +// mask |= attacked ? attackMask & ~board.bb(color) : 0; +// mask |= pinned ? board.pinned(enemy, cKing, empty) : 0; +// mask |= checkers ? board.checkers(enemy, cKing, empty) : 0; + +// // print the board to console +// Rcout << "FEN: " << board.fen() << '\n'; +// for (Index line = 0; line < 17; line++) { +// if (line % 2) { +// Index rankIndex = line / 2; +// Rcout << static_cast('8' - rankIndex); +// for (Index fileIndex = 0; fileIndex < 8; fileIndex++) { +// Index squareIndex = 8 * rankIndex + fileIndex; +// if (bitMask(squareIndex) & mask) { +// if (board.piece(squareIndex)) { +// if (board.color(squareIndex) == black) { +// // bold + italic + underline + black (blue) +// Rcout << " | \033[1;3;4;94m"; +// } else { +// // bold + italic + underline (+ white) +// Rcout << " | \033[1;3;4m"; +// } +// Rcout << UCI::formatPiece(board.piece(squareIndex)) +// << "\033[0m"; +// } else { +// Rcout << " | ."; +// } +// } else if (board.color(squareIndex) == black) { +// Rcout << " | \033[1m\033[94m" // bold + blue (black) +// << UCI::formatPiece(board.piece(squareIndex)) +// << "\033[0m"; +// } else if (board.color(squareIndex) == white) { +// Rcout << " | \033[1m\033[97m" // bold + white +// << UCI::formatPiece(board.piece(squareIndex)) +// << "\033[0m"; +// } else { +// Rcout << " | "; +// } +// } +// Rcout << " |"; +// } else { +// Rcout << " +---+---+---+---+---+---+---+---+"; +// } +// Rcout << "\033[0K\n"; // clear rest of line (remove potential leftovers) +// } +// Rcout << " a b c d e f g h" << std::endl; +// } diff --git a/dataAnalysis/chess/Rchess/src/read_cyclic.cpp b/dataAnalysis/chess/Rchess/src/read_cyclic.cpp new file mode 100644 index 0000000..63c6ff6 --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/read_cyclic.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include + +#include + +//' Reads lines from a text file with recycling. +//' +// [[Rcpp::export(name = "read.cyclic", rng = false)]] +Rcpp::CharacterVector read_cyclic( + const std::string& file, + const int nrows = 1000, + const int skip = 100, + const int start = 1, + const int line_len = 64 +) { + // `unsigned` is not "properly" checked by `Rcpp` to _not_ be negative. + // An implicit cast to unsigned makes negative integers gigantic, not wanted! + if (skip < 0) { + Rcpp::stop("`skip` must be non-negative."); + } + if (nrows < 1 || start < 1 || line_len < 1) { + Rcpp::stop("`start`, `nrows` and `line_len` must be positive."); + } + + // Open input file + std::ifstream input(file); + if (!input) { + Rcpp::stop("Opening file '%s' failed", file); + } + + // Handle different versions of start positions, if the start position is + // large, we guess a value to skip via a simple heuristic and go with it + if (1000 < start) { + // Skip (approx) `start` lines + input.seekg(0, std::ios::end); // get to end of file + unsigned long size = static_cast(input.tellg()); + unsigned long seek = static_cast(line_len) * ( + static_cast(start) - 1UL + ); + seek = seek % size; + // Now seek to the approx line nr. with recycling + input.seekg(seek); + // read till end of line to have a proper start of line position + if (!input.eof()) { + input.ignore(std::numeric_limits::max(), '\n'); + } + // in the occastional case of ending at a last line (recycle) + if (input.eof()) { input.seekg(0); } + } else { + // Skip (exactly) `start` lines + for (int line_nr = 1; line_nr < start; ++line_nr) { + input.ignore(std::numeric_limits::max(), '\n'); + // In case of reaching the end of file, restart (recycle) + if (input.eof()) { input.seekg(0); } + } + } + + // Create character vector for output lines + Rcpp::CharacterVector lines(nrows); + + // Read one line and skip multiple till `nrows` lines are included + std::string line; + for (int i = 0; i < nrows; ++i) { + // Read one line + if (std::getline(input, line)) { + lines[i] = line; + } else { + // retry from start of file, may occure in last empty line of file + input.clear(); + input.seekg(0); + if (std::getline(input, line)) { + lines[i] = line; + } else { + // Another failur is fatal + Rcpp::stop("Recycling lines in file '%s' failed", file); + } + } + + // recycle if at end of file (always check) + if (input.eof()) { input.seekg(0); } + + // skip lines (with recycling) + for (int s = 0; s < skip; ++s) { + input.ignore(std::numeric_limits::max(), '\n'); + if (input.eof()) { input.seekg(0); } + } + } + + return lines; +} diff --git a/dataAnalysis/chess/Rchess/src/sample.cpp b/dataAnalysis/chess/Rchess/src/sample.cpp new file mode 100644 index 0000000..9cbb9d6 --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/sample.cpp @@ -0,0 +1,92 @@ +#include +#include + +#include "SchachHoernchen/Move.h" +#include "SchachHoernchen/Board.h" + +//' Samples a legal move from a given position +// [[Rcpp::export("sample.move", rng = true)]] +Move sample_move(const Board& pos) { + // RNG for continuous uniform X ~ U[0, 1] + auto runif = Rcpp::stats::UnifGenerator(0, 1); + // RNG for discrete uniform X ~ U[0; max - 1] + auto rindex = [&runif](const std::size_t max) { + // sample random index untill in range [0; max - 1] + unsigned index; + do { + index = static_cast(runif() * static_cast(max)); + } while (index >= max); + return index; + }; + + // generate all legal moves + MoveList moves; + pos.moves(moves); + + // check if there are any moves to sample from + if (moves.empty()) { + Rcpp::stop("Attempt to same legal move from terminal node"); + } + + return moves[rindex(moves.size())]; +} + +//' Samples a random FEN (position) by applying `ply` random moves to the start +//' position. +//' +//' @param nr number of positions to sample +//' @param min_depth minimum number of random ply's to generate random positions +//' @param max_depth maximum number of random ply's to generate random positions +// [[Rcpp::export("sample.fen", rng = true)]] +std::vector sample_fen(const unsigned nr, + const unsigned min_depth = 4, const unsigned max_depth = 20 +) { + // Parameter validation + if (min_depth > max_depth) { + Rcpp::stop("max_depth must be bigger equal than min_depth"); + } + if (128 < max_depth) { + Rcpp::stop("max_depth exceeded maximum value 128"); + } + + // RNG for continuous uniform X ~ U[0, 1] + auto runif = Rcpp::stats::UnifGenerator(0, 1); + // RNG for discrete uniform X ~ U[0; max - 1] + auto rindex = [&runif](const std::size_t max) { + // Check if max is bigger than zero, cause we can not sample from the + // empty set + if (max == 0) { + Rcpp::stop("Attempt to sample random index < 0."); + } + // sample random index untill in range [0; max - 1] + unsigned index = max; + while (index >= max) { + index = static_cast(runif() * static_cast(max)); + } + return index; + }; + + // Setup response vector + std::vector fens; + fens.reserve(nr); + + // Sample FENs + MoveList moves; + for (unsigned i = 0; i < nr; ++i) { + Board pos; // start position + unsigned depth = static_cast(runif() * (max_depth - min_depth)); + depth += min_depth; + for (unsigned ply = 0; ply < depth; ++ply) { + moves.clear(); + pos.moves(moves); + if (moves.size()) { + pos.make(moves[rindex(moves.size())]); + } else { + break; + } + } + fens.push_back(pos); + } + + return fens; +} diff --git a/dataAnalysis/chess/Rchess/src/uci.cpp b/dataAnalysis/chess/Rchess/src/uci.cpp new file mode 100644 index 0000000..09ac63a --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/uci.cpp @@ -0,0 +1,146 @@ +// UCI (Univeral Chess Interface) `R` binding to the `SchachHoernchen` engine +#include +#include +#include +#include + +#include + +#include "SchachHoernchen/Move.h" +#include "SchachHoernchen/Board.h" +#include "SchachHoernchen/uci.h" +#include "SchachHoernchen/search.h" + +namespace UCI_State { + std::vector game(1); +}; + +//' Converts a FEN string to a Board (position) or return the current internal state +// [[Rcpp::export(rng = false)]] +Board board(const std::string& fen = "") { + if (fen == "") { + return UCI_State::game.back(); + } else if (fen == "startpos") { + return Board(); + } + Board pos; + bool parseError = false; + pos.init(fen, parseError); + if (parseError) { + Rcpp::stop("Parse parsing FEN"); + } + return pos; +} + +// [[Rcpp::export(name = "print.board", rng = false)]] +void print_board(const std::string& fen = "") { + UCI::printBoard(board(fen)); +} + +// [[Rcpp::export(name = "print.moves", rng = false)]] +void print_moves(const std::string& fen = "") { + UCI::printMoves(board(fen)); +} + +// [[Rcpp::export(name = "print.bitboards", rng = false)]] +void print_bitboards(const std::string& fen = "") { + UCI::printBitBoards(board(fen)); +} + +// [[Rcpp::export(rng = false)]] +Board position( + const Board& pos, + const std::vector& moves, + const bool san = false +) { + // Build UCI command (without "position") + std::stringstream cmd; + cmd << "fen " << pos.fen() << " "; + if (moves.size()) { + cmd << "moves "; + std::copy(moves.begin(), moves.end(), std::ostream_iterator(cmd, " ")); + } + + // set UCI internal flag to interprate move input in from-to format or SAN + UCI::readSAN = san; + + // and invoke UCI position command handler on the internal game state + bool parseError = false; + UCI::position(UCI_State::game, cmd, parseError); + if (parseError) { + Rcpp::stop("Parse Error"); + } + + return UCI_State::game.back(); +} + +// [[Rcpp::export(rng = false)]] +void perft(const int depth = 6) { + // Enforce a depth limit, this is very restrictive but we only want to + // use it as a toolbox and _not_ as a strong chess engine + if (8 < depth) { + Rcpp::stop("In `R` search is limited to depth 8"); + } else if (depth <= 0) { + cout << "Nodes searched: 0" << std::endl; + return; + } + + // Get current set position + Board pos(UCI_State::game.back()); + + // Get all legal moves + MoveList moves; + pos.moves(moves); + + // Setup counter for total nr. of moves + Index totalCount = 0; + + Board copy(pos); + for (Move move : moves) { + // continue traversing + pos.make(move); + Index nodeCount = Search::perft_subroutine(pos, depth - 1); + totalCount += nodeCount; + pos = copy; // unmake move + + // report moves of node + cout << move << ": " << nodeCount << std::endl; + } + + cout << std::endl << "Nodes searched: " << totalCount << std::endl; +} + +// [[Rcpp::export(rng = false)]] +Move go( + const int depth = 6 +) { + // Enforce a depth limit, this is very restrictive but we only want to + // use it as a toolbox and _not_ as a strong chess engine + if (8 < depth) { + Rcpp::stop("In `R` search is limited to depth 8"); + } + + // Setup search configuration + Search::State config; + config.depth = depth < 1 ? 1 : depth; + + // sets worker thread stop condition to false (before dispatch) which in this + // context is the main thread. Only need to ensure its running. + Search::isRunning.store(true, std::memory_order_release); + + // Construct a search object + Search::PVS search(UCI_State::game, config); + + // and start the search + search(); + + // return best move + return search.bestMove(); +} + +// [[Rcpp::export(rng = false)]] +void ucinewgame() { + Search::newgame(); + UCI_State::game.clear(); + UCI_State::game.emplace_back(); +} diff --git a/dataAnalysis/chess/Rchess/src/zzz.cpp b/dataAnalysis/chess/Rchess/src/zzz.cpp new file mode 100644 index 0000000..c1800c7 --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/zzz.cpp @@ -0,0 +1,21 @@ +// Startup and unload routines for initialization and cleanup, see: `R/zzz.R` + +#include + +#include "SchachHoernchen/search.h" + +// [[Rcpp::export(".onLoad")]] +void onLoad(const std::string& libname, const std::string& pkgname) { + // Initialize search (Transposition Table) + Search::init(); + // Report search initialization + Rcpp::Rcout << "info string search initialized" << std::endl; +} + +// [[Rcpp::export(".onUnload")]] +void onUnload(const std::string& libpath) { + // Cleanup any outstanding or running/finished worker tasks + Search::stop(); + // Report shutdown (stopping any remaining searches) + Rcpp::Rcout << "info string shutdown" << std::endl; +} diff --git a/dataAnalysis/chess/gmlm_chess.R b/dataAnalysis/chess/gmlm_chess.R new file mode 100644 index 0000000..840f867 --- /dev/null +++ b/dataAnalysis/chess/gmlm_chess.R @@ -0,0 +1,215 @@ +#' Specialized version of `gmlm_ising()`. +#' +#' Theroetically, equivalent to `gmlm_ising()` except the it uses a stochastic +#' gradient descent version of RMSprop instead of classic gradient descent. +#' Other differences are puerly of technical nature. +#' +#' @param data_gen data generator, samples from the data set conditioned on a +#' slice value `y.min` to `y.max`. Function signature +#' `function(batch.size, y.min, y.max)` with return value `X`, a +#' `8 x 8 x 12 x batch.size` 4D array. +#' @param fun_y known functions of scalar `y`, returning a 3D/4D tensor +#' @param score_breaks numeric vector of two or more unique cut points, the cut +#' points are the interval bounds specifying the slices of `y`. +#' @param nr_threads integer, nr. of threads used by `ising_m2()` +#' @param mcmc_samples integer, nr. of Monte-Carlo Chains passed to `ising_m2()` +#' @param slice_size integer, size of sub-samples generated by `data_gen` for +#' every slice. The batch size of the for every iteration is then equal to +#' `slice_size * (length(score_breaks) - 1L)`. +#' @param max_iter maximum number of iterations for gradient optimization +#' @param patience integer, break condition parameter. If the approximated loss +#' doesn't improve over `patience` iterations, then stop. +#' @param step_size numeric, meta parameter for RMSprop for gradient scaling +#' @param eps numeric, meta parameter for RMSprop avoiding divition by zero in +#' the parameter update rule of RMSprop +#' @param save_point character, file name pattern for storing and retrieving +#' optimization save points. Those save points allow to stop the method and +#' resume optimization later from the last save point. +#' +gmlm_chess <- function( + data_gen, + fun_y, + score_breaks = c(-5.0, -3.0, -2.0, -1.0, -0.5, -0.2, 0.2, 0.5, 1.0, 2.0, 3.0, 5.0), + nr_threads = 8L, + mcmc_samples = 10000L, + slice_size = 512L, + max_iter = 1000L, + patience = 25L, + step_size = 1e-3, + eps = sqrt(.Machine$double.eps), + save_point = "gmlm_chess_save_point_%s.Rdata" +) { + # build intervals from score break points + score_breaks <- sort(score_breaks) + score_min <- head(score_breaks, -1) + score_max <- tail(score_breaks, -1) + score_means <- (score_min + score_max) / 2 + + # build Omega constraint, that is the set of impossible combinations + # (including self interactions) due to the rules of chess + Omega_const <- local({ + # One piece per square + diag_offset <- abs(.row(c(768, 768)) - .col(c(768, 768))) + Omega_const <- !diag(768) & ((diag_offset %% 64L) == 0L) + # One King per color + Omega_const <- Omega_const | kronecker(diag(1:12 %in% c(6, 12)), !diag(64), `&`) + # no pawns on rank 1 or rank 8 + pawn_const <- tcrossprod(as.vector(`[<-`(matrix(0L, 8, 8), c(1, 8), , 1L)), rep(1L, 64)) + pawn_const <- kronecker(`[<-`(matrix(0, 12, 12), c(1, 7), , 1), pawn_const) + which(Omega_const | (pawn_const | t(pawn_const))) + }) + + # Check if there is a save point (load from save) + load_point <- if (is.character(save_point)) { + sort(list.files(pattern = sprintf(save_point, ".*")), decreasing = TRUE) + } else { + character(0) + } + # It a load point is found, resume from save point, otherwise initialize + if (length(load_point)) { + load_point <- load_point[[1]] + cat(sprintf("Resuming from save point '%s'\n", load_point), + "(to restart delete/rename the save points)\n") + load(load_point) + # Fix `iter`, save after increment + iter <- iter - 1L + } else { + # draw initial sample to be passed to the normal GMLM estimator for initial `betas` + X <- Reduce(c, Map(data_gen, slice_size, score_min, score_max)) + dim(X) <- c(8L, 8L, 12L, slice_size * length(score_means)) + F <- fun_y(rep(score_means, each = slice_size)) + + # set object dimensions (`dimX` is constant, `dimF` depends on `fun_y` arg) + dimX <- c(8L, 8L, 12L) + dimF <- dim(F)[1:3] + + # Initial values for `betas` are the tensor normal GMLM estimates + betas <- gmlm_tensor_normal(X, F)$betas + + # and initial values for `Omegas`, based on the same first "big" sample + Omegas <- Map(function(mode) { + n <- prod(dim(X)[-mode]) + prob2 <- mcrossprod(X, mode = mode) / n + prob2[prob2 == 0] <- 1 / n + prob2[prob2 == 1] <- (n - 1) / n + prob1 <- diag(prob2) + `prob1^2` <- outer(prob1, prob1) + + `diag<-`(log(((1 - `prob1^2`) / `prob1^2`) * prob2 / (1 - prob2)), 0) + }, 1:3) + + # Initial sample `(X, F)` no longer needed, remove + rm(X, F) + + # Initialize gradients and aggregated mean squared gradients + grad2_betas <- Map(array, 0, Map(dim, betas)) + grad2_Omegas <- Map(array, 0, Map(dim, Omegas)) + + # initialize optimization tracker for break condition + last_loss <- Inf + non_improving <- 0L + iter <- 0L + } + + # main optimization loop + while ((iter <- iter + 1L) <= max_iter) { + + # At beginning of every iteration, store current state in a save point. + # This allows to resume optimization from the last save point. + if (is.character(save_point)) { + suspendInterrupts(save( + dimX, dimF, + betas, Omegas, + grad2_betas, grad2_Omegas, + last_loss, non_improving, iter, + file = sprintf(save_point, sprintf("%06d", iter - 1L)))) + } + + # start timing for this iteration (this is precise enough) + start_time <- proc.time()[["elapsed"]] + + # full Omega (with constraint elements set to zero) needed to conditional + # parameters of the Ising model to compute (approx) the second moment + Omega <- `[<-`(Reduce(kronecker, rev(Omegas)), Omega_const, 0) + + # Gradient and negative log-likelihood approximation + loss <- 0 # neg. log-likelihood + grad_betas <- Map(matrix, 0, dimX, dimF) # grads for betas + R2 <- array(0, dim = c(dimX, dimX)) # residuals + + # for every score slice + for (i in seq_along(score_means)) { + # function of `y` being the score slice mean (only 3D, same for all obs.) + F <- `dim<-`(fun_y(score_means[i]), dimF) + + # compute parameters of (slice) conditional Ising model + params <- `diag<-`(Omega, as.vector(mlm(F, betas))) + + # second moment of `X | Y = score_means[i]` + m2 <- ising_m2(params, use_MC = TRUE, nr_threads = nr_threads, nr_samples = mcmc_samples) + + # draw random sample from current slice `vec(X) | Y in (score_min, score_max]` + # with columns being the vectorized observations `vec(X)`. + matX <- `dim<-`(data_gen(slice_size, score_min[i], score_max[i]), c(prod(dimX), slice_size)) + + # accumulate (approx) neg. log-likelihood + loss <- loss - (sum(matX * (params %*% matX)) + slice_size * attr(m2, "log_prob_0")) + + # Slice residuals (second order `resid2` and actual residuals `resid1`) + resid2 <- tcrossprod(matX) - slice_size * m2 + resid1 <- `dim<-`(diag(resid2), dimX) + + # accumulate residuals + R2 <- R2 + as.vector(resid2) + + # and the beta gradients + grad_betas <- Map(`+`, grad_betas, Map(function(mode) { + mcrossprod(resid1, mlm(slice_size * F, betas[-mode], (1:3)[-mode]), mode) + }, 1:3)) + } + + # finaly, finish gradient computation with gradients for `Omegas` + grad_Omegas <- Map(function(mode) { + grad <- mlm(kronperm(R2), Map(as.vector, Omegas[-mode]), (1:3)[-mode], transposed = TRUE) + `dim<-`(grad, dim(Omegas[[mode]])) + }, 1:3) + + # Update tracker for break condition + non_improving <- max(0L, non_improving - 1L + 2L * (last_loss < loss)) + loss_last <- loss + + # check break condition + if (non_improving > patience) { break } + + # accumulate root mean squared gradients + grad2_betas <- Map(function(g2, g) 0.9 * g2 + 0.1 * (g * g), grad2_betas, grad_betas) + grad2_Omegas <- Map(function(g2, g) 0.9 * g2 + 0.1 * (g * g), grad2_Omegas, grad_Omegas) + + # Update Parameters + betas <- Map(function(beta, grad, m2) { + beta + (step_size / (sqrt(m2) + eps)) * grad + }, betas, grad_betas, grad2_betas) + Omegas <- Map(function(Omega, grad, m2) { + Omega + (step_size / (sqrt(m2) + eps)) * grad + }, Omegas, grad_Omegas, grad2_Omegas) + + # Log progress + cat(sprintf("iter: %4d, time for iter: %d [s], loss: %f\n", + iter, round(proc.time()[["elapsed"]] - start_time), loss)) + } + + # Save a final (terminal) save point + if (is.character(save_point)) { + suspendInterrupts(save( + dimX, dimF, + betas, Omegas, + grad2_betas, grad2_Omegas, + last_loss, non_improving, iter, + file = sprintf(save_point, "final"))) + } + + structure( + list(betas = betas, Omegas = Omegas), + iter = iter, loss = loss + ) +} diff --git a/dataAnalysis/chess/pgn2fen.cpp b/dataAnalysis/chess/pgn2fen.cpp new file mode 100644 index 0000000..705f4e5 --- /dev/null +++ b/dataAnalysis/chess/pgn2fen.cpp @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include + +#include "utils.h" +#include "Board.h" +#include "Move.h" +#include "search.h" +#include "uci.h" + +static const std::string usage{"usage: pgn2fen [--scored] []"}; + +// Convert PGN (Portable Game Notation) input stream to single FENs +// streamed to stdout +void pgn2fen(std::istream& input, const bool only_scored) { + + // Instantiate Boards, the start of every game as well as the current state + // of the Board while processing a PGN game + Board startpos, pos; + + // Read input line by line + std::string line; + while (std::getline(input, line)) { + // Skip empty and metadata lines (every PGN game starts with ".") + if (line.empty() || line.front() == '[') { + continue; + } + + // Reset position to the start position, every game starts here! + pos = startpos; + + // Read game content (assuming one line is the entire game) + std::istringstream game(line); + std::string count, san, token, eval; + while (game >> count >> san >> token) { + // Consume/Parse PGN comments + if (only_scored) { + // consume the comment and search for an evaluation + bool has_score = false; + while (game >> token) { + // Search for evaluation token (position score _after_ the move) + if (token == "[%eval") { + game >> eval; + eval.pop_back(); // delete trailing ']' + has_score = true; + // Consume the remainder of the comment (ignore it) + std::getline(game, token, '}'); + break; + } else if (token == "}") { + break; + } + } + // In case of not finding an evaluation, skip the game (_not_ an error) + if (!has_score) { + break; + } + } else { + // Consume the remainder of the comment (ignore it) + std::getline(game, token, '}'); + } + + // Perform move + bool parseError = false; + Move move = UCI::parseSAN(san, pos, parseError); + if (parseError) { + std::cerr << "[ Error ] Parsing '" << san << "' at position '" + << pos.fen() << "' failed." << std::endl; + } + move = pos.isLegal(move); // validate legality and extend move info + if (move) { + pos.make(move); + } else { + std::cerr << "[ Error ] Encountered illegal move '" << san + << " (" << move + << ") ' at position '" << pos.fen() << "'." << std::endl; + break; + } + + // Write positions + if (only_scored) { + // Ingore "check mate in" scores (not relevant for eval training) + // Do this after "make move" in situations where the check mate + // was overlooked, leading to new positions + if (eval.length() && eval[0] == '#') { + continue; + } + // Otherwise, classic eval score to be parsed in centipawns + std::cout << pos.fen() << "; " << eval << '\n'; + } else { + // Write only the position FEN + std::cout << pos.fen() << '\n'; + } + } + } +} + + +int main(int argn, char* argv[]) { + // Setup control variables + bool only_scored = false; + std::string file = ""; + + // Parse command arguments + switch (argn) { + case 1: + break; + case 2: + if (std::string("--scored") == argv[1]) { + only_scored = true; + } else { + file = argv[1]; + } + break; + case 3: + if (std::string("--scored") != argv[1]) { + std::cout << usage << std::endl; + return 1; + } + only_scored = true; + file = argv[2]; + break; + default: + std::cout << usage << std::endl; + return 1; + } + + // Invoke converter ether with file input or stdin + if (file == "") { + pgn2fen(std::cin, only_scored); + } else { + // Open input file + std::ifstream input(file); + if (!input) { + std::cerr << "Error opening '" << file << "'" << std::endl; + return 1; + } + + pgn2fen(input, only_scored); + } + + return 0; +} diff --git a/dataAnalysis/chess/preprocessing.sh b/dataAnalysis/chess/preprocessing.sh new file mode 100755 index 0000000..afa8a82 --- /dev/null +++ b/dataAnalysis/chess/preprocessing.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Data set name: Chess games from the Lichess Data Base for standard rated games +# in November 2023 +data=lichess_db_standard_rated_2023-11 + +# Check if file exists and download iff not +if [ -f "${data}.fen" ]; then + echo "File '${data}.fen' already exists, assuming job already done." + echo "To rerun delete (rename) the files '${data}.pgn.zst' and/or '${data}.fen'" +else + # First, compile `png2fen` + make pgn2fen + + # Download the PGN data base via `wegt` if not found. + # The flag `-q` suppresses `wget`s own output and `-O-` tells `wget` to + # stream the downloaded file to `stdout`. + # Otherwise, use the file on disk directly. + # Decompress the stream with `zstdcat` (no temporary files) + # The uncompressed PGN data is then piped into `pgn2fen` which converts + # the PGN data base into a list of FEN strings while filtering only + # positions with evaluation. The `--scored` parameter specifies to extract + # a position evaluation from the PGN and ONLY write positions with scores. + # That is, positions without a score are removed! + if [ -f "${data}.pgn.zst" ]; then + zstdcat ${data}.pgn.zst | ./pgn2fen --scored > ${data}.fen + else + wget -qO- https://database.lichess.org/standard/${data}.pgn.zst \ + | zstdcat | ./pgn2fen --scored > ${data}.fen + fi +fi