stellarUCI Chess engine written in C++20 |
git clone git://git.dimitrijedobrota.com/stellar.git |
Log | Files | Refs | README | LICENSE | |
commit | c36cee0f9d0031a7c068cab56947327f2a199c1e |
parent | 90991d29e87f77efec7d9cafb2df36ba4c5d4dfd |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Mon, 28 Aug 2023 16:24:59 +0200 |
Rewrite uci logic in c++
Diffstat:M | CMakeLists.txt | | | +- |
M | src/board/CMakeLists.txt | | | +- |
M | src/engine/CMakeLists.txt | | | + |
M | src/engine/engine.cpp | | | +++++++--------------------------------------------------------------------------- |
A | src/engine/engine.hpp | | | +++++++++++ |
M | src/engine/transposition.cpp | | | ++-- |
M | src/engine/transposition.hpp | | | +++--- |
A | src/engine/uci.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/engine/uci.hpp | | | ++++++++++++++++++++++++++++++++ |
M | src/move/move.hpp | | | +- |
M | src/move/movelist.hpp | | | ++ |
M | src/utils/utils.hpp | | | ++- |
12 files changed, 179 insertions(+), 190 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(
Stellar
VERSION 0.0.22
VERSION 0.0.23
DESCRIPTION "Chess engine written in C"
HOMEPAGE_URL https://git.dimitrijedobrota.com/stellar.git
LANGUAGES C CXX
diff --git a/src/board/CMakeLists.txt b/src/board/CMakeLists.txt
@@ -3,8 +3,8 @@ add_library(board OBJECT
)
target_link_libraries(board
PRIVATE piece
PRIVATE utils
PUBLIC piece
PUBLIC random
)
diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt
@@ -1,6 +1,7 @@
add_executable(engine
engine.cpp
transposition.cpp
uci.cpp
)
target_link_libraries(engine
diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp
@@ -10,8 +10,8 @@
#include "movelist.hpp"
#include "piece.hpp"
#include "score.hpp"
#include "stellar_version.hpp"
#include "transposition.hpp"
#include "uci.hpp"
#include "utils.hpp"
#define MAX_PLY 64
@@ -21,6 +21,8 @@
#define WINDOW 50
namespace engine {
Board board;
TTable ttable(C64(0x400000));
Move pv_table[MAX_PLY][MAX_PLY];
@@ -31,7 +33,6 @@ bool follow_pv;
U64 nodes;
U32 ply;
Move move_list_best_move;
U32 inline move_list_score(Move move) {
const piece::Type type = board.get_square_piece_type(move.source());
if (move.is_capture()) {
@@ -43,6 +44,7 @@ U32 inline move_list_score(Move move) {
return history[piece::get_index(type, board.get_side())][to_underlying(move.target())];
}
Move move_list_best_move;
void move_list_sort(MoveList &list, std::vector<int> &index, bool bestCheck = true) {
static std::vector<int> score(256);
@@ -157,7 +159,7 @@ int quiescence(int alpha, int beta) {
return alpha;
}
int negamax(int alpha, int beta, int depth, bool null) {
int negamax(int alpha, int beta, uint8_t depth, bool null) {
int pv_node = (beta - alpha) > 1;
HasheFlag flag = HasheFlag::Alpha;
int futility = 0;
@@ -306,18 +308,15 @@ int negamax(int alpha, int beta, int depth, bool null) {
return alpha;
}
void move_print_UCI(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(), board.get_side());
}
void search_position(int depth) {
void search_position(const uci::Settings &setting) {
int alpha = -SCORE_INFINITY, beta = SCORE_INFINITY;
nodes = 0;
for (int crnt = 1; crnt <= depth;) {
board = setting.board;
for (uint8_t depth_crnt = 1; depth_crnt <= setting.depth;) {
follow_pv = 1;
int score = negamax(alpha, beta, crnt, true);
int score = negamax(alpha, beta, depth_crnt, true);
if ((score <= alpha) || (score >= beta)) {
alpha = -SCORE_INFINITY;
beta = SCORE_INFINITY;
@@ -335,189 +334,25 @@ void search_position(int depth) {
std::cout << "info score cp " << score;
}
std::cout << " depth " << crnt;
std::cout << " depth " << (unsigned)depth_crnt;
std::cout << " nodes " << nodes;
std::cout << " pv ";
for (int i = 0; i < pv_length[0]; i++) {
move_print_UCI(pv_table[0][i]);
uci::move_print(board, pv_table[0][i]);
std::cout << " ";
}
std::cout << "\n";
}
crnt++;
depth_crnt++;
}
std::cout << "bestmove ";
move_print_UCI(pv_table[0][0]);
uci::move_print(board, pv_table[0][0]);
std::cout << "\n";
}
void print_info(void) {
std::cout << "id name Stellar " << getStellarVersion() << "\n";
std::cout << "id author Dimitrije Dobrota\n";
std::cout << "uciok\n";
}
typedef struct Instruction Instruction;
struct Instruction {
char *command;
char *token;
char *crnt;
};
char *Instruction_token_next(Instruction *self);
Instruction *Instruction_new(char *command) {
Instruction *p = new Instruction();
p->command = new char[strlen(command) + 1];
p->token = new char[strlen(command) + 1];
strcpy(p->command, command);
p->crnt = command;
Instruction_token_next(p);
return p;
}
void Instruction_free(Instruction **p) {
delete ((*p)->command);
delete ((*p)->token);
delete (*p);
}
char *Instruction_token(Instruction *self) { return self->token; }
char *Instruction_token_n(Instruction *self, int n) {
while (isspace(*self->crnt) && *self->crnt != '\0')
self->crnt++;
if (*self->crnt == '\0') {
*self->token = '\0';
return NULL;
}
char *p = self->token;
while (n--) {
while (!isspace(*self->crnt) && *self->crnt != '\0' && *self->crnt != ';')
*p++ = *self->crnt++;
if (*self->crnt == '\0') {
p++;
break;
}
self->crnt++;
*p++ = ' ';
}
*--p = '\0';
return self->token;
}
char *Instruction_token_next(Instruction *self) { return Instruction_token_n(self, 1); }
Move parse_move(char *move_string) {
Square source = square_from_coordinates(move_string);
Square target = square_from_coordinates(move_string + 2);
const MoveList list(board);
for (int i = 0; i < list.size(); i++) {
const Move move = list[i];
if (move.source() == source && move.target() == target) {
if (move_string[4]) {
if (tolower(piece::get_code(move.promoted())) != move_string[4]) continue;
}
return move;
}
}
return {};
}
Board *Instruction_parse(Instruction *self) {
char *token = Instruction_token(self);
do {
if (strcmp(token, "ucinewgame") == 0) {
board = Board(start_position);
continue;
}
if (strcmp(token, "quit") == 0) return nullptr;
if (strcmp(token, "position") == 0) {
token = Instruction_token_next(self);
if (token && strcmp(token, "startpos") == 0) {
board = Board(start_position);
} else if (token && strcmp(token, "fen") == 0) {
token = Instruction_token_n(self, 6);
board = Board(token);
} else {
printf("Unknown argument after position\n");
}
// board_print(board);
continue;
}
if (strcmp(token, "moves") == 0) {
while ((token = Instruction_token_next(self))) {
Move move = parse_move(token);
if (move != Move()) {
move.make(board, 0);
} else {
printf("Invalid move %s!\n", token);
}
}
return &board;
}
if (strcmp(token, "go") == 0) {
int depth = 6;
for (token = Instruction_token_next(self); token; token = Instruction_token_next(self)) {
if (token && strcmp(token, "depth") == 0) {
token = Instruction_token_next(self);
depth = atoi(token);
} else {
// printf("Unknown argument %s after go\n", token);
}
}
search_position(depth);
continue;
}
if (strcmp(token, "isready") == 0) {
printf("readyok\n");
continue;
}
if (strcmp(token, "uci") == 0) {
print_info();
continue;
}
} while ((token = Instruction_token_next(self)));
return &board;
}
void uci_loop(void) {
Instruction *instruction;
char input[200000];
setbuf(stdin, NULL);
setbuf(stdout, NULL);
print_info();
while (1) {
memset(input, 0, sizeof(input));
fflush(stdout);
if (!fgets(input, sizeof(input), stdin)) continue;
instruction = Instruction_new(input);
if (!Instruction_parse(instruction)) break;
Instruction_free(&instruction);
}
Instruction_free(&instruction);
}
/* MAIN */
} // namespace engine
int main(void) {
uci_loop();
uci::loop();
return 0;
}
diff --git a/src/engine/engine.hpp b/src/engine/engine.hpp
@@ -0,0 +1,11 @@
#ifndef STELLAR_ENGINE_H
#define STELLAR_ENGINE_H
#include "engine/uci.hpp"
#include "uci.hpp"
namespace engine {
void search_position(const uci::Settings &setting);
}
#endif
diff --git a/src/engine/transposition.cpp b/src/engine/transposition.cpp
@@ -2,7 +2,7 @@
#include "board.hpp"
#include "score.hpp"
int TTable::read(const Board &board, int ply, Move *best, int alpha, int beta, int depth) const {
int TTable::read(const Board &board, int ply, Move *best, int alpha, int beta, uint8_t depth) const {
U64 hash = board.get_hash();
const Hashe &phashe = table[hash % table.size()];
@@ -22,7 +22,7 @@ int TTable::read(const Board &board, int ply, Move *best, int alpha, int beta, i
return TTABLE_UNKNOWN;
}
void TTable::write(const Board &board, int ply, Move best, int score, int depth, HasheFlag flag) {
void TTable::write(const Board &board, int ply, Move best, int score, uint8_t depth, HasheFlag flag) {
U64 hash = board.get_hash();
Hashe &phashe = table[hash % table.size()];
diff --git a/src/engine/transposition.hpp b/src/engine/transposition.hpp
@@ -20,7 +20,7 @@ typedef struct Hashe Hashe;
struct Hashe {
U64 key;
Move best;
int depth;
uint8_t depth;
int score;
HasheFlag flag;
};
@@ -30,8 +30,8 @@ class TTable {
TTable(U64 size) : table(size, {0}) {}
void clear() { table.clear(); };
int read(const Board &board, int ply, Move *best, int alpha, int beta, int depth) const;
void write(const Board &board, int ply, Move best, int score, int depth, HasheFlag flag);
int read(const Board &board, int ply, Move *best, int alpha, int beta, uint8_t depth) const;
void write(const Board &board, int ply, Move best, int score, uint8_t depth, HasheFlag flag);
private:
std::vector<Hashe> table;
diff --git a/src/engine/uci.cpp b/src/engine/uci.cpp
@@ -0,0 +1,107 @@
#include <iostream>
#include <sstream>
#include <string>
#include "engine.hpp"
#include "stellar_version.hpp"
#include "uci.hpp"
namespace uci {
void move_print(const Board &board, 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(), board.get_side());
}
inline bool parse_move(const Board &board, Move &move, const std::string &move_string) {
Square source = square_from_coordinates(move_string.substr(0, 2));
Square target = square_from_coordinates(move_string.substr(2, 2));
const MoveList list(board);
for (int i = 0; i < list.size(); i++) {
const Move crnt = list[i];
if (crnt.source() == source && crnt.target() == target) {
if (move_string[4]) {
if (tolower(piece::get_code(crnt.promoted())) != move_string[4]) continue;
}
move = crnt;
return true;
}
}
return false;
}
void loop(void) {
static Settings settings;
static std::string line, command;
static Move move;
while (true) {
std::getline(std::cin, line);
std::istringstream iss(line);
iss >> command;
if (command == "quit") {
break;
} else if (command == "uci") {
std::cout << "id name Stellar " << getStellarVersion() << "\n";
std::cout << "id author Dimitrije Dobrota\n";
std::cout << "uciok\n";
} else if (command == "debug") {
iss >> command;
settings.debug = (command == "on");
} else if (command == "isready") {
std::cout << "readyok\n";
} else if (command == "ucinewgame") {
settings = Settings();
settings.board = Board(start_position);
} else if (command == "position") {
iss >> command;
if (command == "startpos") {
settings.board = Board(start_position);
} else if (command == "fen") {
iss >> command;
settings.board = Board(command);
}
iss >> command;
while (iss >> command) {
if (parse_move(settings.board, move, command))
move.make(settings.board);
else
break;
}
} else if (command == "go") {
while (iss >> command) {
if (command == "wtime")
iss >> settings.wtime;
else if (command == "btime")
iss >> settings.btime;
else if (command == "winc")
iss >> settings.winc;
else if (command == "binc")
iss >> settings.binc;
else if (command == "depth")
iss >> settings.depth;
else if (command == "nodes")
iss >> settings.nodes;
else if (command == "movetime")
iss >> settings.movetime;
else if (command == "ponder")
settings.ponder = true;
else if (command == "mate")
settings.mate = true;
else if (command == "infinite")
settings.infinite = true;
else if (command == "searchmoves") {
while (iss >> command) {
if (parse_move(settings.board, move, command))
settings.searchmoves.push(move);
else
break;
}
}
}
engine::search_position(settings);
}
}
}
} // namespace uci
diff --git a/src/engine/uci.hpp b/src/engine/uci.hpp
@@ -0,0 +1,32 @@
#ifndef STELLAR_UCI_H
#define STELLAR_UCI_H
#include "board.hpp"
#include "move.hpp"
#include "movelist.hpp"
#include "utils.hpp"
namespace uci {
struct Settings {
MoveList searchmoves;
Board board;
uint32_t wtime = 0;
uint32_t btime = 0;
uint32_t movetime = 0;
uint32_t nodes = 0;
uint16_t depth = 0;
uint16_t winc = 0;
uint16_t binc = 0;
bool ponder = false;
bool debug = false;
bool mate = false;
bool infinite = false;
};
void loop(void);
void move_print(const Board &board, Move move);
} // namespace uci
#endif
diff --git a/src/move/move.hpp b/src/move/move.hpp
@@ -51,7 +51,7 @@ struct Move {
const piece::Type promoted(void) const { return static_cast<piece::Type>((flags_i & 0x3) + 1); }
bool make(Board &board, bool attack_only) const;
bool make(Board &board, bool attack_only = false) const;
friend std::ostream &operator<<(std::ostream &os, Move move);
diff --git a/src/move/movelist.hpp b/src/move/movelist.hpp
@@ -15,6 +15,7 @@ class MoveList {
using index_t = std::vector<int>;
public:
MoveList() : list(){};
MoveList(const Board &board) : list() {
list.reserve(256);
generate(board);
@@ -22,6 +23,7 @@ class MoveList {
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); }
friend std::ostream &operator<<(std::ostream &os, const MoveList &list);
diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp
@@ -3,6 +3,7 @@
#include <cstdint>
#include <exception>
#include <string>
#include <type_traits>
template <typename E> constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
@@ -55,7 +56,7 @@ enum class Square: uint8_t {
typedef Iterator<Square, Square::a1, Square::h8> SquareIter;
inline Square square_from_coordinates(const char *cord) {
inline Square square_from_coordinates(const std::string &cord) {
return static_cast<Square>((cord[1] - '1') * 8 + (cord[0] - 'a'));
}