stellar

Stellar - UCI Chess engine written in C++20
git clone git://git.dimitrijedobrota.com/stellar.git
Log | Files | Refs | README | LICENSE

commit 97eeac5b570b67e3fa6e327ec142ea0c5f135d49
parent 8bab42ac758595bc29cfc1ae86398a57e72cd963
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Wed, 30 Aug 2023 22:15:26 +0200

Prevent move repetition

Diffstat:
MCMakeLists.txt | 2+-
Msrc/board/board.cpp | 7++++---
Msrc/engine/engine.cpp | 87++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/engine/uci.cpp | 17++++++++++++-----
Msrc/engine/uci.hpp | 4+++-
Msrc/move/move.hpp | 6++++--
Msrc/move/movelist.cpp | 2+-
Msrc/move/movelist.hpp | 2++
8 files changed, 93 insertions(+), 34 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project( Stellar - VERSION 0.0.26 + VERSION 0.0.28 DESCRIPTION "Chess engine written in C" HOMEPAGE_URL https://git.dimitrijedobrota.com/stellar.git LANGUAGES C CXX diff --git a/src/board/board.cpp b/src/board/board.cpp @@ -23,7 +23,7 @@ Board::Board(const std::string &fen) { file = 0; rank--; } else { - throw std::exception(); + throw std::runtime_error("Invalid piece position"); } } @@ -33,7 +33,7 @@ Board::Board(const std::string &fen) { else if (fen[i] == 'b') side = Color::BLACK; else - throw std::exception(); + throw std::runtime_error("Invalid player char"); for (i += 2; fen[i] != ' '; i++) { if (fen[i] == 'K') @@ -47,9 +47,10 @@ Board::Board(const std::string &fen) { else if (fen[i] == '-') break; else - throw std::exception(); + throw std::runtime_error("Invalid castle rights"); } + i++; if (fen[++i] != '-') enpassant = square_from_coordinates(fen.data() + i); hash = Zobrist::hash(*this); } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp @@ -76,16 +76,46 @@ class TTable { std::vector<Hashe> table; }; -const uci::Settings *settings = nullptr; -Board board; -TTable ttable; -Move pv_table[MAX_PLY][MAX_PLY]; -Move killer[2][MAX_PLY]; -U32 history[12][64]; -int pv_length[MAX_PLY]; -bool follow_pv; -U64 nodes; -U32 ply; +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; +}; + +static const uci::Settings *settings = nullptr; +static Board board; +static TTable ttable; +static RTable rtable; +static Move pv_table[MAX_PLY][MAX_PLY]; +static Move killer[2][MAX_PLY]; +static U32 history[12][64]; +static int pv_length[MAX_PLY]; +static bool follow_pv; +static U64 nodes; +static U32 ply; U32 inline move_list_score(Move move) { const piece::Type type = board.get_square_piece_type(move.source()); @@ -152,15 +182,15 @@ int evaluate(const Board &board) { return score; } -int is_repetition() { return 0; } - -int stats_move_make(Board &copy, Move move, int flag) { +int stats_move_make(Board &copy, const Move move, int flag) { copy = board; if (!move.make(board, flag)) { board = copy; return 0; } ply++; + rtable.push_hash(copy.get_hash()); + if (!move.is_repetable()) rtable.push_null(); return 1; } @@ -171,8 +201,15 @@ void stats_move_make_pruning(Board &copy) { ply++; } -void stats_move_unmake(Board &copy) { +void stats_move_unmake_pruning(Board &copy) { + board = copy; + ply--; +} + +void stats_move_unmake(Board &copy, const Move move) { board = copy; + if (!move.is_repetable()) rtable.pop(); + rtable.pop(); ply--; } @@ -201,13 +238,14 @@ int quiescence(int16_t alpha, int16_t beta) { MoveList list(board); for (int idx : move_list_sort(list, Move())) { + const Move move = list[idx]; if (!stats_move_make(copy, list[idx], 1)) continue; score = -quiescence(-beta, -alpha); - stats_move_unmake(copy); + stats_move_unmake(copy, move); if (score > alpha) { alpha = score; - stats_pv_store(list[idx]); + stats_pv_store(move); if (score >= beta) return beta; } @@ -234,7 +272,7 @@ int negamax(int16_t alpha, int16_t beta, uint8_t depth, bool null) { if (ply && score != TTable::unknown && !pv_node) return score; // && fifty >= 100 - if (ply && is_repetition()) return 0; + if (ply && rtable.is_repetition(board.get_hash())) return 0; if (depth == 0) { nodes++; int score = quiescence(alpha, beta); @@ -267,7 +305,7 @@ int negamax(int16_t alpha, int16_t beta, uint8_t depth, bool null) { if (ply && depth > 2 && staticEval >= beta) { stats_move_make_pruning(copy); score = -negamax(-beta, -beta + 1, depth - 1 - REDUCTION_MOVE, false); - stats_move_unmake(copy); + stats_move_unmake_pruning(copy); if (score >= beta) return beta; } @@ -306,7 +344,7 @@ int negamax(int16_t alpha, int16_t beta, uint8_t depth, bool null) { // futility pruning if (futility && searched && !move.is_capture() && !move.is_promote() && !board.is_check()) { - stats_move_unmake(copy); + stats_move_unmake(copy, move); continue; } @@ -331,7 +369,7 @@ int negamax(int16_t alpha, int16_t beta, uint8_t depth, bool null) { } } - stats_move_unmake(copy); + stats_move_unmake(copy, move); searched++; if (settings->stopped) return 0; @@ -378,9 +416,16 @@ void search_position(const uci::Settings &settingsr) { ttable = TTable(C64(0x1000000)); } + rtable.clear(); + board = settings->board; + 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.push_null(); + } + ply = 0; nodes = 0; - board = settings->board; settings->stopped = false; memset(killer, 0x00, sizeof(killer)); memset(history, 0x00, sizeof(history)); diff --git a/src/engine/uci.cpp b/src/engine/uci.cpp @@ -76,19 +76,26 @@ void loop(void) { settings = Settings(); settings.board = Board(start_position); } else if (command == "position") { + settings.madeMoves.clear(); iss >> command; if (command == "startpos") { settings.board = Board(start_position); } else if (command == "fen") { iss >> command; - settings.board = Board(command); + settings.board = Board(line.substr(13)); } - iss >> command; + + while (iss >> command) + if (command == "moves") break; + + Board board = settings.board; while (iss >> command) { - if (!parse_move(settings.board, move, command)) break; - move.make(settings.board); + if (!parse_move(board, move, command)) break; + settings.madeMoves.push(move); + move.make(board); } } else if (command == "go") { + settings.searchMoves.clear(); uint32_t wtime = 0, btime = 0, movetime = 0; uint16_t winc = 0, binc = 0, movestogo = 30; @@ -118,7 +125,7 @@ void loop(void) { else if (command == "searchmoves") { while (iss >> command) { if (!parse_move(settings.board, move, command)) break; - settings.searchmoves.push(move); + settings.searchMoves.push(move); } } } diff --git a/src/engine/uci.hpp b/src/engine/uci.hpp @@ -9,7 +9,9 @@ namespace uci { struct Settings { - MoveList searchmoves; + MoveList searchMoves; + MoveList madeMoves; + Board board; uint32_t starttime; uint32_t stoptime; diff --git a/src/move/move.hpp b/src/move/move.hpp @@ -16,6 +16,7 @@ struct Move { CASTLEQ, CAPTURE, ENPASSANT, + PQUIET, PKNIGHT = 8, PBISHOP, PROOK, @@ -37,11 +38,12 @@ struct Move { Square source(void) const { return static_cast<Square>(source_i); } Square target(void) const { return static_cast<Square>(target_i); } - bool is_capture(void) const { return flags_i & CAPTURE; } + bool is_capture(void) const { return flags_i != PQUIET && (flags_i & CAPTURE); } bool is_promote(void) const { return flags_i & 0x8; } - bool is_quiet(void) const { return flags_i == QUIET; } bool is_double(void) const { return flags_i == DOUBLE; } + bool is_repetable(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; } bool is_castle_king(void) const { return flags_i == CASTLEK; } diff --git a/src/move/movelist.cpp b/src/move/movelist.cpp @@ -32,7 +32,7 @@ void MoveList::generate(const Board &board) { list.push_back({src, tgt, Move::PROOK}); list.push_back({src, tgt, Move::PQUEEN}); } else { - list.push_back({src, tgt, Move::QUIET}); + list.push_back({src, tgt, Move::PQUIET}); // two ahead const Square tgt = static_cast<Square>(tgt_i + add); diff --git a/src/move/movelist.hpp b/src/move/movelist.hpp @@ -21,7 +21,9 @@ class MoveList { generate(board); } + void clear() { list.clear(); } int size() const { return list.size(); } + const Move operator[](size_t idx) const { return list[idx]; } void push(const Move move) { list.push_back(move); }