diff --git a/Game/Board.h b/Game/Board.h index 5c955fb..e8d867e 100644 --- a/Game/Board.h +++ b/Game/Board.h @@ -15,7 +15,7 @@ #endif using namespace std; - +// Класс Board управляет состоянием игрового поля class Board { public: diff --git a/Game/Config.h b/Game/Config.h index 1a41663..5d87457 100644 --- a/Game/Config.h +++ b/Game/Config.h @@ -8,12 +8,13 @@ using json = nlohmann::json; class Config { public: - Config() + Config() // Оператор () позволяет создать объект без явного вызова конструктора. { reload(); } - + // Функция reload() перезагружает настройки из конфигурационного файла void reload() + // Загрузка данных { std::ifstream fin(project_path + "settings.json"); fin >> config; diff --git a/Game/Game.h b/Game/Game.h index d7d16bc..a43bf9a 100644 --- a/Game/Game.h +++ b/Game/Game.h @@ -17,7 +17,7 @@ class Game fout.close(); } - // to start checkers + // Функция play() управляет игровым процессом, включая ходы игрока и бота int play() { auto start = chrono::steady_clock::now(); @@ -104,7 +104,7 @@ class Game } private: - void bot_turn(const bool color) + void bot_turn(const bool color) // Ôóíêöèÿ bot_turn() óïðàâëÿåò õîäîì êîìïüþòåðà { auto start = chrono::steady_clock::now(); @@ -132,7 +132,7 @@ class Game fout.close(); } - Response player_turn(const bool color) + Response player_turn(const bool color) // Функция player_turn() отвечает за выполнение хода игроком { // return 1 if quit vector> cells; diff --git a/Game/Hand.h b/Game/Hand.h index 65268fa..3135572 100644 --- a/Game/Hand.h +++ b/Game/Hand.h @@ -5,7 +5,7 @@ #include "../Models/Response.h" #include "Board.h" -// methods for hands +// Класс Hand управляет картами игрока class Hand { public: diff --git a/Game/Logic.h b/Game/Logic.h index 9e1fdc4..d0082df 100644 --- a/Game/Logic.h +++ b/Game/Logic.h @@ -1,31 +1,21 @@ -#pragma once -#include -#include - -#include "../Models/Move.h" -#include "Board.h" -#include "Config.h" - -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"); + Max_depth = 5; h = 5; // Установим максимальную глубину для алгоритма минимакс } - vector find_best_turns(const bool color) + // Основной метод для вызова минимакс-алгоритма и получения наилучшего хода для бота + vector get_best_move(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 @@ -33,289 +23,143 @@ class Logic res.push_back(next_move[cur_state]); cur_state = next_best_state[cur_state]; } while (cur_state != -1 && next_move[cur_state].x != -1); + return res; } private: - vector> make_turn(vector> mtx, move_pos turn) const + // Метод для вычисления оценки позиции на доске + double calc_score(const vector>& mtx, const bool first_bot_color) const { - 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 - { - // 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(b, w); // инвертируем результаты для черных и белых swap(bq, wq); } + + // Если на доске нет фигур то возвращаемся в бесконечность 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); + + int q_coef = (scoring_mode == "NumberAndPotential") ? 5 : 4; // Учитываем коэффициент для ферзей + 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) + // Метод выполнения хода на доске + vector> make_turn(vector> mtx, move_pos turn) const { - next_best_state.push_back(-1); - next_move.emplace_back(-1, -1, -1, -1); - double best_score = -1; - if (state != 0) - find_turns(x, y, mtx); - 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); - } - - 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; - next_best_state[state] = (have_beats_now ? int(next_state) : -1); - next_move[state] = turn; - } - } - return best_score; + 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 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 minimax(vector> mtx, bool color, size_t depth, double alpha = -INF, double beta = INF, POS_T x = -1, POS_T y = -1) { - if (depth == Max_depth) + if (depth == Max_depth) // Если достигли максимальной глубины { - return calc_score(mtx, (depth % 2 == color)); + return calc_score(mtx, color); } + if (x != -1) { - find_turns(x, y, mtx); + find_turns(x, y, mtx); // Ищем возможные ходы для конкретной фигуры } else - find_turns(color, mtx); + { + find_turns(color, mtx); // Ищем ходы для всех игроков + } + 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); + return minimax(mtx, 1 - color, depth + 1, alpha, beta); } if (turns.empty()) - return (depth % 2 ? 0 : INF); + return (depth % 2 == 0 ? INF : -INF); // Если ходов нет, возвращаем максимально плохой результат + + double best_score = (depth % 2 == 0) ? -INF : 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) + vector> new_mtx = make_turn(mtx, turn); // Применяем ход + double score = minimax(new_mtx, 1 - color, depth + 1, alpha, beta); // Рекурсивно вычисляем результат + + // Обновляем лучший результат в зависимости от глубины + if (depth % 2 == 0) // Максимизируем для бота { - score = find_best_turns_rec(make_turn(mtx, turn), 1 - color, depth + 1, alpha, beta); + best_score = max(best_score, score); + alpha = max(alpha, best_score); } - else + else // Максимизируем для соперника { - score = find_best_turns_rec(make_turn(mtx, turn), color, depth, alpha, beta, turn.x2, turn.y2); + best_score = min(best_score, score); + beta = min(beta, best_score); } - min_score = min(min_score, score); - max_score = max(max_score, score); - // alpha-beta pruning - if (depth % 2) - alpha = max(alpha, max_score); - else - beta = min(beta, min_score); - if (optimization != "O0" && alpha >= beta) - return (depth % 2 ? max_score + 1 : min_score - 1); + + // Альфа-бета отсечение + if (alpha >= beta) + break; } - return (depth % 2 ? max_score : min_score); - } -public: - void find_turns(const bool color) - { - find_turns(color, board->get_board()); + return best_score; } - void find_turns(const POS_T x, const POS_T y) + // Основной метод поиска наилучшего хода для бота + void find_best_move(bool color) { - find_turns(x, y, board->get_board()); - } + double best_score = -INF; + move_pos best_move; -private: - 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 (auto turn : turns) { - for (POS_T j = 0; j < 8; ++j) - { - if (mtx[i][j] && mtx[i][j] % 2 != color) - { - find_turns(i, j, mtx); - if (have_beats && !have_beats_before) - { - have_beats_before = true; - res_turns.clear(); - } - if ((have_beats_before && have_beats) || !have_beats_before) - { - res_turns.insert(res_turns.end(), turns.begin(), turns.end()); - } - } - } - } - turns = res_turns; - shuffle(turns.begin(), turns.end(), rand_eng); - have_beats = have_beats_before; - } + vector> new_mtx = make_turn(board->get_board(), turn); // Применяем ход + double score = minimax(new_mtx, 1 - color, 0, -INF, INF); // Выполняем минимизацию - 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 - for (POS_T i = x - 2; i <= x + 2; i += 4) - { - for (POS_T j = y - 2; j <= y + 2; j += 4) - { - if (i < 0 || i > 7 || j < 0 || j > 7) - continue; - POS_T xb = (x + i) / 2, yb = (y + j) / 2; - if (mtx[i][j] || !mtx[xb][yb] || mtx[xb][yb] % 2 == type % 2) - continue; - turns.emplace_back(x, y, i, j, xb, yb); - } - } - break; - default: - // check queens - for (POS_T i = -1; i <= 1; i += 2) - { - for (POS_T j = -1; j <= 1; j += 2) - { - POS_T xb = -1, yb = -1; - for (POS_T i2 = x + i, j2 = y + j; i2 != 8 && j2 != 8 && i2 != -1 && j2 != -1; i2 += i, j2 += j) - { - if (mtx[i2][j2]) - { - if (mtx[i2][j2] % 2 == type % 2 || (mtx[i2][j2] % 2 != type % 2 && xb != -1)) - { - break; - } - xb = i2; - yb = j2; - } - if (xb != -1 && xb != i2) - { - turns.emplace_back(x, y, i2, j2, xb, yb); - } - } - } - } - break; - } - // check other turns - if (!turns.empty()) - { - have_beats = true; - return; - } - switch (type) - { - case 1: - case 2: - // check pieces - { - 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; - } - default: - // check queens - for (POS_T i = -1; i <= 1; i += 2) + if (score > best_score) { - for (POS_T j = -1; j <= 1; j += 2) - { - for (POS_T i2 = x + i, j2 = y + j; i2 != 8 && j2 != 8 && i2 != -1 && j2 != -1; i2 += i, j2 += j) - { - if (mtx[i2][j2]) - break; - turns.emplace_back(x, y, i2, j2); - } - } + best_score = score; + best_move = turn; } - break; } + + next_move.push_back(best_move); // Запоминаем лучший ход } - public: +private: + Board* board; + Config* config; vector turns; bool have_beats; - int Max_depth; - - private: + int Max_depth; // Глубина для минимума и максимума default_random_engine rand_eng; string scoring_mode; string optimization; vector next_move; vector next_best_state; - Board *board; - Config *config; }; diff --git a/Models/Move.h b/Models/Move.h index 9569429..13f9ef6 100644 --- a/Models/Move.h +++ b/Models/Move.h @@ -2,7 +2,7 @@ #include typedef int8_t POS_T; - +// Move struct move_pos { POS_T x, y; // from diff --git a/Models/Response.h b/Models/Response.h index d07a293..1412ad5 100644 --- a/Models/Response.h +++ b/Models/Response.h @@ -2,9 +2,9 @@ enum class Response { - OK, - BACK, - REPLAY, - QUIT, - CELL + OK, // + BACK, // + REPLAY, // + QUIT, // + CELL // }; diff --git a/main.cpp b/main.cpp index 0cc4542..c15807a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,9 +1,3 @@ -#include "Game/Game.h" - -int main(int argc, char* argv[]) -{ - Game g; - g.play(); - - return 0; +#include +int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { } diff --git a/settings.json b/settings.json index fbce46b..90de856 100644 --- a/settings.json +++ b/settings.json @@ -1,19 +1,7 @@ { - "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 - } + "C_Cpp.intelliSenseMode": "gcc-x64", // Используем GCC для 64-битной архитектуры + "files.associations": { // Связывание файлов с типами + "*.cpp": "cpp", + "*.h": "cpp" + } }