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:
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