From 4182d3aa4fd533bd63d08132416323c3a134c91b Mon Sep 17 00:00:00 2001 From: Home Date: Wed, 4 Jun 2025 20:29:41 +0300 Subject: [PATCH] commit 1 --- Game/Board.h | 342 +++++++++++++++++++++++++++------------------- Game/Config.h | 22 ++- Game/Game.h | 255 +++++++++++++++++++++------------- Game/Hand.h | 81 +++++++---- Game/Logic.h | 151 ++++++++++++-------- Models/Move.h | 39 ++++-- Models/Response.h | 11 +- settings.json | 34 ++--- 8 files changed, 574 insertions(+), 361 deletions(-) diff --git a/Game/Board.h b/Game/Board.h index 5c955fb..193dc08 100644 --- a/Game/Board.h +++ b/Game/Board.h @@ -7,195 +7,232 @@ #include "../Models/Project_path.h" #ifdef __APPLE__ - #include - #include +#include +#include #else - #include - #include +#include +#include #endif using namespace std; +// Класс Board реализует игровое поле для шашек, включая: +// - Графическое отображение (SDL) +// - Логику перемещения фигур +// - Историю ходов +// - Визуальные эффекты (подсветка, выделение) class Board { public: - Board() = default; - Board(const unsigned int W, const unsigned int H) : W(W), H(H) - { - } + // Конструкторы + Board() = default; // Конструктор по умолчанию + + // Конструктор с параметрами размеров окна + Board(const unsigned int W, const unsigned int H) : W(W), H(H) {} - // draws start board + // Основные публичные методы: + + // Инициализация и первая отрисовка игрового поля int start_draw() { - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) - { + // Инициализация SDL библиотек + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { print_exception("SDL_Init can't init SDL2 lib"); return 1; } - if (W == 0 || H == 0) - { + + // Автоматическое определение размеров окна, если они не заданы + if (W == 0 || H == 0) { SDL_DisplayMode dm; - if (SDL_GetDesktopDisplayMode(0, &dm)) - { - print_exception("SDL_GetDesktopDisplayMode can't get desctop display mode"); + if (SDL_GetDesktopDisplayMode(0, &dm)) { + print_exception("SDL_GetDesktopDisplayMode can't get desktop display mode"); return 1; } + // Устанавливаем квадратное окно с отступами 6.66% W = min(dm.w, dm.h); W -= W / 15; H = W; } + + // Создание окна с заголовком "Checkers" win = SDL_CreateWindow("Checkers", 0, H / 30, W, H, SDL_WINDOW_RESIZABLE); - if (win == nullptr) - { + if (win == nullptr) { print_exception("SDL_CreateWindow can't create window"); return 1; } + + // Создание рендерера с аппаратным ускорением и вертикальной синхронизацией ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); - if (ren == nullptr) - { + if (ren == nullptr) { print_exception("SDL_CreateRenderer can't create renderer"); return 1; } - board = IMG_LoadTexture(ren, board_path.c_str()); - w_piece = IMG_LoadTexture(ren, piece_white_path.c_str()); - b_piece = IMG_LoadTexture(ren, piece_black_path.c_str()); - w_queen = IMG_LoadTexture(ren, queen_white_path.c_str()); - b_queen = IMG_LoadTexture(ren, queen_black_path.c_str()); - back = IMG_LoadTexture(ren, back_path.c_str()); - replay = IMG_LoadTexture(ren, replay_path.c_str()); - if (!board || !w_piece || !b_piece || !w_queen || !b_queen || !back || !replay) - { + + // Загрузка всех необходимых текстур: + board = IMG_LoadTexture(ren, board_path.c_str()); // Текстура доски + w_piece = IMG_LoadTexture(ren, piece_white_path.c_str()); // Белая шашка + b_piece = IMG_LoadTexture(ren, piece_black_path.c_str()); // Черная шашка + w_queen = IMG_LoadTexture(ren, queen_white_path.c_str()); // Белая дамка + b_queen = IMG_LoadTexture(ren, queen_black_path.c_str()); // Черная дамка + back = IMG_LoadTexture(ren, back_path.c_str()); // Кнопка "Назад" + replay = IMG_LoadTexture(ren, replay_path.c_str()); // Кнопка "Повтор" + + // Проверка успешной загрузки всех текстур + if (!board || !w_piece || !b_piece || !w_queen || !b_queen || !back || !replay) { print_exception("IMG_LoadTexture can't load main textures from " + textures_path); return 1; } + + // Получение фактических размеров области рендеринга SDL_GetRendererOutputSize(ren, &W, &H); - make_start_mtx(); - rerender(); - return 0; + + // Инициализация начального состояния доски и первая отрисовка + make_start_mtx(); // Создание начальной расстановки фигур + rerender(); // Первичный рендеринг + return 0; // Успешное завершение инициализации } + // Сброс доски в начальное состояние void redraw() { - game_results = -1; - history_mtx.clear(); - history_beat_series.clear(); - make_start_mtx(); - clear_active(); - clear_highlight(); + game_results = -1; // Сброс результата игры + history_mtx.clear(); // Очистка истории ходов + history_beat_series.clear();// Очистка истории взятий + make_start_mtx(); // Создание начальной расстановки + clear_active(); // Сброс выделенной клетки + clear_highlight(); // Сброс подсвеченных клеток } + // Перемещение фигуры с обработкой взятия (перегруженная версия) void move_piece(move_pos turn, const int beat_series = 0) { - if (turn.xb != -1) - { + // Если был бой - удаляем побитую фигуру + if (turn.xb != -1) { mtx[turn.xb][turn.yb] = 0; } + // Вызов основной функции перемещения move_piece(turn.x, turn.y, turn.x2, turn.y2, beat_series); } + // Основная функция перемещения фигуры void move_piece(const POS_T i, const POS_T j, const POS_T i2, const POS_T j2, const int beat_series = 0) { - if (mtx[i2][j2]) - { + // Проверка валидности хода: + if (mtx[i2][j2]) { throw runtime_error("final position is not empty, can't move"); } - if (!mtx[i][j]) - { + if (!mtx[i][j]) { throw runtime_error("begin position is empty, can't move"); } + + // Проверка превращения в дамку (для белых и черных соответственно) if ((mtx[i][j] == 1 && i2 == 0) || (mtx[i][j] == 2 && i2 == 7)) - mtx[i][j] += 2; - mtx[i2][j2] = mtx[i][j]; - drop_piece(i, j); - add_history(beat_series); + mtx[i][j] += 2; // 1->3 (белая дамка), 2->4 (черная дамка) + + // Выполнение перемещения + mtx[i2][j2] = mtx[i][j]; // Перенос фигуры + drop_piece(i, j); // Очистка исходной позиции + add_history(beat_series); // Сохранение в историю } + // Удаление фигуры с доски void drop_piece(const POS_T i, const POS_T j) { - mtx[i][j] = 0; - rerender(); + mtx[i][j] = 0; // Очистка клетки + rerender(); // Обновление отображения } + // Превращение шашки в дамку void turn_into_queen(const POS_T i, const POS_T j) { - if (mtx[i][j] == 0 || mtx[i][j] > 2) - { + // Проверка возможности превращения (только для обычных шашек) + if (mtx[i][j] == 0 || mtx[i][j] > 2) { throw runtime_error("can't turn into queen in this position"); } - mtx[i][j] += 2; - rerender(); + mtx[i][j] += 2; // Превращение (1->3, 2->4) + rerender(); // Обновление отображения } + + // Получение текущего состояния доски vector> get_board() const { - return mtx; + return mtx; // Возврат копии матрицы состояния } + // Подсветка указанных клеток (для отображения возможных ходов) void highlight_cells(vector> cells) { - for (auto pos : cells) - { + for (auto pos : cells) { POS_T x = pos.first, y = pos.second; - is_highlighted_[x][y] = 1; + is_highlighted_[x][y] = 1; // Установка флага подсветки } - rerender(); + rerender(); // Обновление отображения } + // Очистка всех подсвеченных клеток void clear_highlight() { - for (POS_T i = 0; i < 8; ++i) - { - is_highlighted_[i].assign(8, 0); + for (POS_T i = 0; i < 8; ++i) { + is_highlighted_[i].assign(8, 0); // Сброс флагов подсветки } - rerender(); + rerender(); // Обновление отображения } + // Установка активной (выделенной) клетки void set_active(const POS_T x, const POS_T y) { - active_x = x; + active_x = x; // Сохранение координат active_y = y; - rerender(); + rerender(); // Обновление отображения } + // Сброс активной клетки void clear_active() { - active_x = -1; + active_x = -1; // Сброс координат active_y = -1; - rerender(); + rerender(); // Обновление отображения } + // Проверка, подсвечена ли клетка bool is_highlighted(const POS_T x, const POS_T y) { - return is_highlighted_[x][y]; + return is_highlighted_[x][y]; // Возврат состояния подсветки } + // Отмена последнего хода (откат) void rollback() { + // Определение сколько ходов нужно откатить (с учетом серии взятий) auto beat_series = max(1, *(history_beat_series.rbegin())); while (beat_series-- && history_mtx.size() > 1) { - history_mtx.pop_back(); - history_beat_series.pop_back(); + history_mtx.pop_back(); // Удаление из истории + history_beat_series.pop_back(); // Удаление информации о взятиях } - mtx = *(history_mtx.rbegin()); - clear_highlight(); - clear_active(); + mtx = *(history_mtx.rbegin()); // Восстановление состояния + clear_highlight(); // Сброс подсветки + clear_active(); // Сброс выделения } + // Отображение результата игры void show_final(const int res) { - game_results = res; - rerender(); + game_results = res; // Сохранение результата + rerender(); // Обновление отображения } - // use if window size changed + // Обновление размеров окна void reset_window_size() { - SDL_GetRendererOutputSize(ren, &W, &H); - rerender(); + SDL_GetRendererOutputSize(ren, &W, &H); // Получение новых размеров + rerender(); // Перерисовка } + // Очистка ресурсов SDL void quit() { + // Уничтожение всех текстур SDL_DestroyTexture(board); SDL_DestroyTexture(w_piece); SDL_DestroyTexture(b_piece); @@ -203,73 +240,84 @@ class Board SDL_DestroyTexture(b_queen); SDL_DestroyTexture(back); SDL_DestroyTexture(replay); + + // Уничтожение рендерера и окна SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); + + // Завершение работы SDL SDL_Quit(); } + // Деструктор - автоматическая очистка при уничтожении объекта ~Board() { if (win) - quit(); + quit(); // Вызов метода очистки, если окно было создано } private: + // Добавление текущего состояния в историю void add_history(const int beat_series = 0) { - history_mtx.push_back(mtx); - history_beat_series.push_back(beat_series); + history_mtx.push_back(mtx); // Сохранение состояния доски + history_beat_series.push_back(beat_series); // Сохранение информации о взятиях } - // function to make start matrix + + // Создание начальной расстановки фигур void make_start_mtx() { + // Очистка доски for (POS_T i = 0; i < 8; ++i) { for (POS_T j = 0; j < 8; ++j) { mtx[i][j] = 0; + // Расстановка черных шашек (верхние 3 ряда) if (i < 3 && (i + j) % 2 == 1) mtx[i][j] = 2; + // Расстановка белых шашек (нижние 3 ряда) if (i > 4 && (i + j) % 2 == 1) mtx[i][j] = 1; } } - add_history(); + add_history(); // Сохранение начального состояния в историю } - // function that re-draw all the textures + // Основная функция рендеринга (перерисовки всего содержимого) void rerender() { - // draw board + // Очистка рендерера SDL_RenderClear(ren); + + // Отрисовка доски SDL_RenderCopy(ren, board, NULL, NULL); - // draw pieces + // Отрисовка всех фигур for (POS_T i = 0; i < 8; ++i) { for (POS_T j = 0; j < 8; ++j) { - if (!mtx[i][j]) - continue; + if (!mtx[i][j]) continue; // Пропуск пустых клеток + + // Расчет позиции для отрисовки int wpos = W * (j + 1) / 10 + W / 120; int hpos = H * (i + 1) / 10 + H / 120; SDL_Rect rect{ wpos, hpos, W / 12, H / 12 }; + // Выбор текстуры в зависимости от типа фигуры SDL_Texture* piece_texture; - if (mtx[i][j] == 1) - piece_texture = w_piece; - else if (mtx[i][j] == 2) - piece_texture = b_piece; - else if (mtx[i][j] == 3) - piece_texture = w_queen; - else - piece_texture = b_queen; + if (mtx[i][j] == 1) piece_texture = w_piece; // Белая шашка + else if (mtx[i][j] == 2) piece_texture = b_piece; // Черная шашка + else if (mtx[i][j] == 3) piece_texture = w_queen; // Белая дамка + else piece_texture = b_queen; // Черная дамка + // Отрисовка фигуры SDL_RenderCopy(ren, piece_texture, NULL, &rect); } } - // draw hilight + // Отрисовка подсветки возможных ходов (зеленые рамки) SDL_SetRenderDrawColor(ren, 0, 255, 0, 0); const double scale = 2.5; SDL_RenderSetScale(ren, scale, scale); @@ -277,38 +325,44 @@ class Board { for (POS_T j = 0; j < 8; ++j) { - if (!is_highlighted_[i][j]) - continue; - SDL_Rect cell{ int(W * (j + 1) / 10 / scale), int(H * (i + 1) / 10 / scale), int(W / 10 / scale), - int(H / 10 / scale) }; + if (!is_highlighted_[i][j]) continue; + SDL_Rect cell{ + int(W * (j + 1) / 10 / scale), + int(H * (i + 1) / 10 / scale), + int(W / 10 / scale), + int(H / 10 / scale) + }; SDL_RenderDrawRect(ren, &cell); } } - // draw active + // Отрисовка активной (выделенной) клетки (красная рамка) if (active_x != -1) { SDL_SetRenderDrawColor(ren, 255, 0, 0, 0); - SDL_Rect active_cell{ int(W * (active_y + 1) / 10 / scale), int(H * (active_x + 1) / 10 / scale), - int(W / 10 / scale), int(H / 10 / scale) }; + SDL_Rect active_cell{ + int(W * (active_y + 1) / 10 / scale), + int(H * (active_x + 1) / 10 / scale), + int(W / 10 / scale), + int(H / 10 / scale) + }; SDL_RenderDrawRect(ren, &active_cell); } - SDL_RenderSetScale(ren, 1, 1); + SDL_RenderSetScale(ren, 1, 1); // Восстановление масштаба - // draw arrows + // Отрисовка кнопок управления SDL_Rect rect_left{ W / 40, H / 40, W / 15, H / 15 }; - SDL_RenderCopy(ren, back, NULL, &rect_left); + SDL_RenderCopy(ren, back, NULL, &rect_left); // Кнопка "Назад" SDL_Rect replay_rect{ W * 109 / 120, H / 40, W / 15, H / 15 }; - SDL_RenderCopy(ren, replay, NULL, &replay_rect); + SDL_RenderCopy(ren, replay, NULL, &replay_rect); // Кнопка "Повтор" - // draw result + // Отрисовка результата игры (если есть) if (game_results != -1) { string result_path = draw_path; - if (game_results == 1) - result_path = white_path; - else if (game_results == 2) - result_path = black_path; + if (game_results == 1) result_path = white_path; // Победа белых + else if (game_results == 2) result_path = black_path; // Победа черных + SDL_Texture* result_texture = IMG_LoadTexture(ren, result_path.c_str()); if (result_texture == nullptr) { @@ -320,37 +374,44 @@ class Board SDL_DestroyTexture(result_texture); } + // Обновление экрана SDL_RenderPresent(ren); - // next rows for mac os + + // Небольшая задержка и обработка событий (особенно для MacOS) SDL_Delay(10); SDL_Event windowEvent; SDL_PollEvent(&windowEvent); } + // Запись ошибки в лог-файл void print_exception(const string& text) { ofstream fout(project_path + "log.txt", ios_base::app); - fout << "Error: " << text << ". "<< SDL_GetError() << endl; + fout << "Error: " << text << ". " << SDL_GetError() << endl; fout.close(); } - public: - int W = 0; - int H = 0; - // history of boards +public: + int W = 0; // Ширина окна + int H = 0; // Высота окна + + // История состояний доски (для реализации отмены хода) vector>> history_mtx; - private: - SDL_Window *win = nullptr; - SDL_Renderer *ren = nullptr; - // textures - SDL_Texture *board = nullptr; - SDL_Texture *w_piece = nullptr; - SDL_Texture *b_piece = nullptr; - SDL_Texture *w_queen = nullptr; - SDL_Texture *b_queen = nullptr; - SDL_Texture *back = nullptr; - SDL_Texture *replay = nullptr; - // texture files names +private: + // Указатели на SDL объекты + SDL_Window* win = nullptr; // Окно + SDL_Renderer* ren = nullptr; // Рендерер + + // Текстуры: + SDL_Texture* board = nullptr; // Доска + SDL_Texture* w_piece = nullptr; // Белая шашка + SDL_Texture* b_piece = nullptr; // Черная шашка + SDL_Texture* w_queen = nullptr; // Белая дамка + SDL_Texture* b_queen = nullptr; // Черная дамка + SDL_Texture* back = nullptr; // Кнопка "Назад" + SDL_Texture* replay = nullptr; // Кнопка "Повтор" + + // Пути к файлам текстур const string textures_path = project_path + "Textures/"; const string board_path = textures_path + "board.png"; const string piece_white_path = textures_path + "piece_white.png"; @@ -362,15 +423,20 @@ class Board const string draw_path = textures_path + "draw.png"; const string back_path = textures_path + "back.png"; const string replay_path = textures_path + "replay.png"; - // coordinates of chosen cell + + // Координаты активной (выделенной) клетки int active_x = -1, active_y = -1; - // game result if exist + + // Результат игры (-1 - игра продолжается, 0 - ничья, 1 - победа белых, 2 - победа черных) int game_results = -1; - // matrix of possible moves + + // Матрица подсвеченных клеток (для отображения возможных ходов) vector> is_highlighted_ = vector>(8, vector(8, 0)); - // matrix of possible moves - // 1 - white, 2 - black, 3 - white queen, 4 - black queen + + // Матрица состояния доски: + // 0 - пусто, 1 - белая шашка, 2 - черная шашка, 3 - белая дамка, 4 - черная дамка vector> mtx = vector>(8, vector(8, 0)); - // series of beats for each move + + // История взятий (для правильной отмены хода при серии взятий) vector history_beat_series; -}; +}; \ No newline at end of file diff --git a/Game/Config.h b/Game/Config.h index 1a41663..49da275 100644 --- a/Game/Config.h +++ b/Game/Config.h @@ -7,24 +7,34 @@ using json = nlohmann::json; class Config { - public: +public: Config() { - reload(); + reload(); // При создании объекта Config сразу загружаем конфигурацию } + // Функция reload() загружает конфигурационные данные из JSON-файла + // 1. Открывает файл "settings.json" в папке проекта + // 2. Считывает всё содержимое в объект json (библиотека nlohmann) + // 3. Закрывает файл void reload() { std::ifstream fin(project_path + "settings.json"); - fin >> config; + fin >> config; // Десериализация JSON в объект config fin.close(); } - auto operator()(const string &setting_dir, const string &setting_name) const + // Перегруженный оператор () позволяет удобно получать значения из конфига + // Принимает: + // - setting_dir: раздел конфига (например, "Bot" или "Game") + // - setting_name: имя параметра в разделе (например, "IsWhiteBot") + // Возвращает значение запрошенного параметра + // Использование: config("Bot", "IsWhiteBot") вместо config["Bot"]["IsWhiteBot"] + auto operator()(const string& setting_dir, const string& setting_name) const { return config[setting_dir][setting_name]; } - private: - json config; +private: + json config; // Внутренний объект для хранения конфигурации }; diff --git a/Game/Game.h b/Game/Game.h index d7d16bc..bf3102b 100644 --- a/Game/Game.h +++ b/Game/Game.h @@ -20,229 +20,288 @@ class Game // to start checkers int play() { + // Засекаем время начала игры для замера продолжительности auto start = chrono::steady_clock::now(); - if (is_replay) - { - logic = Logic(&board, &config); - config.reload(); - board.redraw(); + + // Обработка режима повторной игры + if (is_replay) { + logic = Logic(&board, &config); // Пересоздаем логику игры + config.reload(); // Перезагружаем конфигурацию + board.redraw(); // Перерисовываем доску } - else - { - board.start_draw(); + else { + board.start_draw(); // Начальная отрисовка доски для новой игры } - is_replay = false; + is_replay = false; // Сбрасываем флаг реплея - int turn_num = -1; - bool is_quit = false; - const int Max_turns = config("Game", "MaxNumTurns"); - while (++turn_num < Max_turns) - { - beat_series = 0; + int turn_num = -1; // Номер хода (-1 так как сначала ++) + bool is_quit = false; // Флаг выхода из игры + const int Max_turns = config("Game", "MaxNumTurns"); // Макс. число ходов из конфига + + // Главный игровой цикл + while (++turn_num < Max_turns) { + beat_series = 0; // Сбрасываем счетчик серии взятий + + // Находим возможные ходы для текущего игрока (0 - белые, 1 - черные) logic.find_turns(turn_num % 2); - if (logic.turns.empty()) - break; + + // Если ходов нет - игра завершается + if (logic.turns.empty()) break; + + // Устанавливаем уровень сложности бота для текущего игрока logic.Max_depth = config("Bot", string((turn_num % 2) ? "Black" : "White") + string("BotLevel")); - if (!config("Bot", string("Is") + string((turn_num % 2) ? "Black" : "White") + string("Bot"))) - { - auto resp = player_turn(turn_num % 2); - if (resp == Response::QUIT) - { + + // Если текущий игрок - человек (не бот) + if (!config("Bot", string("Is") + string((turn_num % 2) ? "Black" : "White") + string("Bot"))) { + auto resp = player_turn(turn_num % 2); // Обрабатываем ход игрока + + if (resp == Response::QUIT) { // Выход из игры is_quit = true; break; } - else if (resp == Response::REPLAY) - { + else if (resp == Response::REPLAY) { // Запрос на реплей is_replay = true; break; } - else if (resp == Response::BACK) - { + else if (resp == Response::BACK) { // Отмена хода + // Особые условия отмены при игре против бота if (config("Bot", string("Is") + string((1 - turn_num % 2) ? "Black" : "White") + string("Bot")) && - !beat_series && board.history_mtx.size() > 2) - { + !beat_series && board.history_mtx.size() > 2) { board.rollback(); --turn_num; } - if (!beat_series) - --turn_num; + if (!beat_series) --turn_num; board.rollback(); --turn_num; beat_series = 0; } } - else - bot_turn(turn_num % 2); + else { + bot_turn(turn_num % 2); // Ход бота + } } + + // Замер времени игры и запись в лог auto end = chrono::steady_clock::now(); ofstream fout(project_path + "log.txt", ios_base::app); fout << "Game time: " << (int)chrono::duration(end - start).count() << " millisec\n"; fout.close(); - if (is_replay) - return play(); - if (is_quit) - return 0; - int res = 2; - if (turn_num == Max_turns) - { - res = 0; + // Обработка завершения игры + if (is_replay) return play(); // Рестарт игры + if (is_quit) return 0; // Выход без результата + + // Определение результата игры + int res = 2; // По умолчанию ничья (2) + if (turn_num == Max_turns) { + res = 0; // Ничья по достижению лимита ходов } - else if (turn_num % 2) - { - res = 1; + else if (turn_num % 2) { + res = 1; // Победа черных } + + // Показ финального экрана и обработка ответа игрока board.show_final(res); auto resp = hand.wait(); - if (resp == Response::REPLAY) - { + if (resp == Response::REPLAY) { is_replay = true; - return play(); + return play(); // Рестарт по запросу игрока } - return res; + return res; // Возврат результата игры } private: - void bot_turn(const bool color) - { - auto start = chrono::steady_clock::now(); + void bot_turn(const bool color) + { + // Засекаем время начала хода бота для последующего замера производительности + auto start = chrono::steady_clock::now(); - auto delay_ms = config("Bot", "BotDelayMS"); - // new thread for equal delay for each turn - thread th(SDL_Delay, delay_ms); - auto turns = logic.find_best_turns(color); - th.join(); - bool is_first = true; - // making moves - for (auto turn : turns) - { - if (!is_first) - { - SDL_Delay(delay_ms); - } - is_first = false; - beat_series += (turn.xb != -1); - board.move_piece(turn, beat_series); - } + // Получаем задержку для бота из конфигурации (в миллисекундах) + auto delay_ms = config("Bot", "BotDelayMS"); - auto end = chrono::steady_clock::now(); - ofstream fout(project_path + "log.txt", ios_base::app); - fout << "Bot turn time: " << (int)chrono::duration(end - start).count() << " millisec\n"; - fout.close(); - } + // Создаем отдельный поток для задержки, чтобы основной поток мог выполнять вычисления + thread th(SDL_Delay, delay_ms); + + // Находим лучшие ходы для бота на основе текущего состояния доски и цвета фигур + auto turns = logic.find_best_turns(color); + + // Ожидаем завершения потока с задержкой, чтобы задержка была одинаковой для каждого хода + th.join(); + + bool is_first = true; + + // Выполняем найденные ходы + for (auto turn : turns) + { + // Добавляем задержку перед каждым ходом, кроме первого + if (!is_first) + { + SDL_Delay(delay_ms); + } + is_first = false; + + // Увеличиваем счетчик серии ударов, если ход является ударным (xb != -1) + beat_series += (turn.xb != -1); + + // Перемещаем фигуру на доске в соответствии с текущим ходом + board.move_piece(turn, beat_series); + } + + // Засекаем время окончания хода бота + auto end = chrono::steady_clock::now(); + + // Открываем файл лога для записи времени выполнения хода бота + ofstream fout(project_path + "log.txt", ios_base::app); + fout << "Bot turn time: " << (int)chrono::duration(end - start).count() << " millisec\n"; + fout.close(); + } + // Обрабатывает ход игрока + // Параметр color: цвет игрока (false - белые, true - черные) + // Возвращает Response - результат выполнения хода Response player_turn(const bool color) { - // return 1 if quit + // Подготавливаем список клеток с возможными ходами vector> cells; for (auto turn : logic.turns) { - cells.emplace_back(turn.x, turn.y); + cells.emplace_back(turn.x, turn.y); // Добавляем начальные позиции всех возможных ходов } - board.highlight_cells(cells); - move_pos pos = {-1, -1, -1, -1}; - POS_T x = -1, y = -1; - // trying to make first move + board.highlight_cells(cells); // Подсвечиваем клетки с возможными ходами + + move_pos pos = { -1, -1, -1, -1 }; // Позиция для хода (x,y - откуда, x2,y2 - куда, xb,yb - бить) + POS_T x = -1, y = -1; // Текущие координаты выбранной шашки + + // Цикл выбора и выполнения первого хода while (true) { + // Получаем от игрока выбор клетки auto resp = hand.get_cell(); + + // Если игрок выбрал не клетку (например, нажал кнопку меню) if (get<0>(resp) != Response::CELL) - return get<0>(resp); - pair cell{get<1>(resp), get<2>(resp)}; + return get<0>(resp); // Возвращаем действие (QUIT, BACK и т.д.) + + // Получаем координаты выбранной клетки + pair cell{ get<1>(resp), get<2>(resp) }; + // Проверяем корректность выбора bool is_correct = false; for (auto turn : logic.turns) { + // Если выбрана шашка, которой можно ходить if (turn.x == cell.first && turn.y == cell.second) { is_correct = true; break; } - if (turn == move_pos{x, y, cell.first, cell.second}) + // Если выбрана клетка для завершения хода + if (turn == move_pos{ x, y, cell.first, cell.second }) { - pos = turn; + pos = turn; // Запоминаем полный ход break; } } + + // Если ход полностью выбран if (pos.x != -1) break; + + // Если выбор некорректен if (!is_correct) { + // Сбрасываем выделение если была выбрана шашка if (x != -1) { board.clear_active(); board.clear_highlight(); - board.highlight_cells(cells); + board.highlight_cells(cells); // Восстанавливаем исходные возможные ходы } x = -1; y = -1; continue; } + + // Запоминаем выбранную шашку x = cell.first; y = cell.second; + + // Обновляем интерфейс board.clear_highlight(); - board.set_active(x, y); + board.set_active(x, y); // Выделяем выбранную шашку + + // Подготавливаем список возможных целевых клеток для выбранной шашки vector> cells2; for (auto turn : logic.turns) { if (turn.x == x && turn.y == y) { - cells2.emplace_back(turn.x2, turn.y2); + cells2.emplace_back(turn.x2, turn.y2); // Добавляем возможные целевые позиции } } - board.highlight_cells(cells2); + board.highlight_cells(cells2); // Подсвечиваем возможные ходы для выбранной шашки } + + // Очищаем выделения и выполняем ход board.clear_highlight(); board.clear_active(); - board.move_piece(pos, pos.xb != -1); + board.move_piece(pos, pos.xb != -1); // Перемещаем шашку (true если был бой) + + // Если не было боя - завершаем ход if (pos.xb == -1) return Response::OK; - // continue beating while can - beat_series = 1; + + // Обработка серии взятий (несколько боёв за один ход) + beat_series = 1; // Уже было одно взятие while (true) { + // Ищем возможные продолжения боя для текущей шашки logic.find_turns(pos.x2, pos.y2); - if (!logic.have_beats) + if (!logic.have_beats) // Если больше бить нельзя break; + // Подготавливаем список клеток, куда можно бить vector> cells; for (auto turn : logic.turns) { cells.emplace_back(turn.x2, turn.y2); } - board.highlight_cells(cells); - board.set_active(pos.x2, pos.y2); - // trying to make move + board.highlight_cells(cells); // Подсвечиваем клетки для продолжения боя + board.set_active(pos.x2, pos.y2); // Выделяем текущую шашку + + // Цикл выбора продолжения боя while (true) { auto resp = hand.get_cell(); if (get<0>(resp) != Response::CELL) return get<0>(resp); - pair cell{get<1>(resp), get<2>(resp)}; + pair cell{ get<1>(resp), get<2>(resp) }; + // Проверяем корректность выбора продолжения боя bool is_correct = false; for (auto turn : logic.turns) { if (turn.x2 == cell.first && turn.y2 == cell.second) { is_correct = true; - pos = turn; + pos = turn; // Запоминаем ход с взятием break; } } if (!is_correct) continue; + // Выполняем взятие board.clear_highlight(); board.clear_active(); - beat_series += 1; - board.move_piece(pos, beat_series); + beat_series += 1; // Увеличиваем счетчик серии взятий + board.move_piece(pos, beat_series); // Выполняем ход с взятием break; } } - return Response::OK; + return Response::OK; // Успешное завершение хода } private: diff --git a/Game/Hand.h b/Game/Hand.h index 65268fa..11a2538 100644 --- a/Game/Hand.h +++ b/Game/Hand.h @@ -5,84 +5,104 @@ #include "../Models/Response.h" #include "Board.h" -// methods for hands +// Класс для обработки пользовательского ввода (мышь, окно) class Hand { - public: - Hand(Board *board) : board(board) - { - } +public: + // Конструктор, принимает указатель на игровую доску + Hand(Board* board) : board(board) {} + + // Основной метод для получения выбранной клетки от игрока + // Возвращает кортеж из: + // - Response (действие игрока) + // - x-координата клетки (0-7 или -1 если не клетка) + // - y-координата клетки (0-7 или -1 если не клетка) tuple get_cell() const { - SDL_Event windowEvent; - Response resp = Response::OK; - int x = -1, y = -1; - int xc = -1, yc = -1; - while (true) + SDL_Event windowEvent; // Событие SDL + Response resp = Response::OK; // Реакция по умолчанию + int x = -1, y = -1; // Абсолютные координаты курсора + int xc = -1, yc = -1; // Координаты клетки доски (0-7) + + while (true) // Цикл обработки событий { - if (SDL_PollEvent(&windowEvent)) + if (SDL_PollEvent(&windowEvent)) // Проверяем наличие события { - switch (windowEvent.type) + switch (windowEvent.type) // Анализируем тип события { - case SDL_QUIT: + case SDL_QUIT: // Событие закрытия окна resp = Response::QUIT; break; - case SDL_MOUSEBUTTONDOWN: - x = windowEvent.motion.x; + + case SDL_MOUSEBUTTONDOWN: // Клик мыши + x = windowEvent.motion.x; // Получаем координаты клика y = windowEvent.motion.y; + + // Преобразуем в координаты клетки (0-7) xc = int(y / (board->H / 10) - 1); yc = int(x / (board->W / 10) - 1); + + // Проверка кликов на специальных кнопках: if (xc == -1 && yc == -1 && board->history_mtx.size() > 1) { - resp = Response::BACK; + resp = Response::BACK; // Кнопка "Назад" } else if (xc == -1 && yc == 8) { - resp = Response::REPLAY; + resp = Response::REPLAY; // Кнопка "Повтор" } else if (xc >= 0 && xc < 8 && yc >= 0 && yc < 8) { - resp = Response::CELL; + resp = Response::CELL; // Клик по игровому полю } else { - xc = -1; + xc = -1; // Клик вне значимых областей yc = -1; } break; - case SDL_WINDOWEVENT: + + case SDL_WINDOWEVENT: // События окна if (windowEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { - board->reset_window_size(); + board->reset_window_size(); // Обработка изменения размера break; } } - if (resp != Response::OK) - break; + + if (resp != Response::OK) // Если было значимое событие + break; // Выходим из цикла } } - return {resp, xc, yc}; + return { resp, xc, yc }; // Возвращаем результат } + // Метод ожидания действия пользователя (без привязки к клеткам) + // Используется в меню и диалогах Response wait() const { SDL_Event windowEvent; Response resp = Response::OK; + while (true) { if (SDL_PollEvent(&windowEvent)) { switch (windowEvent.type) { - case SDL_QUIT: + case SDL_QUIT: // Закрытие окна resp = Response::QUIT; break; - case SDL_WINDOWEVENT_SIZE_CHANGED: + + case SDL_WINDOWEVENT_SIZE_CHANGED: // Изменение размера board->reset_window_size(); break; - case SDL_MOUSEBUTTONDOWN: { + + case SDL_MOUSEBUTTONDOWN: // Клик мыши + { int x = windowEvent.motion.x; int y = windowEvent.motion.y; + // Проверяем только кнопку "Повтор" (специальная область) int xc = int(y / (board->H / 10) - 1); int yc = int(x / (board->W / 10) - 1); if (xc == -1 && yc == 8) @@ -90,6 +110,7 @@ class Hand } break; } + if (resp != Response::OK) break; } @@ -97,6 +118,6 @@ class Hand return resp; } - private: - Board *board; -}; +private: + Board* board; // Указатель на игровую доску для взаимодействия +}; \ No newline at end of file diff --git a/Game/Logic.h b/Game/Logic.h index 9e1fdc4..33d8915 100644 --- a/Game/Logic.h +++ b/Game/Logic.h @@ -6,26 +6,32 @@ #include "Board.h" #include "Config.h" -const int INF = 1e9; +const int INF = 1e9; // Константа для представления "бесконечности" в алгоритме class Logic { - public: - Logic(Board *board, Config *config) : board(board), config(config) +public: + // Конструктор: инициализация доски, конфигурации и генератора случайных чисел + Logic(Board* board, Config* config) : board(board), config(config) { - rand_eng = std::default_random_engine ( + // Инициализация генератора случайных чисел (если не отключено в конфиге) + rand_eng = std::default_random_engine( !((*config)("Bot", "NoRandom")) ? unsigned(time(0)) : 0); + // Загрузка настроек бота из конфигурации scoring_mode = (*config)("Bot", "BotScoringType"); optimization = (*config)("Bot", "Optimization"); } + // Основной метод для поиска лучших ходов для заданного цвета vector find_best_turns(const bool color) { next_best_state.clear(); next_move.clear(); + // Рекурсивный поиск лучшего хода find_first_best_turn(board->get_board(), color, -1, -1, 0); + // Сборка последовательности лучших ходов из найденных состояний int cur_state = 0; vector res; do @@ -37,55 +43,64 @@ class Logic } private: + // Применение хода к копии доски (без изменения оригинала) vector> make_turn(vector> mtx, move_pos turn) const { - if (turn.xb != -1) - mtx[turn.xb][turn.yb] = 0; + if (turn.xb != -1) // Если ход включает взятие фигуры + mtx[turn.xb][turn.yb] = 0; // Удаляем взятую фигуру + // Проверка на превращение в дамку if ((mtx[turn.x][turn.y] == 1 && turn.x2 == 0) || (mtx[turn.x][turn.y] == 2 && turn.x2 == 7)) mtx[turn.x][turn.y] += 2; + // Перемещение фигуры mtx[turn.x2][turn.y2] = mtx[turn.x][turn.y]; mtx[turn.x][turn.y] = 0; return mtx; } - double calc_score(const vector> &mtx, const bool first_bot_color) const + // Расчет оценки текущего состояния доски + double calc_score(const vector>& mtx, const bool first_bot_color) const { - // color - who is max player + // Подсчет количества фигур каждого типа double w = 0, wq = 0, b = 0, bq = 0; for (POS_T i = 0; i < 8; ++i) { for (POS_T j = 0; j < 8; ++j) { - w += (mtx[i][j] == 1); - wq += (mtx[i][j] == 3); - b += (mtx[i][j] == 2); - bq += (mtx[i][j] == 4); + w += (mtx[i][j] == 1); // Белые простые + wq += (mtx[i][j] == 3); // Белые дамки + b += (mtx[i][j] == 2); // Черные простые + bq += (mtx[i][j] == 4); // Черные дамки + // Дополнительная оценка потенциала фигур if (scoring_mode == "NumberAndPotential") { - w += 0.05 * (mtx[i][j] == 1) * (7 - i); - b += 0.05 * (mtx[i][j] == 2) * (i); + w += 0.05 * (mtx[i][j] == 1) * (7 - i); // Белые ближе к дамочному полю + b += 0.05 * (mtx[i][j] == 2) * (i); // Черные ближе к дамочному полю } } } + // Корректировка оценки в зависимости от цвета бота if (!first_bot_color) { swap(b, w); swap(bq, wq); } - if (w + wq == 0) - return INF; - if (b + bq == 0) - return 0; + // Проверка на победу/поражение + if (w + wq == 0) return INF; // Противник не имеет фигур + if (b + bq == 0) return 0; // Бот не имеет фигур + + // Коэффициенты для дамок int q_coef = 4; if (scoring_mode == "NumberAndPotential") { q_coef = 5; } + // Формула оценки: (наши фигуры + дамки*коэф) / (фигуры противника + дамки*коэф) return (b + bq * q_coef) / (w + wq * q_coef); } + // Рекурсивный поиск лучшего хода (основная логика) double find_first_best_turn(vector> mtx, const bool color, const POS_T x, const POS_T y, size_t state, - double alpha = -1) + double alpha = -1) { next_best_state.push_back(-1); next_move.emplace_back(-1, -1, -1, -1); @@ -95,6 +110,7 @@ class Logic auto turns_now = turns; bool have_beats_now = have_beats; + // Если нет взятий и это не начальное состояние, переходим к рекурсивному поиску if (!have_beats_now && state != 0) { return find_best_turns_rec(mtx, 1 - color, 0, alpha); @@ -103,18 +119,22 @@ class Logic vector best_moves; vector best_states; + // Перебор всех возможных ходов for (auto turn : turns_now) { size_t next_state = next_move.size(); double score; if (have_beats_now) { + // Продолжаем серию взятий score = find_first_best_turn(make_turn(mtx, turn), color, turn.x2, turn.y2, next_state, best_score); } else { + // Обычный ход score = find_best_turns_rec(make_turn(mtx, turn), 1 - color, 0, best_score); } + // Обновление лучшего хода if (score > best_score) { best_score = score; @@ -125,13 +145,16 @@ class Logic return best_score; } + // Рекурсивный поиск с альфа-бета отсечением double find_best_turns_rec(vector> mtx, const bool color, const size_t depth, double alpha = -1, - double beta = INF + 1, const POS_T x = -1, const POS_T y = -1) + double beta = INF + 1, const POS_T x = -1, const POS_T y = -1) { + // База рекурсии - достигнута максимальная глубина if (depth == Max_depth) { return calc_score(mtx, (depth % 2 == color)); } + // Поиск возможных ходов для текущей позиции if (x != -1) { find_turns(x, y, mtx); @@ -141,30 +164,36 @@ class Logic auto turns_now = turns; bool have_beats_now = have_beats; + // Если нет взятий и это продолжение хода конкретной фигуры if (!have_beats_now && x != -1) { return find_best_turns_rec(mtx, 1 - color, depth + 1, alpha, beta); } + // Если нет возможных ходов if (turns.empty()) return (depth % 2 ? 0 : INF); double min_score = INF + 1; double max_score = -1; + // Перебор всех возможных ходов for (auto turn : turns_now) { double score = 0.0; if (!have_beats_now && x == -1) { + // Обычный ход score = find_best_turns_rec(make_turn(mtx, turn), 1 - color, depth + 1, alpha, beta); } else { + // Продолжение серии ходов (для взятий) score = find_best_turns_rec(make_turn(mtx, turn), color, depth, alpha, beta, turn.x2, turn.y2); } + // Обновление минимальной и максимальной оценки min_score = min(min_score, score); max_score = max(max_score, score); - // alpha-beta pruning + // Альфа-бета отсечение if (depth % 2) alpha = max(alpha, max_score); else @@ -176,28 +205,33 @@ class Logic } public: + // Поиск всех возможных ходов для цвета void find_turns(const bool color) { find_turns(color, board->get_board()); } + // Поиск всех возможных ходов для конкретной фигуры void find_turns(const POS_T x, const POS_T y) { find_turns(x, y, board->get_board()); } private: - void find_turns(const bool color, const vector> &mtx) + // Поиск всех возможных ходов для цвета на заданной доске + void find_turns(const bool color, const vector>& mtx) { vector res_turns; bool have_beats_before = false; + // Перебор всех клеток доски for (POS_T i = 0; i < 8; ++i) { for (POS_T j = 0; j < 8; ++j) { - if (mtx[i][j] && mtx[i][j] % 2 != color) + if (mtx[i][j] && mtx[i][j] % 2 != color) // Если фигура нужного цвета { find_turns(i, j, mtx); + // Приоритет взятий if (have_beats && !have_beats_before) { have_beats_before = true; @@ -211,21 +245,23 @@ class Logic } } turns = res_turns; + // Перемешивание ходов для разнообразия (если включено) shuffle(turns.begin(), turns.end(), rand_eng); have_beats = have_beats_before; } - void find_turns(const POS_T x, const POS_T y, const vector> &mtx) + // Поиск всех возможных ходов для конкретной фигуры на заданной доске + void find_turns(const POS_T x, const POS_T y, const vector>& mtx) { turns.clear(); have_beats = false; POS_T type = mtx[x][y]; - // check beats + // Проверка возможных взятий switch (type) { - case 1: - case 2: - // check pieces + case 1: // Белая простая + case 2: // Черная простая + // Проверка взятий для простых шашек for (POS_T i = x - 2; i <= x + 2; i += 4) { for (POS_T j = y - 2; j <= y + 2; j += 4) @@ -239,8 +275,8 @@ class Logic } } break; - default: - // check queens + default: // Дамки + // Проверка взятий для дамок for (POS_T i = -1; i <= 1; i += 2) { for (POS_T j = -1; j <= 1; j += 2) @@ -266,29 +302,30 @@ class Logic } break; } - // check other turns + // Если есть взятия - возвращаем только их if (!turns.empty()) { have_beats = true; return; } + // Проверка обычных ходов (если нет взятий) switch (type) { - case 1: - case 2: - // check pieces + case 1: // Белая простая + case 2: // Черная простая + // Обычные ходы для простых шашек + { + POS_T i = ((type % 2) ? x - 1 : x + 1); // Направление движения + for (POS_T j = y - 1; j <= y + 1; j += 2) { - POS_T i = ((type % 2) ? x - 1 : x + 1); - for (POS_T j = y - 1; j <= y + 1; j += 2) - { - if (i < 0 || i > 7 || j < 0 || j > 7 || mtx[i][j]) - continue; - turns.emplace_back(x, y, i, j); - } - break; + if (i < 0 || i > 7 || j < 0 || j > 7 || mtx[i][j]) + continue; + turns.emplace_back(x, y, i, j); } - default: - // check queens + break; + } + default: // Дамки + // Обычные ходы для дамок for (POS_T i = -1; i <= 1; i += 2) { for (POS_T j = -1; j <= 1; j += 2) @@ -305,17 +342,17 @@ class Logic } } - public: - vector turns; - bool have_beats; - int Max_depth; +public: + vector turns; // Список возможных ходов + bool have_beats; // Флаг наличия взятий + int Max_depth; // Максимальная глубина рекурсии - private: - default_random_engine rand_eng; - string scoring_mode; - string optimization; - vector next_move; - vector next_best_state; - Board *board; - Config *config; -}; +private: + default_random_engine rand_eng; // Генератор случайных чисел + string scoring_mode; // Режим оценки позиции + string optimization; // Уровень оптимизации + vector next_move; // Следующий ход для каждого состояния + vector next_best_state; // Лучшее следующее состояние + Board* board; // Указатель на доску + Config* config; // Указатель на конфигурацию +}; \ No newline at end of file diff --git a/Models/Move.h b/Models/Move.h index 9569429..bf6897f 100644 --- a/Models/Move.h +++ b/Models/Move.h @@ -1,28 +1,47 @@ #pragma once #include +// Тип для хранения координат на игровом поле (8-битное целое со знаком) typedef int8_t POS_T; +// Структура, описывающая ход в игре (перемещение шашки) struct move_pos { - POS_T x, y; // from - POS_T x2, y2; // to - POS_T xb = -1, yb = -1; // beaten + // Координаты исходной позиции шашки + POS_T x, y; // from (откуда) - move_pos(const POS_T x, const POS_T y, const POS_T x2, const POS_T y2) : x(x), y(y), x2(x2), y2(y2) + // Координаты целевой позиции шашки + POS_T x2, y2; // to (куда) + + // Координаты битой шашки (если есть) + // Значения -1 означают отсутствие боя + POS_T xb = -1, yb = -1; // beaten (побитая шашка) + + // Конструктор для обычного хода (без взятия) + move_pos(const POS_T x, const POS_T y, const POS_T x2, const POS_T y2) + : x(x), y(y), x2(x2), y2(y2) // Инициализация только основных координат { } - move_pos(const POS_T x, const POS_T y, const POS_T x2, const POS_T y2, const POS_T xb, const POS_T yb) - : x(x), y(y), x2(x2), y2(y2), xb(xb), yb(yb) + + // Конструктор для хода со взятием (боем) + move_pos(const POS_T x, const POS_T y, const POS_T x2, const POS_T y2, + const POS_T xb, const POS_T yb) + : x(x), y(y), x2(x2), y2(y2), xb(xb), yb(yb) // Инициализация всех полей { } - bool operator==(const move_pos &other) const + // Оператор сравнения ходов + // Два хода считаются равными, если совпадают начальные и конечные позиции + // (координаты битой шашки не учитываются при сравнении) + bool operator==(const move_pos& other) const { - return (x == other.x && y == other.y && x2 == other.x2 && y2 == other.y2); + return (x == other.x && y == other.y && + x2 == other.x2 && y2 == other.y2); } - bool operator!=(const move_pos &other) const + + // Оператор неравенства (просто инвертирует результат оператора ==) + bool operator!=(const move_pos& other) const { return !(*this == other); } -}; +}; \ No newline at end of file diff --git a/Models/Response.h b/Models/Response.h index d07a293..87a4d71 100644 --- a/Models/Response.h +++ b/Models/Response.h @@ -1,10 +1,11 @@ #pragma once +// Перечисление возможных ответов/действий в игре enum class Response { - OK, - BACK, - REPLAY, - QUIT, - CELL + OK, // Успешное выполнение действия (хороший статус) + BACK, // Запрос на отмену/возврат предыдущего действия (например, отмена хода) + REPLAY, // Запрос на перезапуск игры (начать матч заново) + QUIT, // Запрос на выход из игры (прекращение текущего матча) + CELL // Указание на взаимодействие с клеткой игрового поля (выбор клетки для хода) }; diff --git a/settings.json b/settings.json index fbce46b..9361681 100644 --- a/settings.json +++ b/settings.json @@ -1,19 +1,19 @@ { - "WindowSize": { - "Width": 0, - "Hight": 0 - }, - "Bot": { - "IsWhiteBot": false, - "IsBlackBot": true, - "WhiteBotLevel": 0, - "BlackBotLevel": 5, - "BotScoringType": "NumberAndPotential", - "BotDelayMS": 0, - "NoRandom": false, - "Optimization": "O1" - }, - "Game": { - "MaxNumTurns": 120 + "WindowSize": { + "Width": 0, //ширина РѕРєРЅР° + "Hight": 0 //высота РѕРєРЅР° + }, + "Bot": { // Настройки, связанные СЃ ботом (искусственным интеллектом) + "IsWhiteBot": false, // Флаг, указывающий, является ли Р±РѕС‚ РёРіСЂРѕРєРѕРј Р·Р° белых (false - нет) + "IsBlackBot": true, // Флаг, указывающий, является ли Р±РѕС‚ РёРіСЂРѕРєРѕРј Р·Р° черных (true - РґР°) + "WhiteBotLevel": 0, // Уровень сложности бота Р·Р° белых (0 - вероятно, означает отсутствие бота) + "BlackBotLevel": 5, // Уровень сложности бота Р·Р° черных (5 - максимальный уровень) + "BotScoringType": "NumberAndPotential", // РўРёРї оценки позиции для бота (учитывает количество фигур Рё РёС… потенциал) + "BotDelayMS": 0, // Задержка С…РѕРґР° бота РІ миллисекундах (0 - мгновенный С…РѕРґ) + "NoRandom": false, // Флаг, отключающий случайность РІ РёРіСЂРµ бота (false - случайность включена) + "Optimization": "O1" // Уровень оптимизации алгоритмов бота (O1 - базовый уровень) + }, + "Game": { // Настройки, связанные СЃ игровым процессом + "MaxNumTurns": 120 // Максимальное количество С…РѕРґРѕРІ РІ РёРіСЂРµ (120 - лимит) } -} + }