add: chess data example with Rchess
This commit is contained in:
parent
4b4b30ceb0
commit
6792cf93a9
|
@ -43,10 +43,11 @@
|
||||||
*.idb
|
*.idb
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
## R environment, data and pacakge build intermediate files/folders
|
## R environment, data and package build intermediate files/folders
|
||||||
# R Data Object files
|
# R Data Object files
|
||||||
*.Rds
|
*.Rds
|
||||||
*.rds
|
*.rds
|
||||||
|
*.Rdata
|
||||||
|
|
||||||
# Example code in package build process
|
# Example code in package build process
|
||||||
*-Ex.R
|
*-Ex.R
|
||||||
|
@ -110,7 +111,10 @@ simulations/
|
||||||
|
|
||||||
mlda_analysis/
|
mlda_analysis/
|
||||||
References/
|
References/
|
||||||
dataAnalysis/
|
dataAnalysis/*
|
||||||
|
!dataAnalysis/chess/
|
||||||
|
dataAnalysis/chess/*.fen
|
||||||
|
|
||||||
*.csv
|
*.csv
|
||||||
*.csv.log
|
*.csv.log
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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 <your@email.com>
|
||||||
|
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
|
|
@ -0,0 +1,4 @@
|
||||||
|
useDynLib(Rchess, .registration=TRUE)
|
||||||
|
importFrom(Rcpp, evalCpp)
|
||||||
|
exportPattern("^[[:alpha:]]+")
|
||||||
|
S3method(print, board)
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
#ifndef INCLUDE_GUARD_RCHESS_TYPES
|
||||||
|
#define INCLUDE_GUARD_RCHESS_TYPES
|
||||||
|
|
||||||
|
#include <RcppCommon.h>
|
||||||
|
|
||||||
|
#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 <Rcpp.h>
|
||||||
|
|
||||||
|
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<std::string>(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<std::string>(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<Board> as(SEXP obj) {
|
||||||
|
// Convert SEXP to be a vector of string
|
||||||
|
auto fens = Rcpp::as<std::vector<std::string>>(obj);
|
||||||
|
// Try to parse every string as a Board from a FEN
|
||||||
|
std::vector<Board> 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 */
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,224 @@
|
||||||
|
#ifndef INCLUDE_GUARD_BOARD_H
|
||||||
|
#define INCLUDE_GUARD_BOARD_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
#include <functional> // 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<Index>(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<bool>(_plyCount % 2); }
|
||||||
|
enum piece sideToMove() const {
|
||||||
|
return static_cast<enum piece>(!(_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<u64, 781> hashTable = []() {
|
||||||
|
std::array<u64, 781> 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<Board> into standard name space
|
||||||
|
namespace std {
|
||||||
|
template <>
|
||||||
|
struct hash<Board> {
|
||||||
|
using result_type = u64;
|
||||||
|
|
||||||
|
u64 operator()(const Board& board) const noexcept {
|
||||||
|
return board.hash();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* INCLUDE_GUARD_BOARD_H */
|
|
@ -0,0 +1,150 @@
|
||||||
|
#ifndef UCI_GUARD_HASHTABLE_H
|
||||||
|
#define UCI_GUARD_HASHTABLE_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include <utility> // std::pair
|
||||||
|
#include <functional> // std::hash
|
||||||
|
#include <type_traits>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
// Default policy; replace everything with the newest entry
|
||||||
|
template <typename T>
|
||||||
|
struct policy {
|
||||||
|
constexpr bool operator()(const T&, const T&) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Key,
|
||||||
|
typename Entry,
|
||||||
|
typename Policy = policy<Entry>,
|
||||||
|
typename Hashing = std::hash<Key>
|
||||||
|
>
|
||||||
|
class HashTable {
|
||||||
|
public:
|
||||||
|
using Hash = typename Hashing::result_type;
|
||||||
|
using Line = typename std::pair<Hash, Entry>;
|
||||||
|
|
||||||
|
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<Hash>(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<Hash>(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<Hash>(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<Index>(hash) % _size;
|
||||||
|
if (_table[index].first == static_cast<Hash>(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<Hash>(0)) {
|
||||||
|
return Iter(_table + _size);
|
||||||
|
}
|
||||||
|
Index index = static_cast<Index>(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 */
|
|
@ -0,0 +1,305 @@
|
||||||
|
#ifndef INCLUDE_GUARD_MOVE_H
|
||||||
|
#define INCLUDE_GUARD_MOVE_H
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#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<enum piece>(0)) { };
|
||||||
|
explicit Move(Index from, Index to, enum piece promotion)
|
||||||
|
: Move(static_cast<enum piece>(0), static_cast<enum piece>(0), from, to,
|
||||||
|
static_cast<enum piece>(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<enum piece>(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<base_type>(
|
||||||
|
( 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<enum piece>((_bits >> 12) & 7U);
|
||||||
|
}
|
||||||
|
enum piece piece() const {
|
||||||
|
return static_cast<enum piece>((_bits >> 15) & 7U);
|
||||||
|
}
|
||||||
|
enum piece victim() const {
|
||||||
|
return static_cast<enum piece>((_bits >> 18) & 7U);
|
||||||
|
}
|
||||||
|
enum piece color() const {
|
||||||
|
return static_cast<enum piece>((_bits >> 21) & 7U);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t score() const {
|
||||||
|
return static_cast<uint32_t>(_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<base_type>(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<bool>(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 <from><to>[<promotion>] 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<bool>(_size); };
|
||||||
|
static constexpr unsigned max_size() { return _max_size; };
|
||||||
|
void clear() { _size = 0; };
|
||||||
|
Move* data() { return _moves; };
|
||||||
|
const Move* data() const { return _moves; };
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void emplace_back(Args&&... args) {
|
||||||
|
assert(_size < _max_size);
|
||||||
|
|
||||||
|
_moves[_size++] = Move(std::forward<Args>(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 */
|
|
@ -0,0 +1,63 @@
|
||||||
|
#ifndef INCLUDE_GUARD_BIT_H
|
||||||
|
#define INCLUDE_GUARD_BIT_H
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
// Polyfill for `<bit>`, 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 <typename T>
|
||||||
|
constexpr T byteswap(T x) noexcept {
|
||||||
|
#ifdef __GNUC__
|
||||||
|
if constexpr (sizeof(T) == 8) {
|
||||||
|
uint64_t swapped = __builtin_bswap64(*reinterpret_cast<uint64_t*>(&x));
|
||||||
|
return *reinterpret_cast<double*>(&swapped);
|
||||||
|
} else if constexpr (sizeof(T) == 4) {
|
||||||
|
uint32_t swapped = __builtin_bswap32(*reinterpret_cast<uint32_t*>(&x));
|
||||||
|
return *reinterpret_cast<double*>(&swapped);
|
||||||
|
} else if constexpr (sizeof(T) == 2) {
|
||||||
|
uint16_t swapped = __builtin_bswap16(*reinterpret_cast<uint16_t*>(&x));
|
||||||
|
return *reinterpret_cast<double*>(&swapped);
|
||||||
|
} else {
|
||||||
|
static_assert(sizeof(T) == 1);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if constexpr (sizeof(T) == 8) {
|
||||||
|
uint64_t swapped = *reinterpret_cast<uint64_t*>(&x);
|
||||||
|
swapped = (((swapped & static_cast<uint64_t>(0xFF00FF00FF00FF00ULL)) >> 8)
|
||||||
|
| ((swapped & static_cast<uint64_t>(0x00FF00FF00FF00FFULL)) << 8));
|
||||||
|
swapped = (((swapped & static_cast<uint64_t>(0xFFFF0000FFFF0000ULL)) >> 16)
|
||||||
|
| ((swapped & static_cast<uint64_t>(0x0000FFFF0000FFFFULL)) << 16));
|
||||||
|
swapped = ((swapped >> 32) | (swapped << 32));
|
||||||
|
return *reinterpret_cast<T*>(&swapped);
|
||||||
|
} else if constexpr (sizeof(T) == 4) {
|
||||||
|
uint32_t swapped = *reinterpret_cast<uint32_t*>(&x);
|
||||||
|
swapped = (((swapped & static_cast<uint32_t>(0xFF00FF00)) >> 8)
|
||||||
|
| ((swapped & static_cast<uint32_t>(0x00FF00FF)) << 8));
|
||||||
|
swapped = ((swapped >> 16) | (swapped << 16));
|
||||||
|
return *reinterpret_cast<T*>(&swapped);
|
||||||
|
} else if constexpr (sizeof(T) == 2) {
|
||||||
|
uint16_t swapped = *reinterpret_cast<uint16_t*>(&x);
|
||||||
|
swapped = ((swapped << 8) | (swapped >> 8));
|
||||||
|
return *reinterpret_cast<T*>(&swapped);
|
||||||
|
} else {
|
||||||
|
static_assert(sizeof(T) == 1);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace std */
|
||||||
|
|
||||||
|
#endif /* INCLUDE_GUARD_BIT_H */
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef INCLUDE_GUARD_NULLSTREAM_H
|
||||||
|
#define INCLUDE_GUARD_NULLSTREAM_H
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
class nullstream : public std::ostream {
|
||||||
|
public:
|
||||||
|
nullstream() : std::ostream(nullptr) { }
|
||||||
|
nullstream(const nullstream&) : std::ostream(nullptr) { }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
nullstream& operator<<(const T& rhs) { return *this; }
|
||||||
|
|
||||||
|
nullstream& operator<<(std::ostream& (*fn)(std::ostream&)) { return *this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* INCLUDE_GUARD_NULLSTREAM_H */
|
|
@ -0,0 +1,700 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#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<Board, TTEntry, TTPolicy> tt;
|
||||||
|
std::atomic<bool> isRunning;
|
||||||
|
std::vector<std::thread> workers;
|
||||||
|
|
||||||
|
// Set initial static age of search
|
||||||
|
template <typename Evaluator>
|
||||||
|
unsigned PVS<Evaluator>::_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<Board, TTEntry, TTPolicy>::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<Board, TTEntry, TTPolicy>::Line);
|
||||||
|
// compute size in MB from number of lines (size)
|
||||||
|
return static_cast<double>(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 <typename Evaluator>
|
||||||
|
PVS<Evaluator>::PVS(const std::vector<Board>& 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<int>(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 <typename Evaluator>
|
||||||
|
Score PVS<Evaluator>::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<Score>::lower();
|
||||||
|
beta = limits<Score>::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<Score>::lower() - 1);
|
||||||
|
} else if (beta <= score) {
|
||||||
|
beta = std::max(prevScore + delta, limits<Score>::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<milliseconds>(now - _start_time);
|
||||||
|
// Check if found mate and report mate instead of centi pawn score
|
||||||
|
if (limits<Score>::upper() <= (std::abs(score) + depth)) {
|
||||||
|
// Note: +-1 for converting 0-indexed ply count to a 1-indexed
|
||||||
|
// move count
|
||||||
|
if (score < 0) {
|
||||||
|
score = limits<Score>::lower() - score - 1;
|
||||||
|
} else {
|
||||||
|
score = limits<Score>::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<milliseconds>(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 <class Evaluator>
|
||||||
|
Score PVS<Evaluator>::qSearch(const PVS<Evaluator>::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<Score>::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 <class Evaluator>
|
||||||
|
Score PVS<Evaluator>::pvs(const PVS<Evaluator>::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<Score>::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<bool>(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<Score>::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<Board>& 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<Board>(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<Board>;
|
||||||
|
|
||||||
|
} /* namespace Search */
|
|
@ -0,0 +1,203 @@
|
||||||
|
#ifndef INCLUDE_GUARD_SEARCH_H
|
||||||
|
#define INCLUDE_GUARD_SEARCH_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#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<bool> 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 CRTP>
|
||||||
|
class GameState : public Board {
|
||||||
|
public:
|
||||||
|
GameState(const Board& pos) : Board(pos) { };
|
||||||
|
|
||||||
|
Score eval() { return static_cast<CRTP*>(this)->eval(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized CRTP derived HCE Board evaluation position
|
||||||
|
*/
|
||||||
|
class BoardState : public GameState<BoardState> {
|
||||||
|
public:
|
||||||
|
BoardState(const Board& pos) : GameState<BoardState>(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<Board>`
|
||||||
|
* or the NNUE evaluation `PVS<NNUE>`.
|
||||||
|
*
|
||||||
|
* @note The NNUE evaluation is removed from this code base!
|
||||||
|
*/
|
||||||
|
template <typename Evaluator>
|
||||||
|
class PVS {
|
||||||
|
public:
|
||||||
|
PVS(const std::vector<Board>&, 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<Board>& 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 */
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef INCLUDE_GUARD_SYNCSTREAM_H
|
||||||
|
#define INCLUDE_GUARD_SYNCSTREAM_H
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
class osyncstream {
|
||||||
|
public:
|
||||||
|
osyncstream(std::ostream& base_ostream)
|
||||||
|
: _base_ostream(base_ostream)
|
||||||
|
{
|
||||||
|
mtx().lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
~osyncstream() {
|
||||||
|
mtx().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
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 */
|
|
@ -0,0 +1,195 @@
|
||||||
|
#ifndef INCLUDE_GUARD_TYPES_H
|
||||||
|
#define INCLUDE_GUARD_TYPES_H
|
||||||
|
|
||||||
|
#include <cstdint> // uint64_t
|
||||||
|
#include <limits> // 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 <typename T>
|
||||||
|
struct limits {
|
||||||
|
static constexpr T upper();
|
||||||
|
static constexpr T lower();
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct limits<Score> {
|
||||||
|
static constexpr Score upper() { return static_cast<Score>(+32768); };
|
||||||
|
static constexpr Score lower() { return static_cast<Score>(-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 <Rcpp.h>
|
||||||
|
// Set I/O streams to Rcpp I/O streams
|
||||||
|
static Rcpp::Rostream<true> cout;
|
||||||
|
static Rcpp::Rostream<false> cerr;
|
||||||
|
#elif NULLSTREAM
|
||||||
|
#include "nullstream.h"
|
||||||
|
// Set I/O streams to "null"
|
||||||
|
static nullstream cout;
|
||||||
|
static nullstream cerr;
|
||||||
|
#else
|
||||||
|
#include <iostream>
|
||||||
|
// 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 */
|
|
@ -0,0 +1,964 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <tuple>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "Move.h"
|
||||||
|
#include "Board.h"
|
||||||
|
#include "uci.h"
|
||||||
|
#include "search.h"
|
||||||
|
#if __cplusplus > 201703L
|
||||||
|
#include <syncstream>
|
||||||
|
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<char>('a' + (move.from() % 8))
|
||||||
|
<< static_cast<char>('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<char>('a' + (move.to() % 8))
|
||||||
|
<< static_cast<char>('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<Index>(64);
|
||||||
|
}
|
||||||
|
if (str[0] < 'a' || 'h' < str[0] || str[1] < '1' || '8' < str[1]) {
|
||||||
|
parseError = true;
|
||||||
|
return static_cast<Index>(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
Index fileIndex = static_cast<Index>(str[0] - 'a');
|
||||||
|
Index rankIndex = static_cast<Index>('8' - str[1]);
|
||||||
|
|
||||||
|
return 8 * rankIndex + fileIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Move parseMove(const std::string& str, bool& parseError) {
|
||||||
|
// check length <from><to> = 4 plus 1 if promotion
|
||||||
|
if ((str.length() < 4) || (5 < str.length())) {
|
||||||
|
parseError = true;
|
||||||
|
return Move(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse <from><to> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// <SAN piece moves> ::= <Piece symbol>[<from file>|<from rank>|<from square>]['x']<to square>
|
||||||
|
// <SAN pawn captures> ::= <from file>[<from rank>] 'x' <to square>[<promoted to>]
|
||||||
|
// <SAN pawn push> ::= <to square>[<promoted to>]
|
||||||
|
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<Down>(bitMask<Square>(toSq), ~pos.bb(pawn));
|
||||||
|
from &= pos.bb(white);
|
||||||
|
} else {
|
||||||
|
from = fill7dump<Up>(bitMask<Square>(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<File>(static_cast<Index>(str[0] - 'a'));
|
||||||
|
} else {
|
||||||
|
parseError = true;
|
||||||
|
return Move(0);
|
||||||
|
}
|
||||||
|
if (color == white) {
|
||||||
|
from &= shift<Down>(bitMask<Rank>(toSq));
|
||||||
|
from &= pos.bb(white) & pos.bb(pawn);
|
||||||
|
} else {
|
||||||
|
from &= shift<Up>(bitMask<Rank>(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<Square>(toSq);
|
||||||
|
u64 from = pos.bb(color) & pos.bb(piece);
|
||||||
|
u64 empty = ~(pos.bb(white) | pos.bb(black));
|
||||||
|
switch(piece) {
|
||||||
|
case queen:
|
||||||
|
from &= fill7dump<Left> (to, empty)
|
||||||
|
| fill7dump<Right> (to, empty)
|
||||||
|
| fill7dump<Up> (to, empty)
|
||||||
|
| fill7dump<Down> (to, empty)
|
||||||
|
| fill7dump<LeftUp> (to, empty)
|
||||||
|
| fill7dump<RightUp> (to, empty)
|
||||||
|
| fill7dump<LeftDown> (to, empty)
|
||||||
|
| fill7dump<RightDown>(to, empty);
|
||||||
|
break;
|
||||||
|
case rook:
|
||||||
|
from &= fill7dump<Left> (to, empty)
|
||||||
|
| fill7dump<Right>(to, empty)
|
||||||
|
| fill7dump<Up> (to, empty)
|
||||||
|
| fill7dump<Down> (to, empty);
|
||||||
|
break;
|
||||||
|
case bishop:
|
||||||
|
from &= fill7dump<LeftUp> (to, empty)
|
||||||
|
| fill7dump<RightUp> (to, empty)
|
||||||
|
| fill7dump<LeftDown> (to, empty)
|
||||||
|
| fill7dump<RightDown>(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<Rank>(8 * static_cast<Index>('8' - part[1]));
|
||||||
|
} else if (('a' <= part[1]) && (part[1] <= 'h')) {
|
||||||
|
from &= bitMask<File>(static_cast<Index>(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<Square>(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<Rank>(8 * static_cast<Index>('8' - part[1]));
|
||||||
|
} else if (('a' <= part[1]) && (part[1] <= 'h')) {
|
||||||
|
from &= bitMask<File>(static_cast<Index>(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<Square>(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<char>('a' + (board.enPassant() % 8))
|
||||||
|
<< static_cast<char>('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 <fenstring> | startpos ] moves <move1> <move2> .... <moveN>
|
||||||
|
// \______________________ remainder in cmd ______________________/
|
||||||
|
void position(std::vector<Board>& 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<Board> 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 <cmd>
|
||||||
|
// with <cmd>;
|
||||||
|
// ...
|
||||||
|
// TODO: implement
|
||||||
|
//
|
||||||
|
void go(std::vector<Board>& 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<Board>& 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<Board>& 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 <move1> [<move2> ...]]\n"
|
||||||
|
" position fen <fen> [moves <move1> [<move2> ...]]\n"
|
||||||
|
" position this moves <move1> [<move2> ...]\n"
|
||||||
|
" go\n"
|
||||||
|
" go perft <depth>\n"
|
||||||
|
" go [depth <depth>] [searchmoves <move> [<move> ...]]\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 <size>\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 */
|
|
@ -0,0 +1,103 @@
|
||||||
|
#ifndef UCI_GUARD_MOVE_H
|
||||||
|
#define UCI_GUARD_MOVE_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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 `<from><to>[<promotion>]` 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<Board>&, std::istream&, bool&);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handles `go ...` commands. Starts searches, performance tests, ...
|
||||||
|
*/
|
||||||
|
void go(std::vector<Board>&, std::istream&, bool&);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes/Parses input from stdin and dispatches appropriate command handler.
|
||||||
|
*/
|
||||||
|
void stdin_listen(std::vector<Board>& game);
|
||||||
|
|
||||||
|
} /* namespace UCI */
|
||||||
|
|
||||||
|
#endif /* UCI_GUARD_MOVE_H */
|
|
@ -0,0 +1,698 @@
|
||||||
|
#ifndef INCLUDE_GUARD_UTILS_H
|
||||||
|
#define INCLUDE_GUARD_UTILS_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib> // for strto* (instead of std::strto*)
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Helpfull in debugging
|
||||||
|
template <typename T>
|
||||||
|
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<T>(1) << index) & mask ? '1' : '.');
|
||||||
|
}
|
||||||
|
if (sep) out << sep;
|
||||||
|
}
|
||||||
|
if (sepByte && (i < (sizeof(T) - 1))) out << sepByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline std::string bits(T mask, char sepByte = 0, char sep = 0,
|
||||||
|
Index mark = -1, char marker = 'X'
|
||||||
|
) {
|
||||||
|
std::string str(rbits<T>(mask, sepByte, sep, mark, marker));
|
||||||
|
return std::string(str.rbegin(), str.rend());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<std::string> split(const std::string& str, char delim = ' ') {
|
||||||
|
std::vector<std::string> 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<std::string> parse(const std::string& str, const std::string& format) {
|
||||||
|
std::vector<std::string> 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<std::string> lLines = split(lhs, '\n');
|
||||||
|
std::vector<std::string> 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 <typename... Args>
|
||||||
|
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 <typename T>
|
||||||
|
T parseUnsigned(const std::string& str, bool& parseError) {
|
||||||
|
if (str.empty()) {
|
||||||
|
parseError = true;
|
||||||
|
return static_cast<T>(-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<T>(-1);
|
||||||
|
}
|
||||||
|
if (*end != '\0') {
|
||||||
|
parseError = true;
|
||||||
|
return static_cast<T>(-1);
|
||||||
|
}
|
||||||
|
// check overflow (using complement trick for unsigned integer types)
|
||||||
|
if (num > static_cast<unsigned long long>(static_cast<T>(-1))) {
|
||||||
|
parseError = true;
|
||||||
|
return static_cast<T>(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<T>(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <enum location>
|
||||||
|
constexpr u64 bitMask();
|
||||||
|
|
||||||
|
template <enum location>
|
||||||
|
constexpr u64 bitMask(Index);
|
||||||
|
|
||||||
|
template <enum location L>
|
||||||
|
inline constexpr u64 bitMask(Index file, Index rank) {
|
||||||
|
return bitMask<L>(8 * rank + file);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<Square>(Index sq) {
|
||||||
|
return static_cast<u64>(1) << sq;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<File>(Index sq) {
|
||||||
|
return static_cast<u64>(0x0101010101010101) << (sq & 7);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<Rank>(Index sq) {
|
||||||
|
return static_cast<u64>(0xFF) << (sq & 56);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<Left>(Index sq) {
|
||||||
|
return bitMask<Rank>(sq) & (bitMask<Square>(sq) - 1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<Right>(Index sq) {
|
||||||
|
return bitMask<Rank>(sq) & (static_cast<u64>(-2) << sq);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<Up>(Index sq) {
|
||||||
|
return bitMask<File>(sq) & (bitMask<Square>(sq) - 1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<Down>(Index sq) {
|
||||||
|
return bitMask<File>(sq) & (static_cast<u64>(-2) << sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<Diag>(Index sq) {
|
||||||
|
const u64 diag = static_cast<u64>(0x8040201008040201);
|
||||||
|
int offset = 8 * static_cast<int>(sq & 7) - static_cast<int>(sq & 56);
|
||||||
|
int nort = -offset & ( offset >> 31);
|
||||||
|
int sout = offset & (-offset >> 31);
|
||||||
|
return (diag >> sout) << nort;
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<AntiDiag>(Index sq) {
|
||||||
|
const u64 diag = static_cast<u64>(0x0102040810204080);
|
||||||
|
int offset = 56 - 8 * static_cast<int>(sq & 7) - static_cast<int>(sq & 56);
|
||||||
|
int nort = -offset & ( offset >> 31);
|
||||||
|
int sout = offset & (-offset >> 31);
|
||||||
|
return (diag >> sout) << nort;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<WhiteSquares>() {
|
||||||
|
return 0x55AA55AA55AA55AA;
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 bitMask<BlackSquares>() {
|
||||||
|
return 0xAA55AA55AA55AA55;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shifts with respect to "off board" shifting
|
||||||
|
*
|
||||||
|
* bits shift<Left>(bits) shift<Up>(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 <enum location>
|
||||||
|
constexpr u64 shift(u64);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 shift<Left>(u64 bits) {
|
||||||
|
return (bits >> 1) & ~bitMask<File>(h1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 shift<Right>(u64 bits) {
|
||||||
|
return (bits << 1) & ~bitMask<File>(a1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 shift<Down>(u64 bits) {
|
||||||
|
return bits << 8;
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 shift<Up>(u64 bits) {
|
||||||
|
return bits >> 8;
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 shift<RightUp>(u64 bits) {
|
||||||
|
return (bits >> 7) & ~bitMask<File>(a1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 shift<RightDown>(u64 bits) {
|
||||||
|
return (bits << 9) & ~bitMask<File>(a1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 shift<LeftUp>(u64 bits) {
|
||||||
|
return (bits >> 9) & ~bitMask<File>(h1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 shift<LeftDown>(u64 bits) {
|
||||||
|
return (bits << 7) & ~bitMask<File>(h1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills (including) start square till the end of the board.
|
||||||
|
*/
|
||||||
|
template <enum location>
|
||||||
|
constexpr u64 fill(u64);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bits fill<File>(bits) fill<Rank>(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<Up>(u64 bits) {
|
||||||
|
bits |= bits >> 8;
|
||||||
|
bits |= bits >> 16;
|
||||||
|
return bits | (bits >> 32);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 fill<Down>(u64 bits) {
|
||||||
|
bits |= bits << 8;
|
||||||
|
bits |= bits << 16;
|
||||||
|
return bits | (bits << 32);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 fill<Left>(u64 bits) {
|
||||||
|
bits |= ((bits & 0xFEFEFEFEFEFEFEFE) >> 1);
|
||||||
|
bits |= ((bits & 0xFCFCFCFCFCFCFCFC) >> 2);
|
||||||
|
return bits | ((bits & 0xF0F0F0F0F0F0F0F0) >> 4);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 fill<Right>(u64 bits) {
|
||||||
|
bits |= ((bits & 0x7F7F7F7F7F7F7F7F) << 1);
|
||||||
|
bits |= ((bits & 0x3F3F3F3F3F3F3F3F) << 2);
|
||||||
|
return bits | ((bits & 0x0F0F0F0F0F0F0F0F) << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 fill<File>(u64 bits) {
|
||||||
|
return fill<Up>(bits) | fill<Down>(bits);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline constexpr u64 fill<Rank>(u64 bits) {
|
||||||
|
return fill<Left>(bits) | fill<Right>(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attack fill (excludes the attacker but includes blocking pieces)
|
||||||
|
* see: https://www.chessprogramming.org/Dumb7Fill
|
||||||
|
*/
|
||||||
|
template <enum location>
|
||||||
|
inline u64 fill7dump(u64, u64);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline u64 fill7dump<Left>(u64 attacker, u64 empty) {
|
||||||
|
u64 flood = attacker;
|
||||||
|
empty &= ~bitMask<File>(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<File>(h1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline u64 fill7dump<Right>(u64 attacker, u64 empty) {
|
||||||
|
u64 flood = attacker;
|
||||||
|
empty &= ~bitMask<File>(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<File>(a1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline u64 fill7dump<Down>(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<Up>(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<RightUp>(u64 attacker, u64 empty) {
|
||||||
|
u64 flood = attacker;
|
||||||
|
empty &= ~bitMask<File>(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<File>(a1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline u64 fill7dump<RightDown>(u64 attacker, u64 empty) {
|
||||||
|
u64 flood = attacker;
|
||||||
|
empty &= ~bitMask<File>(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<File>(a1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline u64 fill7dump<LeftUp>(u64 attacker, u64 empty) {
|
||||||
|
u64 flood = attacker;
|
||||||
|
empty &= ~bitMask<File>(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<File>(h1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline u64 fill7dump<LeftDown>(u64 attacker, u64 empty) {
|
||||||
|
u64 flood = attacker;
|
||||||
|
empty &= ~bitMask<File>(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<File>(h1);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline u64 fill7dump<Plus>(u64 attacker, u64 empty) {
|
||||||
|
return fill7dump<Up> (attacker, empty)
|
||||||
|
| fill7dump<Down> (attacker, empty)
|
||||||
|
| fill7dump<Left> (attacker, empty)
|
||||||
|
| fill7dump<Right>(attacker, empty);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline u64 fill7dump<Cross>(u64 attacker, u64 empty) {
|
||||||
|
return fill7dump<LeftUp> (attacker, empty)
|
||||||
|
| fill7dump<LeftDown> (attacker, empty)
|
||||||
|
| fill7dump<RightUp> (attacker, empty)
|
||||||
|
| fill7dump<RightDown>(attacker, empty);
|
||||||
|
}
|
||||||
|
template <>
|
||||||
|
inline u64 fill7dump<Star>(u64 attacker, u64 empty) {
|
||||||
|
return fill7dump<Plus> (attacker, empty)
|
||||||
|
| fill7dump<Cross>(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<int>(rankIndex(sq1));
|
||||||
|
int r2 = static_cast<int>(rankIndex(sq2));
|
||||||
|
|
||||||
|
int f1 = static_cast<int>(fileIndex(sq1));
|
||||||
|
int f2 = static_cast<int>(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<Rank>(sq1); }
|
||||||
|
if (f1 == f2) { return bitMask<File>(sq1); }
|
||||||
|
if ((r1 - r2) == (f1 - f2)) { return bitMask<Diag>(sq1); }
|
||||||
|
if ((r1 - r2) == (f2 - f1)) { return bitMask<AntiDiag>(sq1); }
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
|
||||||
|
return bitMask<Square>(sq1) | bitMask<Square>(sq2);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline u64 bitReverse(u64 x) {
|
||||||
|
x = (((x & static_cast<u64>(0xAAAAAAAAAAAAAAAA)) >> 1)
|
||||||
|
| ((x & static_cast<u64>(0x5555555555555555)) << 1));
|
||||||
|
x = (((x & static_cast<u64>(0xCCCCCCCCCCCCCCCC)) >> 2)
|
||||||
|
| ((x & static_cast<u64>(0x3333333333333333)) << 2));
|
||||||
|
x = (((x & static_cast<u64>(0xF0F0F0F0F0F0F0F0)) >> 4)
|
||||||
|
| ((x & static_cast<u64>(0x0F0F0F0F0F0F0F0F)) << 4));
|
||||||
|
#ifdef __GNUC__
|
||||||
|
return __builtin_bswap64(x);
|
||||||
|
#else
|
||||||
|
x = (((x & static_cast<u64>(0xFF00FF00FF00FF00)) >> 8)
|
||||||
|
| ((x & static_cast<u64>(0x00FF00FF00FF00FF)) << 8));
|
||||||
|
x = (((x & static_cast<u64>(0xFFFF0000FFFF0000)) >> 16)
|
||||||
|
| ((x & static_cast<u64>(0x0000FFFF0000FFFF)) << 16));
|
||||||
|
return((x >> 32) | (x << 32));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template <enum location>
|
||||||
|
inline u64 bitFlip(u64 x);
|
||||||
|
|
||||||
|
// Reverses byte order, aka rank 8 <-> rank 1, rank 7 <-> rank 2, ...
|
||||||
|
template <>
|
||||||
|
inline u64 bitFlip<Rank>(u64 x) {
|
||||||
|
#ifdef __GNUC__
|
||||||
|
return __builtin_bswap64(x);
|
||||||
|
#elif
|
||||||
|
x = (((x & static_cast<u64>(0xFF00FF00FF00FF00)) >> 8)
|
||||||
|
| ((x & static_cast<u64>(0x00FF00FF00FF00FF)) << 8));
|
||||||
|
x = (((x & static_cast<u64>(0xFFFF0000FFFF0000)) >> 16)
|
||||||
|
| ((x & static_cast<u64>(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<File> (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<Diag>(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<AntiDiag>(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<u64>(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 */
|
|
@ -0,0 +1,4 @@
|
||||||
|
PKG_CXXFLAGS += -I'../inst/include' -pthread -DRCPP_RCOUT
|
||||||
|
|
||||||
|
SOURCES = $(wildcard *.cpp) $(wildcard ../inst/include/SchachHoernchen/*.cpp)
|
||||||
|
OBJECTS = $(SOURCES:.cpp=.o)
|
|
@ -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 <Rcpp.h>
|
||||||
|
|
||||||
|
using namespace Rcpp;
|
||||||
|
|
||||||
|
#ifdef RCPP_USE_GLOBAL_ROSTREAM
|
||||||
|
Rcpp::Rostream<true>& Rcpp::Rcout = Rcpp::Rcpp_cout_get();
|
||||||
|
Rcpp::Rostream<false>& 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<Board>& boards);
|
||||||
|
RcppExport SEXP _Rchess_fen2int(SEXP boardsSEXP) {
|
||||||
|
BEGIN_RCPP
|
||||||
|
Rcpp::RObject rcpp_result_gen;
|
||||||
|
Rcpp::traits::input_parameter< const std::vector<Board>& >::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<Board> 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<std::string>& 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<std::string>& >::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);
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <Rcpp.h>
|
||||||
|
|
||||||
|
#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<std::streamsize>::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<std::streamsize>::max(), '\n');
|
||||||
|
if (input.eof()) {
|
||||||
|
input.seekg(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sample;
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
#include <vector>
|
||||||
|
#include <Rcpp.h>
|
||||||
|
|
||||||
|
#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<Board>& 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;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
// #include <Rcpp.h>
|
||||||
|
|
||||||
|
// #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<piece>(!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<char>('8' - rankIndex);
|
||||||
|
// for (Index fileIndex = 0; fileIndex < 8; fileIndex++) {
|
||||||
|
// Index squareIndex = 8 * rankIndex + fileIndex;
|
||||||
|
// if (bitMask<Square>(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;
|
||||||
|
// }
|
|
@ -0,0 +1,94 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <Rcpp.h>
|
||||||
|
|
||||||
|
//' 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<unsigned long>(input.tellg());
|
||||||
|
unsigned long seek = static_cast<unsigned long>(line_len) * (
|
||||||
|
static_cast<unsigned long>(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<std::streamsize>::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<std::streamsize>::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<std::streamsize>::max(), '\n');
|
||||||
|
if (input.eof()) { input.seekg(0); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
#include <vector>
|
||||||
|
#include <Rcpp.h>
|
||||||
|
|
||||||
|
#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<unsigned>(runif() * static_cast<double>(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<Board> 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<unsigned>(runif() * static_cast<double>(max));
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup response vector
|
||||||
|
std::vector<Board> fens;
|
||||||
|
fens.reserve(nr);
|
||||||
|
|
||||||
|
// Sample FENs
|
||||||
|
MoveList moves;
|
||||||
|
for (unsigned i = 0; i < nr; ++i) {
|
||||||
|
Board pos; // start position
|
||||||
|
unsigned depth = static_cast<unsigned>(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;
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
// UCI (Univeral Chess Interface) `R` binding to the `SchachHoernchen` engine
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
#include <Rcpp.h>
|
||||||
|
|
||||||
|
#include "SchachHoernchen/Move.h"
|
||||||
|
#include "SchachHoernchen/Board.h"
|
||||||
|
#include "SchachHoernchen/uci.h"
|
||||||
|
#include "SchachHoernchen/search.h"
|
||||||
|
|
||||||
|
namespace UCI_State {
|
||||||
|
std::vector<Board> 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<std::string>& 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<std::string>(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<Board> 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();
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Startup and unload routines for initialization and cleanup, see: `R/zzz.R`
|
||||||
|
|
||||||
|
#include <Rcpp.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
#include "Board.h"
|
||||||
|
#include "Move.h"
|
||||||
|
#include "search.h"
|
||||||
|
#include "uci.h"
|
||||||
|
|
||||||
|
static const std::string usage{"usage: pgn2fen [--scored] [<input>]"};
|
||||||
|
|
||||||
|
// 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 "<nr>.")
|
||||||
|
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;
|
||||||
|
}
|
|
@ -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
|
Loading…
Reference in New Issue