stellarUCI Chess engine written in C++20 |
git clone git://git.dimitrijedobrota.com/stellar.git |
Log | Files | Refs | README | LICENSE | |
commit | d519ce0daa0a384b31511d5a730900697f6c3fe6 |
parent | 0dc53eb006ea1ea7da949a6f096ee81f70ae22e7 |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Tue, 19 Sep 2023 12:06:59 +0000 |
Version 1.1 - General Improvements * Fix UCI time bugs * Improve formatting * Report score from engine * Terminate when mate found * Play last best move when search has been terminated * MoveList generate only legal moves -- expensive! * Extract repetition logic * Extract timer logic - Add Arena for testing purposes * Start the engine and make communication * Play multiple games between engines * Custom starting position * Game PGN info with SAN notation * Arena detects threefold, illegal moves, and timeouts * Multilevel Logging
Diffstat:M | .clang-format | | | +++--- |
M | CMakeLists.txt | | | +- |
A | src/arena/CMakeLists.txt | | | ++++++++++++++++++++ |
A | src/arena/arena.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/arena/engine.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/arena/engine.hpp | | | +++++++++++++++++++++++++++++++++++++++ |
A | src/arena/game.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/arena/game.hpp | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/arena/logger.hpp | | | +++++++++++++++++++++++++++++++ |
A | src/arena/match.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/arena/match.hpp | | | +++++++++++++++++++++++++++++ |
M | src/board/board.hpp | | | ++++++++ |
M | src/engine/engine.cpp | | | ++++++++++++++++++++++++++++++++++-------------------------------------------- |
M | src/engine/uci.cpp | | | ++++++++++++--------------------------------- |
M | src/engine/uci.hpp | | | +-------- |
M | src/move/move.cpp | | | ++++++----- |
M | src/move/move.hpp | | | ++- |
M | src/move/movelist.cpp | | | ++-- |
M | src/move/movelist.hpp | | | +++++++++- |
M | src/perft/perft.cpp | | | ++++---------- |
M | src/utils/CMakeLists.txt | | | ++++ |
M | src/utils/color.hpp | | | +++++ |
A | src/utils/repetition.hpp | | | +++++++++++++++++++++++++++++++++++++ |
M | src/utils/square.hpp | | | +- |
A | src/utils/timer.hpp | | | ++++++++++++++++ |
25 files changed, 770 insertions(+), 109 deletions(-)
diff --git a/.clang-format b/.clang-format
@@ -34,11 +34,11 @@ AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: true
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: Always
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(
Stellar
VERSION 1.0.0
VERSION 1.1.0
DESCRIPTION "Chess engine written in C++"
HOMEPAGE_URL https://git.dimitrijedobrota.com/stellar.git
LANGUAGES CXX
diff --git a/src/arena/CMakeLists.txt b/src/arena/CMakeLists.txt
@@ -0,0 +1,20 @@
add_executable(arena
arena.cpp
match.cpp
engine.cpp
game.cpp
)
target_link_libraries(arena
PRIVATE Stellar_version
PRIVATE board
PRIVATE moves
PRIVATE piece
PRIVATE utils
)
set_target_properties(arena PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
diff --git a/src/arena/arena.cpp b/src/arena/arena.cpp
@@ -0,0 +1,102 @@
#include <format>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>
#include <bits/getopt_core.h>
#include <stdexcept>
#include "logger.hpp"
#include "match.hpp"
class Arena {
public:
Arena(const Arena &) = delete;
Arena &operator==(const Arena &) = delete;
Arena(const char *name1, const char *name2) : engine1(new Engine(name1)), engine2(new Engine(name2)) {
logger::log(std::format("Arena {}: created", id), logger::Debug);
}
~Arena() {
delete (engine1), delete (engine2);
logger::log(std::format("Arena {}: destroyed", id), logger::Debug);
}
void operator()(const std::vector<std::string> positions, Match::Settings swhite,
Match::Settings sblack) {
Match match(*engine1, *engine2);
for (const std::string &fen : positions) {
logger::log(
std::format("Arena {}: match from {}", id, fen == Game::startPosition ? "startpos" : fen),
logger::Debug);
Arena::print(match.play(swhite, sblack, fen));
}
}
private:
static void print(const Game game) {
mutex.lock();
std::cout << game << std::endl;
mutex.unlock();
}
static uint16_t id_t;
uint16_t id = id_t++;
Engine *engine1;
Engine *engine2;
static std::mutex mutex;
};
uint16_t Arena::id_t = 0;
std::mutex Arena::mutex;
void usage(const char *program) {
std::cerr << program << ": ";
std::cerr << "[-f fen]";
std::cerr << "\n[-G wmovestogo -g bmovestogo]";
std::cerr << "\n[-D wdepth -d bdepth]";
std::cerr << "\n[-T wtime -t btime]";
std::cerr << "\n[-I winc -i binc]";
std::cerr << "\n-E engine1 -e engine2";
std::cerr << "\n[- startpos] ... fen ...";
}
int main(int argc, char *argv[]) {
char *engine1 = NULL, *engine2 = NULL;
Match::Settings settings1, settings2;
char c;
while ((c = getopt(argc, argv, "hE:e:D:d:T:t:I:i:G:g:N:")) != -1) {
switch (c) {
case 'E': engine1 = optarg; break;
case 'e': engine2 = optarg; break;
case 'D': settings1.depth = atoi(optarg); break;
case 'd': settings2.depth = atoi(optarg); break;
case 'T': settings1.time = atoll(optarg); break;
case 't': settings2.time = atoll(optarg); break;
case 'I': settings1.inc = atoll(optarg); break;
case 'i': settings2.inc = atoll(optarg); break;
case 'G': settings1.togo = atoi(optarg); break;
case 'g': settings2.togo = atoi(optarg); break;
case 'h': usage(argv[0]); return 1;
default: usage(argv[0]), abort();
}
}
if (!engine1 || !engine2) {
usage(argv[0]);
abort();
}
std::vector<std::string> positions;
for (int i = optind; i < argc; i++)
positions.push_back(!strcmp(argv[i], "-") ? start_position : argv[i]);
Arena arena(engine1, engine2);
arena(positions, settings1, settings2);
return 0;
}
diff --git a/src/arena/engine.cpp b/src/arena/engine.cpp
@@ -0,0 +1,121 @@
#include "engine.hpp"
#include "logger.hpp"
#include <sstream>
#include <sys/wait.h>
#include <unistd.h>
uint16_t Engine::id_t = 0;
Engine::Engine(const char *file) : file(file) {
if (pipe(fd_to) < 0 || pipe(fd_from) < 0) {
logger::error("pipe");
throw std::runtime_error("pipe failed");
}
if ((engine_pid = fork()) > 0) {
start_engine();
} else if (engine_pid < 0) {
logger::error("fork");
throw std::runtime_error("fork failed");
}
if (close(fd_to[0]) < 0 || close(fd_from[1])) {
logger::error("close");
throw std::runtime_error("close failed");
}
send("uci");
logger::log(std::format("Engine {}: waiting for uciok from {}", id, file));
while (true) {
std::string tmp, response = receive();
if (response == "uciok") break;
std::stringstream ss(response);
ss >> tmp >> tmp;
ss.ignore(1);
if (tmp == "name") getline(ss, name);
if (tmp == "author") getline(ss, author);
}
logger::log(std::format("Engine {}: {} is {} by {}", id, file, name, author));
logger::log(std::format("Engine {}: created", id), logger::Debug);
}
Engine::~Engine() {
send("quit");
waitpid(engine_pid, NULL, 0);
// kill(engine_pid, SIGKILL);
if (close(fd_to[1]) < 0) logger::error("close");
if (close(fd_from[0]) < 0) logger::error("close");
logger::log("Engine: destroyed", logger::Debug);
}
void Engine::send(std::string &&command) {
command.push_back('\n');
const char *buffer = command.data();
size_t to_write = command.size();
while (to_write) {
ssize_t size = write(fd_to[1], buffer, to_write);
if (size == -1) {
logger::error("write");
throw std::runtime_error("write failed");
}
buffer += size, to_write -= size;
}
command.pop_back();
logger::log(std::format("Engine {}: TO {}: {}", id, name.size() ? name : file, command), logger::Info);
}
std::string Engine::receive(void) {
int size = 0;
while (true) {
if (!q.empty()) {
std::string response = q.front();
logger::log(std::format("Engine {}: FROM {}: {}", id, name.size() ? name : file, response),
logger::Info);
q.pop();
return response;
}
if ((size = read(fd_from[0], rb + rb_size, sizeof(rb) - rb_size)) == -1) {
logger::error("read");
throw std::runtime_error("read failed");
}
int last = 0;
for (int i = rb_size; i < rb_size + size; i++) {
if (rb[i] == '\n') {
q.push(q_buffer);
q_buffer.clear();
last = i;
continue;
}
q_buffer += rb[i];
}
rb_size += size;
if (last) {
rb_size -= last + 1;
memcpy(rb, rb + last + 1, rb_size);
}
}
}
[[noreturn]] void Engine::start_engine(void) {
if (close(fd_to[1]) < 0 || close(fd_from[0])) {
logger::error("close");
throw std::runtime_error("close failed");
}
if (dup2(fd_to[0], 0) < 0 || dup2(fd_from[1], 1) < 0) {
logger::error("dup2");
throw std::runtime_error("dup2 failed");
}
execl(file, file, (char *)NULL);
logger::error("execl");
throw std::runtime_error("execl failed");
}
diff --git a/src/arena/engine.hpp b/src/arena/engine.hpp
@@ -0,0 +1,39 @@
#ifndef STELLAR_ARENA_ENGINE_H
#define STELLAR_ARENA_ENGINE_H
#include <queue>
class Engine {
public:
Engine(const Engine &) = delete;
Engine &operator=(const Engine &) = delete;
Engine(const char *file);
~Engine();
const std::string &get_name(void) const { return name; }
const std::string &get_author(void) const { return author; }
void send(std::string &&command);
std::string receive(void);
private:
[[noreturn]] void start_engine(void);
const char *file = nullptr;
int fd_to[2], fd_from[2];
pid_t engine_pid;
std::string name, author;
char rb[1000];
int rb_size = 0;
std::queue<std::string> q;
std::string q_buffer;
static uint16_t id_t;
uint16_t id = id_t++;
};
#endif
diff --git a/src/arena/game.cpp b/src/arena/game.cpp
@@ -0,0 +1,106 @@
#include "game.hpp"
#include "board.hpp"
#include "logger.hpp"
#include "timer.hpp"
#include <format>
bool Game::san = true;
const std::string Game::startPosition = start_position;
uint16_t Game::id_t = 0;
Game::~Game() { logger::log(std::format("Game {}: destroyed", id), logger::Debug); }
Game::Game(const uint16_t match_id, const std::string white, const std::string black, const std::string fen)
: match_id(match_id), white(white), black(black), fen(fen) {
logger::log(std::format("Game {}: started", id), logger::Debug);
}
const std::string Game::get_moves(void) const {
std::string res;
if (list.size()) res += (std::string)list[0];
for (int i = 1; i < list.size(); i++)
res += " " + (std::string)list[i];
return res;
}
const std::string Game::to_san(const Board &board, const Move move) {
Board copy = board;
if (!move.make(copy)) {
logger::log("illegal move", logger::Critical);
throw std::runtime_error("illegal move");
}
if (move.is_castle_king()) return "O-O";
if (move.is_castle_queen()) return "O-O-O";
const piece::Type piece = board.get_square_piece_type(move.source());
const piece::Type target = board.get_square_piece_type(move.target());
std::string res;
if (piece != piece::PAWN) {
U64 potential = board.get_bitboard_square_land(move.target(), piece, board.get_side());
if (bit::count(potential) > 1) {
int file[9] = {0}, rank[9] = {0};
uint8_t square_i;
bitboard_for_each_bit(square_i, potential) {
const std::string crd = square::to_coordinates(static_cast<square::Square>(square_i));
file[crd[0] & 0x3]++;
rank[crd[1] & 0x3]++;
}
const std::string crd = square::to_coordinates(move.source());
if (file[crd[0] & 0x3] == 1) res += crd[0];
else if (rank[crd[1] & 0x3] == 1)
res += crd[1];
else
res += crd;
}
res += piece::get_code(piece, color::WHITE);
if (target != piece::NONE) res += "x";
res += square::to_coordinates(move.target());
} else {
if (target != piece::NONE) res += std::format("{}x", square::to_coordinates(move.source())[0]);
res += square::to_coordinates(move.target());
if (move.is_promote()) res += piece::get_code(move.promoted(), color::WHITE);
if (move.is_enpassant()) res += " e.p.";
}
if (!MoveList(copy, false, true).size()) res += "#";
else if (copy.is_check())
res += "+";
return res;
}
std::ostream &operator<<(std::ostream &os, const Game &game) {
static const std::string name[] = {"death", "time forfeit", "rules infraction", "repetition"};
os << std::format("[Event \"Match {}\"]", game.match_id);
os << std::format("\n[Site \"{}\"]", "Stellar Arena");
os << std::format("\n[Date \"{}\"]", timer::get_ms());
os << std::format("\n[Round \"{}\"]", game.id);
os << std::format("\n[White \"{}\"]", game.get_white());
os << std::format("\n[Black \"{}\"]", game.get_black());
os << std::format("\n[Result \"{}-{}\"]", (int)game.is_win_white(), (int)game.is_win_black());
os << std::format("\n[Termination \"{}\"]", name[to_underlying(game.get_terminate())]);
if (game.fen != Game::startPosition) {
os << std::format("\n[SetUp \"1\"]");
os << std::format("\n[FEN \"{}\"]", game.fen);
}
os << '\n';
if (!game.list.size()) return os;
Board board(game.fen);
const color::Color side = board.get_side();
if (side == color::BLACK) os << std::format("1. ... ");
for (int i = 0; i < game.list.size(); i++) {
if (i % 2 == to_underlying(side)) os << std::format("{}. ", i / 2 + 1);
os << std::format("{} ", Game::san ? Game::to_san(board, game.list[i]) : (std::string)game.list[i]);
game.list[i].make(board);
}
return os;
}
diff --git a/src/arena/game.hpp b/src/arena/game.hpp
@@ -0,0 +1,56 @@
#ifndef STELLAR_ARENA_GAME_H
#define STELLAR_ARENA_GAME_H
#include "movelist.hpp"
class Game {
public:
static const std::string startPosition;
enum Terminate {
Deatch,
Timeout,
Illegal,
Repetition,
};
Game(const uint16_t match_id, const std::string white, const std::string black, const std::string fen);
~Game();
void play(const Move move) { list.push(move); }
const std::string get_moves(void) const;
const std::string &get_white(void) const { return white; }
const std::string &get_black(void) const { return black; }
const Terminate get_terminate(void) const { return terminate; }
const bool is_win_white(void) const { return !draw && winner == color::WHITE; }
const bool is_win_black(void) const { return !draw && winner == color::BLACK; }
const bool is_draw(void) const { return draw; }
void set_terminate(const Terminate terminate) { this->terminate = terminate; }
void set_winner(const color::Color winner) { this->winner = winner; }
void set_draw(const bool draw) { this->draw = draw; }
static void set_san(bool san) { Game::san = san; }
friend std::ostream &operator<<(std::ostream &os, const Game &game);
private:
static const std::string to_san(const Board &board, const Move move);
static uint16_t id_t;
uint16_t id = id_t++;
uint16_t match_id;
const std::string white, black;
const std::string fen;
MoveList list;
bool draw = false;
color::Color winner;
Terminate terminate = Terminate::Deatch;
static bool san;
};
#endif
diff --git a/src/arena/logger.hpp b/src/arena/logger.hpp
@@ -0,0 +1,31 @@
#ifndef STELLAR_ARENA_LOGGER_H
#define STELLAR_ARENA_LOGGER_H
#include "utils.hpp"
#include <cerrno>
#include <cstring>
#include <format>
namespace logger {
enum Level {
Critical,
Arena,
Debug,
Info,
};
static Level active = Debug;
inline void set_level(const Level lvl) { active = lvl; }
inline void log(const std::string &message, const Level lvl = Arena) {
static const std::string name[] = {"crit", "arena", "debug", "info"};
if (lvl > active) return;
std::cerr << std::format("[ {:>5} ] {}\n", name[to_underlying(lvl)], message);
}
inline void error(const char *call) { log(std::format("{}, {}", call, std::strerror(errno)), Critical); }
} // namespace logger
#endif
diff --git a/src/arena/match.cpp b/src/arena/match.cpp
@@ -0,0 +1,121 @@
#include "match.hpp"
#include "logger.hpp"
#include "repetition.hpp"
#include "timer.hpp"
uint16_t Match::id_t = 0;
Match::~Match() { logger::log(std::format("Match {}: destroyed", id), logger::Debug); }
Match::Match(Engine &white, Engine &black) : engines({&white, &black}) {
logger::log(std::format("Match {}: created", id), logger::Debug);
}
Game Match::play(Settings swhite, Settings sblack, const std::string fen = Game::startPosition) {
const std::string position = "position " + (fen == Game::startPosition ? "startpos" : "fen " + fen);
repetition::Table rtable;
Board board(fen);
Move move;
logger::log(std::format("Match {}: Play a game between {}(white) and {}(black)", id,
engines[0]->get_name(), engines[1]->get_name()));
Game game(id, engines[0]->get_name(), engines[1]->get_name(), fen);
engines[0]->send("ucinewgame");
engines[1]->send("ucinewgame");
color::Color turn = board.get_side();
while (true) {
const MoveList list = MoveList(board, false, true);
if (!list.size()) {
game.set_winner(color::other(turn));
break;
}
Engine *engine = engines[to_underlying(turn)];
engine->send(std::format("{} moves {}", position, game.get_moves()));
engine->send(get_go(swhite, sblack, turn));
uint64_t time_start = timer::get_ms();
std::string response;
while (true) {
response = engine->receive();
if (response.starts_with("bestmove")) break;
}
std::string move_str = response.substr(9);
if ((move = parse_move(list, move_str)) == Move() || !move.make(board)) {
logger::log(
std::format("Match {}: {} illegal {}", id, color::to_string(turn), (std::string)move));
game.set_terminate(Game::Illegal);
game.set_winner(color::other(turn));
break;
}
if (rtable.is_repetition(board.get_hash())) {
logger::log(std::format("Match {}: {} repetition", id, color::to_string(turn)));
game.set_terminate(Game::Repetition);
game.set_draw(true);
break;
}
rtable.push_hash(board.get_hash());
if (!move.is_repeatable()) rtable.push_null();
game.play(move);
uint64_t time_passed = timer::get_ms() - time_start;
if (turn == color::WHITE ? swhite.time <= time_passed : sblack.time <= time_passed) {
logger::log(std::format("Match {}: {} timeout", id, color::to_string(turn)));
game.set_terminate(Game::Timeout);
game.set_winner(color::other(turn));
break;
}
if (turn == color::WHITE && !swhite.depth) swhite.time -= time_passed;
if (turn == color::BLACK && !sblack.depth) sblack.time -= time_passed;
turn = color::other(turn);
}
if (!game.is_draw()) {
logger::log(std::format("Match {}: winner is {}", id, color::to_string(turn)));
} else {
logger::log(std::format("Match {}: ended in a draw", id));
}
std::swap(engines[0], engines[1]);
return game;
}
std::string Match::get_go(Settings &swhite, Settings &sblack, color::Color side) {
std::string go = "go";
if (side == color::WHITE && swhite.depth) go += " depth " + std::to_string(swhite.depth);
else {
if (side == color::WHITE && swhite.togo) go += " movestogo " + std::to_string(swhite.togo);
if (!sblack.depth && swhite.time) go += " wtime " + std::to_string(swhite.time);
if (swhite.inc) go += " winc " + std::to_string(swhite.inc);
if (swhite.movetime) go += " movetime " + std::to_string(swhite.movetime);
}
if (side == color::BLACK && sblack.depth) go += " depth " + std::to_string(sblack.depth);
else {
if (side == color::BLACK && sblack.togo) go += " movestogo " + std::to_string(sblack.togo);
if (!swhite.depth && sblack.time) go += " btime " + std::to_string(sblack.time);
if (sblack.inc) go += " binc " + std::to_string(sblack.inc);
if (swhite.movetime) go += " movetime " + std::to_string(sblack.movetime);
}
return go;
}
Move Match::parse_move(const MoveList list, const std::string &move_string) {
const square::Square source = square::from_coordinates(move_string.substr(0, 2));
const square::Square target = square::from_coordinates(move_string.substr(2, 2));
for (int i = 0; i < list.size(); i++) {
const Move crnt = list[i];
if (crnt.source() != source || crnt.target() != target) continue;
if (move_string[4] && tolower(piece::get_code(crnt.promoted())) != move_string[4]) continue;
return crnt;
}
return {};
}
diff --git a/src/arena/match.hpp b/src/arena/match.hpp
@@ -0,0 +1,29 @@
#ifndef STELLAR_ARENA_MATCH_H
#define STELLAR_ARENA_MATCH_H
#include "engine.hpp"
#include "game.hpp"
class Match {
public:
struct Settings {
uint64_t time = 30000, inc = 0, movetime = 0;
uint8_t depth = 0, togo = 0;
};
Match(Engine &white, Engine &black);
~Match();
Game play(Settings swhite, Settings sblack, const std::string fen);
private:
static std::string get_go(Settings &swhite, Settings &sblack, color::Color side);
static Move parse_move(const MoveList list, const std::string &move_string);
std::array<Engine *, 2> engines;
static uint16_t id_t;
uint16_t id = id_t++;
};
#endif
diff --git a/src/board/board.hpp b/src/board/board.hpp
@@ -44,6 +44,8 @@ class Board {
square::Square from) const;
inline constexpr U64 get_bitboard_piece_moves(piece::Type piece, color::Color color,
square::Square from) const;
inline constexpr U64 get_bitboard_square_land(square::Square land, piece::Type piece,
color::Color side) const;
inline constexpr color::Color get_square_piece_color(square::Square square) const;
inline constexpr piece::Type get_square_piece_type(square::Square square) const;
@@ -109,6 +111,12 @@ constexpr U64 Board::get_bitboard_piece_moves(piece::Type type, color::Color col
return get_bitboard_piece_attacks(type, color, square) & ~get_bitboard_color(color);
}
constexpr U64 Board::get_bitboard_square_land(square::Square land, piece::Type piece,
color::Color side) const {
return get_bitboard_piece_attacks(piece, color::other(side), land) & get_bitboard_piece(piece, side);
}
constexpr color::Color Board::get_square_piece_color(square::Square square) const {
if (bit::get(colors[to_underlying(color::WHITE)], to_underlying(square))) return color::WHITE;
if (bit::get(colors[to_underlying(color::BLACK)], to_underlying(square))) return color::BLACK;
diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp
@@ -10,7 +10,9 @@
#include "move.hpp"
#include "movelist.hpp"
#include "piece.hpp"
#include "repetition.hpp"
#include "score.hpp"
#include "timer.hpp"
#include "uci.hpp"
#include "utils.hpp"
@@ -76,35 +78,6 @@ class TTable {
std::vector<Hashe> table;
};
class RTable {
public:
RTable() = default;
bool is_repetition(const U64 hash) const {
for (int i = repetitions.size() - 1; i >= 0; i--) {
if (repetitions[i] == hash) return true;
if (repetitions[i] == hashNull) return false;
}
return false;
}
void pop(void) { repetitions.pop_back(); }
void clear(void) { repetitions.clear(); }
void push_null(void) { repetitions.push_back(hashNull); }
void push_hash(U64 hash) { repetitions.push_back(hash); }
friend std::ostream &operator<<(std::ostream &os, const RTable &rtable) {
for (const U64 hash : rtable.repetitions)
os << hash << " ";
return os;
}
private:
std::vector<U64> repetitions;
const static int hashNull = 0;
};
class PVTable {
public:
Move best(uint8_t ply = 0) { return table[0][ply]; }
@@ -133,7 +106,7 @@ std::ostream &operator<<(std::ostream &os, const PVTable &pvtable) {
static const uci::Settings *settings = nullptr;
static Board board;
static TTable ttable;
static RTable rtable;
static repetition::Table rtable;
static PVTable pvtable;
@@ -191,7 +164,7 @@ int stats_move_make(Board ©, const Move move) {
}
ply++;
rtable.push_hash(copy.get_hash());
if (!move.is_repetable()) rtable.push_null();
if (!move.is_repeatable()) rtable.push_null();
return 1;
}
@@ -209,7 +182,7 @@ void stats_move_unmake_pruning(Board ©) {
void stats_move_unmake(Board ©, const Move move) {
board = copy;
if (!move.is_repetable()) rtable.pop();
if (!move.is_repeatable()) rtable.pop();
rtable.pop();
ply--;
}
@@ -391,8 +364,7 @@ int16_t negamax(int16_t alpha, int16_t beta, uint8_t depth, bool null) {
}
if (legal_moves == 0) {
if (isCheck)
return -MATE_VALUE + ply;
if (isCheck) return -MATE_VALUE + ply;
else
return 0;
}
@@ -414,7 +386,7 @@ Move search_position(const uci::Settings &settingsr) {
for (int i = 0; i < settings->madeMoves.size(); i++) {
rtable.push_hash(board.get_hash());
settings->madeMoves[i].make(board);
if (!settings->madeMoves[i].is_repetable()) rtable.clear();
if (!settings->madeMoves[i].is_repeatable()) rtable.clear();
}
ply = 0;
@@ -422,31 +394,49 @@ Move search_position(const uci::Settings &settingsr) {
settings->stopped = false;
memset(killer, 0x00, sizeof(killer));
memset(history, 0x00, sizeof(history));
rtable = RTable();
rtable = repetition::Table();
Move lastBest;
uint8_t max_depth = settings->depth ? settings->depth : MAX_PLY;
for (uint8_t depth = 1; depth <= max_depth; depth++) {
lastBest = pvtable.best();
follow_pv = 1;
int16_t score = negamax(alpha, beta, depth, true);
for (uint8_t depth = 1; depth <= settings->depth; depth++) {
uci::communicate(settings);
if (settings->stopped) break;
follow_pv = 1;
int16_t score = negamax(alpha, beta, depth, true);
if ((score <= alpha) || (score >= beta)) {
alpha = -SCORE_INFINITY;
beta = SCORE_INFINITY;
depth--;
continue;
}
alpha = score - WINDOW;
beta = score + WINDOW;
uci::pv_print(score, depth, nodes, pvtable);
if (settings->depth == 64 && uci::get_time_ms() >= (settings->stoptime + settings->starttime) / 2)
break;
uint8_t mate_ply = 0xFF;
if (score > -MATE_VALUE && score < -MATE_SCORE) {
mate_ply = (score + MATE_VALUE) / 2 + 1;
std::cout << "info score mate -" << (int)mate_ply;
} else if (score > MATE_SCORE && score < MATE_VALUE) {
mate_ply = (MATE_VALUE - score) / 2 + 1;
std::cout << "info score mate " << (int)mate_ply;
} else {
std::cout << "info score cp " << score;
}
std::cout << " depth " << (unsigned)depth;
std::cout << " nodes " << nodes;
std::cout << " time " << timer::get_ms() - settings->starttime;
std::cout << " pv " << pvtable << std::endl;
if (depth >= mate_ply) break;
}
settings->board = board;
return pvtable.best();
return !settings->stopped ? pvtable.best() : lastBest;
}
} // namespace engine
diff --git a/src/engine/uci.cpp b/src/engine/uci.cpp
@@ -2,36 +2,16 @@
#include <limits>
#include <sstream>
#include <string>
#include <sys/time.h>
#include "engine.hpp"
#include "stellar_version.hpp"
#include "timer.hpp"
#include "uci.hpp"
namespace uci {
uint32_t get_time_ms(void) {
struct timeval time;
gettimeofday(&time, NULL);
return time.tv_sec * 1000 + time.tv_usec / 1000;
}
void pv_print(int16_t score, uint8_t depth, uint64_t nodes, const engine::PVTable &pvtable) {
if (score > -MATE_VALUE && score < -MATE_SCORE) {
std::cout << "info score mate " << -(score + MATE_VALUE) / 2 - 1;
} else if (score > MATE_SCORE && score < MATE_VALUE) {
std::cout << "info score mate " << (MATE_VALUE - score) / 2 + 1;
} else {
std::cout << "info score cp " << score;
}
std::cout << " depth " << (unsigned)depth;
std::cout << " nodes " << nodes;
std::cout << " pv " << pvtable << "\n";
}
void communicate(const uci::Settings *settings) {
if (!settings->infinite && uci::get_time_ms() > settings->stoptime) {
if (!settings->infinite && timer::get_ms() > settings->stoptime) {
settings->stopped = true;
return;
}
@@ -60,6 +40,8 @@ void loop(void) {
static std::string line, command;
static Move move;
Board board;
while (true) {
std::getline(std::cin, line);
std::istringstream iss(line);
@@ -91,7 +73,7 @@ void loop(void) {
while (iss >> command)
if (command == "moves") break;
Board board = settings.board;
board = settings.board;
while (iss >> command) {
if (!parse_move(board, move, command)) break;
settings.madeMoves.push(move);
@@ -127,29 +109,26 @@ void loop(void) {
settings.infinite = true;
else if (command == "searchmoves") {
while (iss >> command) {
if (!parse_move(settings.board, move, command)) break;
if (!parse_move(board, move, command)) break;
settings.searchMoves.push(move);
}
}
}
settings.starttime = get_time_ms();
uint32_t time = (settings.board.get_side() == color::WHITE) ? wtime : btime;
settings.starttime = timer::get_ms();
uint64_t time = (board.get_side() == color::WHITE) ? wtime : btime;
if (movetime != 0) {
time = movetime;
movestogo = 1;
}
if (time != 0) {
uint16_t inc = (settings.board.get_side() == color::WHITE) ? winc : binc;
} else if (time != 0) {
uint16_t inc = (board.get_side() == color::WHITE) ? winc : binc;
time /= movestogo;
time -= 50;
settings.stoptime = settings.starttime + time + inc;
settings.infinite = false;
}
if (!time) settings.infinite = true;
} else
settings.infinite = true;
const Move best = engine::search_position(settings);
std::cout << "bestmove " << best << '\n';
diff --git a/src/engine/uci.hpp b/src/engine/uci.hpp
@@ -7,12 +7,6 @@
#include "score.hpp"
#include "utils.hpp"
namespace engine {
class PVTable;
} // namespace engine
namespace uci {
struct Settings {
@@ -23,7 +17,7 @@ struct Settings {
uint64_t starttime;
uint64_t stoptime;
uint16_t depth = 64;
uint16_t depth = 0;
uint32_t nodes = 0;
bool ponder = false;
@@ -38,7 +32,6 @@ struct Settings {
void loop(void);
uint32_t get_time_ms(void);
void communicate(const uci::Settings *settings);
void pv_print(int16_t score, uint8_t depth, uint64_t nodes, const engine::PVTable &pvtable);
} // namespace uci
#endif
diff --git a/src/move/move.cpp b/src/move/move.cpp
@@ -102,9 +102,10 @@ void Move::print(void) const {
std::cout << is_castle();
}
std::ostream &operator<<(std::ostream &os, Move move) {
std::cout << square::to_coordinates(move.source()) << square::to_coordinates(move.target());
if (move.is_promote()) std::cout << piece::get_code(move.promoted());
return os;
Move::operator std::string() const {
std::string res = square::to_coordinates(source()) + square::to_coordinates(target());
if (is_promote()) res += piece::get_code(promoted());
return res;
}
std::ostream &operator<<(std::ostream &os, Move move) { return os << (std::string)move; }
diff --git a/src/move/move.hpp b/src/move/move.hpp
@@ -42,7 +42,7 @@ struct Move {
bool is_promote(void) const { return flags_i & 0x8; }
bool is_double(void) const { return flags_i == DOUBLE; }
bool is_repetable(void) const { return flags_i == QUIET; }
bool is_repeatable(void) const { return flags_i == QUIET; }
bool is_quiet(void) const { return flags_i == QUIET || flags_i == PQUIET; }
bool is_castle(void) const { return flags_i == CASTLEK || flags_i == CASTLEQ; }
@@ -55,6 +55,7 @@ struct Move {
bool make(Board &board) const;
operator std::string() const;
friend std::ostream &operator<<(std::ostream &os, Move move);
void print(void) const;
diff --git a/src/move/movelist.cpp b/src/move/movelist.cpp
@@ -1,4 +1,5 @@
#include "movelist.hpp"
#include "color.hpp"
#include "piece.hpp"
#include <iomanip>
@@ -15,8 +16,7 @@ using piece::Type::PAWN;
void MoveList::generate(const Board &board, bool attacks_only) {
uint8_t src_i, tgt_i;
const color::Color color = board.get_side();
const color::Color colorOther = color == color::BLACK ? color::WHITE : color::BLACK;
const color::Color color = board.get_side(), colorOther = color::other(color);
// pawn moves
const int add = (color == color::WHITE) ? +8 : -8;
diff --git a/src/move/movelist.hpp b/src/move/movelist.hpp
@@ -15,9 +15,17 @@ class MoveList {
public:
MoveList() : list(){};
MoveList(const Board &board, bool attacks_only = false) : list() {
MoveList(const Board &board, bool attacks_only = false, bool legal = false) : list() {
list.reserve(256);
generate(board, attacks_only);
if (!legal) return;
int size = 0;
for (int i = 0; i < list.size(); i++) {
Board copy = board;
if (list[i].make(copy)) list[size++] = list[i];
}
list.resize(size);
}
void clear() { list.clear(); }
diff --git a/src/perft/perft.cpp b/src/perft/perft.cpp
@@ -126,7 +126,7 @@ void usage(const char *program) {
std::cout << " [-h]";
std::cout << " [-t thread number]";
std::cout << " [-d depth]";
std::cout << " [-f fen]" <<std::endl;
std::cout << " [-f fen]" << std::endl;
}
int main(int argc, char *argv[]) {
@@ -139,19 +139,13 @@ int main(int argc, char *argv[]) {
thread_num = atoi(optarg);
if (thread_num <= 0 && thread_num > THREAD_MAX) abort();
break;
case 'f':
fen = optarg;
break;
case 'f': fen = optarg; break;
case 'd':
depth = atoi(optarg);
if (depth <= 0) abort();
break;
case 'h':
usage(argv[0]);
return 1;
default:
usage(argv[0]);
abort();
case 'h': usage(argv[0]); return 1;
default: usage(argv[0]); abort();
}
}
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
@@ -14,6 +14,10 @@ add_library(square INTERFACE)
target_include_directories(square INTERFACE .)
stellar_target_precompile_headers(square INTERFACE "square.hpp")
add_library(timer INTERFACE)
target_include_directories(timer INTERFACE .)
stellar_target_precompile_headers(timer INTERFACE "timer.hpp")
add_library(utils INTERFACE)
target_include_directories(utils INTERFACE .)
stellar_target_precompile_headers(utils INTERFACE "utils.hpp")
diff --git a/src/utils/color.hpp b/src/utils/color.hpp
@@ -1,6 +1,8 @@
#ifndef STELLAR_COLOR_H
#define STELLAR_COLOR_H
#include <string>
namespace color {
enum Color {
@@ -9,6 +11,9 @@ enum Color {
};
inline constexpr const Color other(const Color color) { return color == WHITE ? BLACK : WHITE; }
inline constexpr const std::string to_string(const Color color) {
return std::string(color == WHITE ? "white" : "black");
}
} // namespace color
diff --git a/src/utils/repetition.hpp b/src/utils/repetition.hpp
@@ -0,0 +1,37 @@
#ifndef STELLAR_REPETITION_H
#define STELLAR_REPETITION_H
namespace repetition {
class Table {
public:
Table() = default;
bool is_repetition(const U64 hash) const {
for (int i = repetitions.size() - 1; i >= 0; i--) {
if (repetitions[i] == hash) return true;
if (repetitions[i] == hashNull) return false;
}
return false;
}
void pop(void) { repetitions.pop_back(); }
void clear(void) { repetitions.clear(); }
void push_null(void) { repetitions.push_back(hashNull); }
void push_hash(U64 hash) { repetitions.push_back(hash); }
friend std::ostream &operator<<(std::ostream &os, const Table &rtable) {
for (const U64 hash : rtable.repetitions)
os << hash << " ";
return os;
}
private:
std::vector<U64> repetitions;
const static int hashNull = 0;
};
} // namespace repetition
#endif
diff --git a/src/utils/square.hpp b/src/utils/square.hpp
@@ -51,7 +51,7 @@ inline constexpr const uint8_t file(const Square square) { return to_underlying(
inline constexpr const uint8_t rank(const Square square) { return to_underlying(square) >> 3; }
inline constexpr const Square mirror(const Square square) { return mirror_array[square]; }
inline constexpr const char *to_coordinates(const Square square) {
inline constexpr const std::string to_coordinates(const Square square) {
return coordinates_array[to_underlying(square)];
}
diff --git a/src/utils/timer.hpp b/src/utils/timer.hpp
@@ -0,0 +1,16 @@
#ifndef STELLAR_TIME_H
#define STELLAR_TIME_H
#include <sys/time.h>
namespace timer {
inline uint32_t get_ms(void) {
struct timeval time;
gettimeofday(&time, NULL);
return time.tv_sec * 1000 + time.tv_usec / 1000;
}
} // namespace timer
#endif