Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП_23_ИСТ_1_1_Какушкина_Ольга_Витальевна_KР_03АБ.docx
Скачиваний:
4
Добавлен:
23.06.2025
Размер:
420.18 Кб
Скачать
  • Класс Game - управляет игровым процессом:

    • Инициализация игры (setup())

    • Выбор стороны (choose())

    • Поочередные ходы игрока и компьютера

    • Обработка пользовательского ввода

    • Определение победителя

    Поток данных

    1. 1. Инициализация:

      • Пользователь выбирает размер доски

      • Пользователь выбирает сторону (черные или белые)

    2. Игровой цикл:

      • Если ход компьютера:

        • AI анализирует состояние доски

        • Выбирает лучший ход с помощью MiniMax

        • Применяет ход на доске

      • Если ход игрока:

        • Отображаются возможные варианты хода

        • Обрабатывается пользовательский ввод

        • Применяется выбранный ход

      • После каждого хода проверяется условие окончания игры

    3. Завершение:

      • Подсчет фишек

      • Объявление победителя

      • Предложение сыграть снова

    3. Описание ключевых компонентов

    1. Класс Board (Игровая доска)

    Поля:

    vector<vector<char>> board - матрица N×N (размер задается пользователем)

    const char white = 'W' - символ белой фишки

    const char black = 'B' - символ черной фишки

    const char empty = '+' - символ пустой клетки

    static const int direct[6][2] - направления соседних клеток в гексагональной сетке

    Основные методы:

    Инициализация и отображение:

    Board(int size) - конструктор, создает пустую доску заданного размера

    printBoard() - выводит доску в консоль с гексагональным форматированием

    copy() - создает глубокую копию доски

    Проверки:

    inBoard(x, y) - проверяет, находятся ли координаты в пределах доски

    isAdjacent(x1, y1, x2, y2) - проверяет, являются ли клетки соседними

    isJumpMoveValid() - проверяет допустимость прыжкового хода

    isGameOver() - проверяет, заполнена ли доска полностью

    hasStandardMoves(p) - проверяет наличие стандартных ходов у игрока

    Работа с фишками:

    place(x, y, p) - размещает новую фишку игрока p в указанной позиции

    move(fromX, fromY, toX, toY, p) - перемещает фишку игрока p

    flipAdjacentOpponentPieces(x, y, p) - переворачивает соседние фишки противника

    countPieces(p) - подсчитывает количество фишек игрока

    Генерация ходов:

    getPlayerPieces(p) - возвращает все фишки игрока

    getEmpty() - возвращает все пустые клетки

    getValidNewPlacements(p) - генерирует допустимые позиции для новых фишек

    getValidMovesForPiece(x, y, p) - генерирует допустимые ходы для конкретной фишки

    getValidMoves(p) - возвращает все допустимые ходы для игрока

    2. Класс ai (Искусственный интеллект)

    Поля:

    int maxDepth - глубина поиска (по умолчанию 3)

    Основные методы:

    Оценка позиции:

    evaluate(board, p) - оценивает текущую позицию для игрока p:

    • Считает разницу в количестве фишек

    • Учитывает подвижность (количество возможных ходов)

    • При окончании игры возвращает разницу фишек

    Алгоритм MiniMax:

    minimax(board, depth, alpha, beta, maximizingPlayer, p):

    • Рекурсивно оценивает возможные ходы

    • Использует альфа-бета отсечение для оптимизации

    • На глубине 0 или конце игры вызывает evaluate()

    • Для maximizingPlayer выбирает ход с максимальной оценкой

    • Для minimizingPlayer - с минимальной

    Выбор хода:

    next(board, p) - выбирает лучший ход для игрока p:

    • Генерирует все допустимые ходы

    • Для каждого хода вызывает minimax()

    • Возвращает ход с максимальной оценкой

    3. Класс Game (Игровой процесс)

    Поля:

    Board board - игровая доска

    Player computer - цвет фишек компьютера

    Player player - цвет фишек игрока

    AI robotAI - ИИ-соперник

    Основные методы:

    Настройка игры:

    setup() - запрашивает размер доски и инициализирует игру

    choose() - позволяет игроку выбрать цвет фишек

    Игровой цикл:

    play() - основной цикл игры с чередованием ходов

    computerTurn() - обработка хода компьютера

    playerTurn() - обработка хода игрока

    processPlayerMove() - валидация и применение хода игрока

    4. Описание функций

    4.1 Логика игры

    isAdjacent(x1, y1, x2, y2) - проверяет соседство клеток по 6 направлениям

    isJumpMoveValid(from, to) - проверяет допустимость прыжка через одну клетку

    flipAdjacentOpponentPieces(x, y, p) - переворачивает все смежные фишки противника

    getValidMoves(p) - основной метод генерации ходов:

    • При наличии стандартных ходов возвращает их

    • При отсутствии - разрешает ход в любую свободную клетку

    4.2 Алгоритм ии

    evaluate() - оценочная функция:

    • Основной критерий: разница количества фишек

    • Дополнительный критерий: разница в подвижности (количество возможных ходов)

    • При окончании игры: больший вес разницы фишек

    minimax() - рекурсивный алгоритм:

    • Глубина поиска: 3 (настраивается)

    • Альфа-бета отсечение для оптимизации

    • Чередование максимизации и минимизации оценки

    next() - выбор хода:

    • Перебирает все допустимые ходы

    • Для каждого вычисляет оценку через minimax()

    • Выбирает ход с максимальной оценкой

    5. Возможные улучшения

    1. Улучшение ИИ

    • Добавить кэширование результатов (transposition table)

    • Расширить глубину поиска до 4+

    2. Улучшения интерфейса

    • Интерфейс на SFML

    • Цветовая подсветка возможных ходов

    4. Дополнительные функции

    • Поддержка сетевой игры

    • Сохранение/загрузка партии

    • Поддержка режима “человек против человека”

    • Выбор категории игры с ИИ от легкого уровня до сложного.

    Руководство системного администратора

    Программа "Гексагон" представляет собой консольную стратегическую игру с искусственным интеллектом, реализованную на C++. Основные цели программы:

    • Обучение базовым стратегиям перемещения и захвата фишек на гексагональной доске

    • Развитие логического и пространственного мышления

    • Проведение партий между человеком и компьютером с пошаговым управлением

    • Демонстрация работы алгоритма MiniMax с альфа-бета отсечением

    1.1 Основные компоненты:

    1. Текстовый интерфейс:

      • Отображение гексагонального поля размером N×N (по выбору пользователя)

      • Визуализация фишек игроков (W - белые, B - черные)

      • Отображение доступных ходов

    2. Логика игры:

      • Правила размещения новых фишек

      • Правила перемещения фишек (простые и прыжковые ходы)

      • Механизм переворота фишек противника

      • Условия окончания игры и определения победителя

    3. Искусственный интеллект:

      • Реализация алгоритма MiniMax с альфа-бета отсечением

      • Оценочная функция, учитывающая количество фишек и подвижность

      • Глубина поиска 3 хода (настраивается)

    4. Подсчет очков:

      • Подсчет фишек каждого игрока

      • Определение победителя по количеству фишек

    5. Обработка ввода:

      • Ввод координат для размещения фишек

      • Ввод координат для перемещения фишек

      • Выбор типа хода (размещение/перемещение)

    1.2 Основные принципы функционирования

    • Игрок и компьютер ходят по очереди

    • Цель игры - захватить больше фишек противника к моменту заполнения доски

    • ИИ использует алгоритм MiniMax с глубиной 3 для расчета оптимальных ходов

    • Игра завершается при полном заполнении доски или отсутствии допустимых ходов

    2. Архитектура и принципы функционирования

    2.1 Архитектура системы

    Клиентская часть:

    • Консольный интерфейс с отображением гексагональной доски

    • Список допустимых ходов для игрока

    • Информация о текущем состоянии игры

    Логическая часть:

    • Класс Board - управление игровым полем и правилами

    • Класс AI - реализация искусственного интеллекта

    • Класс Game - управление игровым процессом

    Основные функции:

    • Board::place() - размещение новой фишки

    • Board::move() - перемещение существующей фишки

    • AI::minimax() - алгоритм поиска оптимального хода

    • Game::play() - основной игровой цикл

    2.2 Принцип работы

    1. Инициализация доски заданного размера

    2. Выбор стороны (черные или белые)

    3. Поочередные ходы игрока и компьютера:

      • Игрок выбирает тип хода (размещение/перемещение) и координаты

      • Компьютер автоматически рассчитывает и делает оптимальный ход

    4. После каждого хода:

      • Переворачиваются смежные фишки противника

      • Проверяется условие окончания игры

    5. Игра завершается при полном заполнении доски

    6. Определяется победитель по количеству фишек

    7. Предлагается сыграть еще раз

    3. Системные требования

    3.1 Минимальные требования

    • Операционная система: Windows 10/11

    • Процессор: Intel Core i5

    • Оперативная память: 512 МБ

    • Место на диске: 5 МБ

    • Компилятор C++: поддержка стандарта C++11

    3.2 Рекомендуемые требования

    • Операционная система: Windows 10/11

    • Процессор: Intel Core i5 или аналогичный (2 ГГц и выше)

    • Оперативная память: 1 ГБ и более

    • Место на диске: 10 МБ

    4. Установка программы

    Для Windows:

    • Установите MinGW (https://www.mingw-w64.org/) или Visual Studio

    4.2 Сборка и запуск

    Соберите проект:

    g++ main.cpp -o hexagon_game -std=c++11

    Запустите:

    hexagon_game.exe# Windows

    5. Административные функции

    5.1 Настройка сложности ИИ

    Уровень сложности ИИ можно настроить, изменив параметр глубины поиска в конструкторе класса AI:

    explicit AI(int depth = 3) : maxDepth(depth) {}

    Рекомендуемые значения:

    • 2-3 - для начинающих

    • 4-5 - для продвинутых игроков

    • Более 5 может значительно увеличить время расчета ходов

    5.2 Настройка правил игры

    Основные параметры игры можно изменить в коде:

    1. Размер доски (по умолчанию 9x9):

    board = Board(dimensions); // в Game::setup()

    1. Символы для отображения фишек:

    const char Board::white = 'W';

    const char Board::black = 'B';

    const char Board::empty = '+';

    1. Правила переворота фишек (метод flipAdjacentOpponentPieces)

    6. Проверка работоспособности

    6.1 Тестирование основных функций

    1. Инициализация доски:

      • Проверить создание доски разного размера (5x5, 9x9)

      • Убедиться, что все клетки изначально пустые ('+')

    2. Размещение фишек:

      • Проверить размещение фишек в допустимых позициях

      • Убедиться, что нельзя разместить фишку в занятую клетку

      • Проверить переворот соседних фишек противника

    3. Перемещение фишек:

      • Проверить простые перемещения на соседние клетки

      • Проверить прыжковые перемещения через одну клетку

      • Убедиться, что нельзя переместить фишку противника

    4. Окончание игры:

      • Заполнить доску и проверить корректное определение победителя

      • Проверить обработку ситуации без допустимых ходов

    6.2 Тестирование ИИ

    1. Проверить время ответа ИИ для разных размеров доски

    2. Убедиться, что ИИ делает допустимые ходы

    3. Проверить, что сложность игры меняется с изменением глубины поиска

    4. Убедиться, что ИИ корректно обрабатывает конечные позиции

    6.3 Тестирование интерфейса

    1. Проверить отображение доски разного размера

    2. Убедиться в корректности отображения фишек и пустых клеток

    3. Проверить обработку некорректного ввода пользователя

    4. Убедиться в правильности отображения сообщений о ходе игры

    7. Возможные ошибки и их устранение

    Ошибка

    Причина

    Решение

    g++: command not found

    Компилятор не установлен

    Установите g++ или clang++

    Ошибка компиляции

    Отсутствуют зависимости или неверный синтаксис

    Проверьте наличие всех заголовочных файлов (<iostream>, <vector> и др.) и синтаксис

    ИИ не отвечает/зависает

    Слишком большая глубина поиска или бесконечный цикл

    Уменьшите maxDepth в классе AI (например, до 3)

    Некорректный ход ИИ

    Ошибка в логике minimax() или evaluate()

    Проверьте GenerateAllMoves() и Evaluate(), добавьте логирование

    Победа засчитывается сразу

    Неправильная инициализация доски или проверка isGameOver()

    Проверьте InitBoard() и логику isGameOver()

    Некорректное отображение доски

    Ошибка в printBoard() или неверные символы

    Проверьте символы white, black, empty и отступы в printBoard()

    Игрок не может сделать ход

    Ошибка в getValidMoves() или hasStandardMoves()

    Добавьте отладку для проверки допустимых ходов

    Некорректный переворот фишек

    Ошибка в flipAdjacentOpponentPieces()

    Проверьте направления direct и логику переворота

    Ошибка сегментации

    Выход за границы массива в методах доски

    Добавьте проверки inBoard() перед доступом к board[x][y]

    Некорректный ввод

    Пользователь ввел нечисловые значения

    Добавьте проверку ввода в playerTurn() и setup()

    8. Техническое обслуживание

    1. Изменение логики игры:

      • Правьте main.cpp (особенно методы move(), place(), flipAdjacentOpponentPieces()).

      • Для изменения правил победы измените isGameOver() и evaluate().

    2. Оптимизация ИИ:

      • Измените глубину поиска в конструкторе AI (по умолчанию 3).

      • Для ускорения работы оптимизируйте minimax() (кеширование, отсечение неважных веток).

    3. Настройка отображения:

      • Измените символы white, black, empty в классе Board.

      • Для изменения размера доски правьте setup().

    4. Отладка:

      • Добавьте логирование в ключевые методы (move, place, minimax).

      • Проверяйте граничные случаи (пустая доска, почти заполненная доска).

    5. Расширение функционала:

      • Добавьте меню настроек (цвета, размер доски, уровень сложности).

      • Реализуйте сохранение/загрузку игры (сериализация объекта Board).

    Для быстрого исправления критических ошибок:

    1. Всегда проверяйте входные координаты с помощью inBoard().

    2. Убедитесь, что isAdjacent() и isJumpMoveValid() работают корректно.

    3. Проверьте инициализацию доски в конструкторе Board(int size).

    Программный код

    #include <iostream>

    #include <vector>

    #include <utility>

    #include <algorithm>

    #include <climits>

    #include <cstdlib>

    using namespace std;

    const int SIZE = 9;

    const int CENTER_START = 3;

    const int CENTER_END = 5;

    const char EMPTY = '.';

    const char PLAYER1 = 'X'; // верхний лепесток

    const char PLAYER2 = 'O'; // нижний лепесток

    typedef pair<int, int> Coord;

    typedef vector<vector<char>> Board;

    const int dirX[] = { -1, 1, 0, 0 };

    const int dirY[] = { 0, 0, -1, 1 };

    struct Move {

    vector<Coord> path; // от -> ... -> до

    };// Проверка допустимости координат

    bool IsValid(const Board& board, int x, int y) {

    return x >= 0 && y >= 0 && x < SIZE && y < SIZE && board[x][y] != ' ';

    }// Инициализация доски

    Board InitBoard() {

    Board board(SIZE, vector<char>(SIZE, ' '));

    for (int i = CENTER_START; i <= CENTER_END; ++i)

    for (int j = CENTER_START; j <= CENTER_END; ++j)

    board[i][j] = EMPTY;

    for (int i = 0; i < 3; ++i)

    for (int j = CENTER_START; j <= CENTER_END; ++j)

    board[i][j] = PLAYER1;

    for (int i = 6; i < 9; ++i)

    for (int j = CENTER_START; j <= CENTER_END; ++j)

    board[i][j] = PLAYER2;

    for (int i = CENTER_START; i <= CENTER_END; ++i) {

    for (int j = 0; j < 3; ++j)

    board[i][j] = EMPTY;

    for (int j = 6; j < 9; ++j)

    board[i][j] = EMPTY;

    }

    return board;

    }

    void PrintBoard(const Board& board) {

    cout << " ";

    for (int j = 0; j < SIZE; ++j)

    cout << j << " ";

    cout << endl;

    for (int i = 0; i < SIZE; ++i) {

    cout << i << " | ";

    for (int j = 0; j < SIZE; ++j)

    cout << board[i][j] << " ";

    cout << endl;

    }

    }

    void PrintMove(const Move& move) {

    for (size_t i = 0; i < move.path.size(); ++i) {

    cout << "(" << move.path[i].first << "," << move.path[i].second << ")";

    if (i + 1 < move.path.size()) cout << " -> ";

    }

    cout << endl;

    }

    void JumpDFSChain(const Board& board, Coord current, vector<Coord>& path, vector<Move>& moves, vector<vector<bool>>& visited, char player) {

    bool hasJump = false;

    for (int d = 0; d < 4; ++d) {

    int mx = current.first + dirX[d];

    int my = current.second + dirY[d];

    int jx = current.first + 2 * dirX[d];

    int jy = current.second + 2 * dirY[d];

    if (IsValid(board, jx, jy) && board[jx][jy] == EMPTY && board[mx][my] != EMPTY && !visited[jx][jy]) {

    visited[jx][jy] = true;

    path.push_back({ jx, jy });

    JumpDFSChain(board, { jx, jy }, path, moves, visited, player);

    path.pop_back();

    visited[jx][jy] = false;

    hasJump = true;

    }

    }

    if (!hasJump && path.size() > 1)

    moves.push_back({ path });

    }

    vector<Move> GetAllMovesForPiece(const Board& board, int x, int y, char player) {

    vector<Move> moves;

    for (int d = 0; d < 4; ++d) {

    int nx = x + dirX[d];

    int ny = y + dirY[d];

    if (IsValid(board, nx, ny) && board[nx][ny] == EMPTY)

    moves.push_back({ {{x, y}, {nx, ny}} });

    }

    vector<Coord> path = { {x, y} };

    vector<vector<bool>> visited(SIZE, vector<bool>(SIZE, false));

    JumpDFSChain(board, { x, y }, path, moves, visited, player);

    return moves;

    }

    vector<Move> GenerateAllMoves(const Board& board, char player) {

    vector<Move> allMoves;

    for (int i = 0; i < SIZE; ++i)

    for (int j = 0; j < SIZE; ++j)

    if (board[i][j] == player) {

    auto moves = GetAllMovesForPiece(board, i, j, player);

    allMoves.insert(allMoves.end(), moves.begin(), moves.end());

    }

    return allMoves;

    }

    void ApplyMove(Board& board, const Move& move, char player) {

    Coord from = move.path.front();

    Coord to = move.path.back();

    board[from.first][from.second] = EMPTY;

    board[to.first][to.second] = player;

    }

    bool CheckWin(const Board& board, char player) {

    int count = 0;

    if (player == PLAYER1) {

    for (int i = 6; i < 9; ++i)

    for (int j = 3; j <= 5; ++j)

    if (board[i][j] == PLAYER1) count++;

    }

    else {

    for (int i = 0; i < 3; ++i)

    for (int j = 3; j <= 5; ++j)

    if (board[i][j] == PLAYER2) count++;

    }

    return count == 9;

    }

    int Evaluate(const Board& board, char player) {

    int score = 0;

    char opponent = (player == PLAYER1) ? PLAYER2 : PLAYER1;

    auto distanceToTarget = [&](int i, int j, char p) {

    return (p == PLAYER1) ? abs(8 - i) : abs(i);

    };

    for (int i = 0; i < SIZE; ++i)

    for (int j = 0; j < SIZE; ++j) {

    if (board[i][j] == player) {

    score -= distanceToTarget(i, j, player) * 10;

    if ((player == PLAYER1 && i >= 6) || (player == PLAYER2 && i <= 2))

    score += 50;

    }

    else if (board[i][j] == opponent) {

    score += distanceToTarget(i, j, opponent) * 5;

    }

    }

    return score;

    }

    int MiniMax(Board board, int depth, int alpha, int beta, bool maximizingPlayer, char currentPlayer) {

    if (CheckWin(board, PLAYER1)) return -10000 + depth;

    if (CheckWin(board, PLAYER2)) return 10000 - depth;

    if (depth == 0) return Evaluate(board, PLAYER2);

    auto moves = GenerateAllMoves(board, currentPlayer);

    if (moves.empty()) return Evaluate(board, currentPlayer);

    char nextPlayer = (currentPlayer == PLAYER1) ? PLAYER2 : PLAYER1;

    if (maximizingPlayer) {

    int maxEval = INT_MIN;

    for (auto& move : moves) {

    Board next = board;

    ApplyMove(next, move, currentPlayer);

    int eval = MiniMax(next, depth - 1, alpha, beta, false, nextPlayer);

    maxEval = max(maxEval, eval);

    alpha = max(alpha, eval);

    if (beta <= alpha) break;

    }

    return maxEval;

    }

    else {

    int minEval = INT_MAX;

    for (auto& move : moves) {

    Board next = board;

    ApplyMove(next, move, currentPlayer);

    int eval = MiniMax(next, depth - 1, alpha, beta, true, nextPlayer);

    minEval = min(minEval, eval);

    beta = min(beta, eval);

    if (beta <= alpha) break;

    }

    return minEval;

    }

    }

    Move FindBestAIMove(Board board) {

    int bestValue = INT_MIN;

    Move bestMove;

    auto moves = GenerateAllMoves(board, PLAYER2);

    for (auto& move : moves) {

    Board next = board;

    ApplyMove(next, move, PLAYER2);

    int value = MiniMax(next, 3, INT_MIN, INT_MAX, false, PLAYER1);

    if (value > bestValue) {

    bestValue = value;

    bestMove = move;

    }

    }

    return bestMove;

    }

    int main() {

    setlocale(LC_ALL, "RU");

    Board board = InitBoard();

    char currentPlayer = PLAYER1;

    while (true) {

    PrintBoard(board);

    if (CheckWin(board, PLAYER1)) {

    cout << "Игрок X победил!" << endl;

    break;

    }

    if (CheckWin(board, PLAYER2)) {

    cout << "Игрок O (AI) победил!" << endl;

    break;

    }

    auto allMoves = GenerateAllMoves(board, currentPlayer);

    if (allMoves.empty()) {

    cout << "Игрок " << currentPlayer << " не может ходить. Игра окончена.\n";

    break;

    }

    if (currentPlayer == PLAYER1) {

    cout << "Ходы игрока X:\n";

    for (size_t i = 0; i < allMoves.size(); ++i) {

    cout << i + 1 << ": ";

    PrintMove(allMoves[i]);

    }

    int moveIndex;

    cout << "Выберите ход (номер): ";

    cin >> moveIndex;

    if (moveIndex >= 1 && moveIndex <= allMoves.size()) {

    ApplyMove(board, allMoves[moveIndex - 1], PLAYER1);

    currentPlayer = PLAYER2;

    }

    else {

    cout << "Неверный номер хода, попробуйте снова.\n";

    }

    }

    else {

    cout << "Ходит AI (O)...\n";

    Move bestMove = FindBestAIMove(board);

    ApplyMove(board, bestMove, PLAYER2);

    currentPlayer = PLAYER1;

    }

    }

    return 0;

    }#include <ctime>

    #include <cstdlib>

    #include <iostream>

    #include <queue>

    #include <string>

    #include <utility>

    #include <vector>

    #include <algorithm>

    #include <limits>

    using namespace std;

    // Перечисление для представления игроков

    enum class Player { WHITE, BLACK };

    // Класс, представляющий игровую доску

    class Board {

    private:

    // Направления для шестиугольной доски (6 соседей)

    static const int direct[6][2];

    // Символы для представления фишек и пустых клеток

    static const char white;

    static const char black;

    static const char empty;

    int size = 0; // Размер доски

    string line; // Строка для отрисовки линий доски

    vector<vector<char>> board; // Матрица для хранения состояния доски

    // Вспомогательные методы

    bool isAdjacent(int x1, int y1, int x2, int y2) const; // Проверка соседства клеток

    bool isJumpMoveValid(int fromX, int fromY, int toX, int toY) const; // Проверка прыжка

    public:

    Board() : size(0) {}

    explicit Board(int size) : size(size), board(size, vector<char>(size, empty)) {

    // Инициализация строки для отрисовки доски

    line = "\\";

    for (int i = 1; i < size; i++)

    line += " / \\";

    }

    // Основные методы доски

    bool inBoard(int x, int y) const; // Проверка, что координаты в пределах доски

    bool place(int x, int y, Player p); // Размещение фишки

    bool move(int fromX, int fromY, int toX, int toY, Player p); // Перемещение фишки

    // Методы для получения информации о состоянии доски

    vector<pair<int, int>> getPlayerPieces(Player p); // Фишки игрока

    vector<pair<int, int>> getEmpty(); // Пустые клетки

    bool isGameOver(); // Проверка окончания игры

    int countPieces(Player p); // Подсчет фишек игрока

    // Методы для отображения и работы с ходами

    void printBoard(); // Печать доски

    vector<pair<int, int>> getValidMoves(Player p); // Все допустимые ходы

    vector<pair<int, int>> getValidNewPlacements(Player p); // Допустимые размещения

    vector<pair<int, int>> getValidMovesForPiece(int x, int y, Player p); // Ходы для фишки

    void flipAdjacentOpponentPieces(int x, int y, Player p); // Переворот фишек противника

    Board copy() const; // Создание копии доски

    // Проверка наличия стандартных ходов

    bool hasStandardMoves(Player p);

    };

    // Класс для реализации ИИ

    class AI {

    private:

    int maxDepth; // Максимальная глубина поиска

    public:

    explicit AI(int depth = 3) : maxDepth(depth) {}

    pair<pair<int, int>, pair<int, int>> next(Board& board, Player p); // Выбор лучшего хода

    private:

    int evaluate(Board& board, Player p); // Оценка позиции

    int minimax(Board& board, int depth, int alpha, int beta, bool maximizingPlayer, Player p); // Алгоритм минимакса

    };

    // Класс для управления игровым процессом

    class Game {

    private:

    Player computer = Player::WHITE; // ИИ

    Player player = Player::BLACK; // Игрок

    AI robotAI; // Экземпляр ИИ

    Board board; // Игровая доска

    public:

    Game() : robotAI(3) {}

    void play(); // Основной игровой цикл

    private:

    void setup(); // Настройка игры

    void choose(); // Выбор стороны

    bool computerTurn(); // Ход компьютера

    bool playerTurn(); // Ход игрока

    bool processPlayerMove(int x, int y, Player p); // Обработка размещения фишки

    bool processPlayerMove(int fromX, int fromY, int toX, int toY, Player p); // Обработка перемещения

    };

    // Инициализация статических членов класса Board

    const int Board::direct[6][2] = {

    {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0} // 6 направлений для шестиугольной доски

    };

    const char Board::white = 'W';

    const char Board::black = 'B';

    const char Board::empty = '+';

    // --------------------------Методы доски---------------------------------

    // Проверка, что координаты находятся в пределах доски

    bool Board::inBoard(int x, int y) const {

    return (x >= 0 && y >= 0 && x < size && y < size);

    }

    // Проверка, являются ли две клетки соседями

    bool Board::isAdjacent(int x1, int y1, int x2, int y2) const {

    for (int i = 0; i < 6; i++) {

    if (x2 == x1 + direct[i][0] && y2 == y1 + direct[i][1]) {

    return true;

    }

    }

    return false;

    }

    // Проверка допустимости прыжка (перемещения через одну клетку)

    bool Board::isJumpMoveValid(int fromX, int fromY, int toX, int toY) const {

    for (int i = 0; i < 6; i++) {

    int adjX = fromX + direct[i][0];

    int adjY = fromY + direct[i][1];

    if (inBoard(adjX, adjY) && isAdjacent(adjX, adjY, toX, toY)) {

    return true;

    }

    }

    return false;

    }

    // Размещение фишки на доске

    bool Board::place(int x, int y, Player p) {

    if (!inBoard(x, y)) return false;

    if (board[x][y] != empty) return false;

    board[x][y] = (p == Player::BLACK) ? black : white;

    flipAdjacentOpponentPieces(x, y, p); // Переворачиваем соседние фишки противника

    return true;

    }

    // Перемещение фишки

    bool Board::move(int fromX, int fromY, int toX, int toY, Player p) {

    if (!inBoard(fromX, fromY) || !inBoard(toX, toY)) return false;

    if (board[toX][toY] != empty) return false;

    char currentPiece = (p == Player::BLACK) ? black : white;

    if (board[fromX][fromY] != currentPiece) return false;

    if (isAdjacent(fromX, fromY, toX, toY)) {

    // Обычное перемещение на соседнюю клетку

    board[fromX][fromY] = empty;

    board[toX][toY] = currentPiece;

    flipAdjacentOpponentPieces(toX, toY, p);

    return true;

    }

    else if (isJumpMoveValid(fromX, fromY, toX, toY)) {

    // Прыжок через клетку

    board[fromX][fromY] = empty;

    board[toX][toY] = currentPiece;

    flipAdjacentOpponentPieces(toX, toY, p);

    return true;

    }

    return false;

    }

    // Переворот соседних фишек противника после хода

    void Board::flipAdjacentOpponentPieces(int x, int y, Player p) {

    char opponent = (p == Player::BLACK) ? white : black;

    char current = (p == Player::BLACK) ? black : white;

    for (int i = 0; i < 6; i++) {

    int nx = x + direct[i][0];

    int ny = y + direct[i][1];

    if (inBoard(nx, ny) && board[nx][ny] == opponent) {

    board[nx][ny] = current;

    }

    }

    }

    // Получение всех фишек игрока

    vector<pair<int, int>> Board::getPlayerPieces(Player p) {

    vector<pair<int, int>> pieces;

    char piece = (p == Player::BLACK) ? black : white;

    for (int i = 0; i < size; i++) {

    for (int j = 0; j < size; j++) {

    if (board[i][j] == piece) {

    pieces.emplace_back(i, j);

    }

    }

    }

    return pieces;

    }

    // Получение всех пустых клеток

    vector<pair<int, int>> Board::getEmpty() {

    vector<pair<int, int>> emptySpots;

    for (int i = 0; i < size; i++) {

    for (int j = 0; j < size; j++) {

    if (board[i][j] == empty) {

    emptySpots.emplace_back(i, j);

    }

    }

    }

    return emptySpots;

    }

    // Получение допустимых позиций для размещения новых фишек

    vector<pair<int, int>> Board::getValidNewPlacements(Player p) {

    vector<pair<int, int>> validMoves;

    vector<pair<int, int>> playerPieces = getPlayerPieces(p);

    vector<pair<int, int>> emptyCells = getEmpty();

    // Если у игрока нет фишек, можно разместить в любую пустую клетку

    if (playerPieces.empty()) {

    return emptyCells;

    }

    // Иначе можно размещать только рядом со своими фишками

    for (auto& emptyCell : emptyCells) {

    for (auto& piece : playerPieces) {

    if (isAdjacent(piece.first, piece.second, emptyCell.first, emptyCell.second)) {

    validMoves.push_back(emptyCell);

    break;

    }

    }

    }

    return validMoves;

    }

    // Получение допустимых ходов для конкретной фишки

    vector<pair<int, int>> Board::getValidMovesForPiece(int x, int y, Player p) {

    vector<pair<int, int>> validMoves;

    if (!inBoard(x, y)) return validMoves;

    char piece = (p == Player::BLACK) ? black : white;

    if (board[x][y] != piece) return validMoves;

    // Проверка обычных перемещений на соседние клетки

    for (int i = 0; i < 6; i++) {

    int nx = x + direct[i][0];

    int ny = y + direct[i][1];

    if (inBoard(nx, ny) && board[nx][ny] == empty) {

    validMoves.emplace_back(nx, ny);

    }

    }

    // Проверка прыжков через клетку

    for (int i = 0; i < 6; i++) {

    int adjX = x + direct[i][0];

    int adjY = y + direct[i][1];

    if (inBoard(adjX, adjY)) {

    for (int j = 0; j < 6; j++) {

    int nx = adjX + direct[j][0];

    int ny = adjY + direct[j][1];

    if (inBoard(nx, ny) && board[nx][ny] == empty &&

    !isAdjacent(x, y, nx, ny) &&

    find(validMoves.begin(), validMoves.end(), make_pair(nx, ny)) == validMoves.end()) {

    validMoves.emplace_back(nx, ny);

    }

    }

    }

    }

    return validMoves;

    }

    // Проверка наличия стандартных ходов (размещение или перемещение)

    bool Board::hasStandardMoves(Player p) {

    vector<pair<int, int>> playerPieces = getPlayerPieces(p);

    vector<pair<int, int>> newPlacements = getValidNewPlacements(p);

    if (!newPlacements.empty()) return true;

    for (auto& piece : playerPieces) {

    if (!getValidMovesForPiece(piece.first, piece.second, p).empty()) {

    return true;

    }

    }

    return false;

    }

    // Получение всех допустимых ходов для игрока

    vector<pair<int, int>> Board::getValidMoves(Player p) {

    vector<pair<int, int>> validMoves;

    vector<pair<int, int>> playerPieces = getPlayerPieces(p);

    // Если есть стандартные ходы - возвращаем их

    if (hasStandardMoves(p)) {

    vector<pair<int, int>> newPlacements = getValidNewPlacements(p);

    validMoves.insert(validMoves.end(), newPlacements.begin(), newPlacements.end());

    for (auto& piece : playerPieces) {

    vector<pair<int, int>> pieceMoves = getValidMovesForPiece(piece.first, piece.second, p);

    validMoves.insert(validMoves.end(), pieceMoves.begin(), pieceMoves.end());

    }

    }

    else if (!playerPieces.empty()) { // Если нет стандартных ходов, но есть фишки

    // Разрешаем ход в любую свободную клетку (правило форсированного пропуска)

    validMoves = getEmpty();

    }

    return validMoves;

    }

    // Проверка окончания игры (доска заполнена)

    bool Board::isGameOver() {

    return getEmpty().empty();

    }

    // Подсчет фишек игрока

    int Board::countPieces(Player p) {

    char piece = (p == Player::BLACK) ? black : white;

    int count = 0;

    for (int i = 0; i < size; i++) {

    for (int j = 0; j < size; j++) {

    if (board[i][j] == piece) {

    count++;

    }

    }

    }

    return count;

    }

    // Создание копии доски

    Board Board::copy() const {

    Board newBoard(size);

    newBoard.board = board;

    return newBoard;

    }

    // Печать доски в консоль

    void Board::printBoard() {

    if (size <= 0)

    return;

    // Печать заголовка с номерами столбцов

    cout << " 0";

    for (int i = 1; i < size; i++)

    cout << " " << i;

    cout << endl;

    // Печать первой строки доски

    cout << "0 " << board[0][0];

    for (int i = 1; i < size; i++)

    cout << "---" << board[0][i];

    cout << endl;

    string indent = "";

    // Печать остальных строк доски с соединительными линиями

    for (int i = 1; i < size; i++) {

    indent += ' ';

    cout << indent << " " << line << endl;

    if (i < 10) {

    indent += ' ';

    cout << indent << i << ' ' << board[i][0];

    }

    else {

    cout << indent << i << ' ' << board[i][0];

    indent += ' ';

    }

    for (int j = 1; j < size; j++)

    cout << "---" << board[i][j];

    cout << endl;

    }

    cout << "_________________________________________________________" << endl;

    }

    // --------------------------Методы ИИ---------------------------------

    // Оценка текущей позиции для игрока p

    int AI::evaluate(Board& board, Player p) {

    // Если игра окончена, считаем разницу фишек

    if (board.isGameOver()) {

    int playerCount = board.countPieces(p);

    int opponentCount = board.countPieces(p == Player::BLACK ? Player::WHITE : Player::BLACK);

    return playerCount - opponentCount;

    }

    // Базовая оценка - разница фишек

    int score = board.countPieces(p) - board.countPieces(p == Player::BLACK ? Player::WHITE : Player::BLACK);

    // Учитываем мобильность (количество возможных ходов)

    int playerMobility = static_cast<int>(board.getValidMoves(p).size());

    int opponentMobility = static_cast<int>(board.getValidMoves(p == Player::BLACK ? Player::WHITE : Player::BLACK).size());

    score += (playerMobility - opponentMobility) / 2;

    return score;

    }

    // Реализация алгоритма минимакса с альфа-бета отсечением

    int AI::minimax(Board& board, int depth, int alpha, int beta, bool maximizingPlayer, Player p) {

    // Если достигнута максимальная глубина или игра окончена, возвращаем оценку

    if (depth == 0 || board.isGameOver()) {

    return evaluate(board, p);

    }

    if (maximizingPlayer) {

    int maxEval = numeric_limits<int>::min();

    vector<pair<int, int>> validMoves = board.getValidMoves(p);

    // Перебираем все возможные ходы

    for (auto& move : validMoves) {

    Board newBoard = board.copy();

    if (newBoard.place(move.first, move.second, p)) {

    int eval = minimax(newBoard, depth - 1, alpha, beta, false, p);

    maxEval = max(maxEval, eval);

    alpha = max(alpha, eval);

    if (beta <= alpha) // Альфа-бета отсечение

    break;

    }

    }

    return maxEval;

    }

    else {

    int minEval = numeric_limits<int>::max();

    Player opponent = (p == Player::BLACK) ? Player::WHITE : Player::BLACK;

    vector<pair<int, int>> validMoves = board.getValidMoves(opponent);

    // Перебираем все возможные ходы противника

    for (auto& move : validMoves) {

    Board newBoard = board.copy();

    if (newBoard.place(move.first, move.second, opponent)) {

    int eval = minimax(newBoard, depth - 1, alpha, beta, true, p);

    minEval = min(minEval, eval);

    beta = min(beta, eval);

    if (beta <= alpha) // Альфа-бета отсечение

    break;

    }

    }

    return minEval;

    }

    }

    // Выбор лучшего хода для ИИ

    pair<pair<int, int>, pair<int, int>> AI::next(Board& board, Player p) {

    vector<pair<int, int>> validMoves = board.getValidMoves(p);

    pair<pair<int, int>, pair<int, int>> bestMove;

    int bestValue = numeric_limits<int>::min();

    // Перебираем все возможные ходы и выбираем лучший по оценке

    for (auto& move : validMoves) {

    Board newBoard = board.copy();

    if (newBoard.place(move.first, move.second, p)) {

    int moveValue = minimax(newBoard, maxDepth - 1, numeric_limits<int>::min(),

    numeric_limits<int>::max(), false, p);

    if (moveValue > bestValue) {

    bestValue = moveValue;

    bestMove = make_pair(make_pair(-1, -1), move); // -1 означает размещение новой фишки

    }

    }

    }

    return bestMove;

    }

    // --------------------------Методы игры---------------------------------

    // Основной игровой цикл

    void Game::play() {

    cout << "Добро пожаловать в игру Гексагон!" << endl;

    cout << "Правила:" << endl;

    cout << "1. Размещайте новые фишки рядом со своими существующими" << endl;

    cout << "2. Перемещайте фишки на соседние клетки или прыгайте через одну" << endl;

    cout << "3. Если нет возможных ходов - можно поставить фишку в любую свободную клетку" << endl;

    cout << "4. Фишки противника рядом с вашим ходом переворачиваются" << endl;

    cout << "5. Игра заканчивается при заполнении доски - побеждает тот, у кого больше фишек!" << endl << endl;

    while (true) {

    setup(); // Настройка игры

    choose(); // Выбор стороны

    bool gameOver = false;

    // Определение, кто ходит первым

    int turn = (computer == Player::BLACK ? 0 : 1);

    while (!gameOver) {

    if (board.isGameOver()) {

    gameOver = true;

    break;

    }

    turn = !turn; // Смена хода

    if (turn) {

    gameOver = computerTurn(); // Ход компьютера

    }

    else {

    gameOver = playerTurn(); // Ход игрока

    }

    }

    // Подсчет результатов и вывод победителя

    int playerCount = board.countPieces(player);

    int computerCount = board.countPieces(computer);

    cout << "Игра окончена!" << endl;

    cout << "Ваши фишки: " << playerCount << endl;

    cout << "Фишки компьютера: " << computerCount << endl;

    if (playerCount > computerCount) {

    cout << "Вы победили!" << endl;

    }

    else if (computerCount > playerCount) {

    cout << "Компьютер победил!" << endl;

    }

    else {

    cout << "Ничья!" << endl;

    }

    // Предложение сыграть еще раз

    char playAgain;

    cout << "Сыграем еще? (y/n): ";

    cin >> playAgain;

    if (playAgain != 'y' && playAgain != 'Y') {

    break;

    }

    }

    cout << "Спасибо за игру!" << endl;

    }

    // Настройка параметров игры (размер доски)

    void Game::setup() {

    int dimensions;

    cout << "Введите размер доски (рекомендуется 5-11): ";

    cin >> dimensions;

    if (dimensions < 3 || dimensions > 20) {

    cout << "Недопустимый размер. Используется размер по умолчанию 9." << endl;

    dimensions = 9;

    }

    board = Board(dimensions);

    cout << "Создана доска размером " << dimensions << "x" << dimensions << endl;

    board.printBoard();

    }

    // Выбор стороны (черные или белые)

    void Game::choose() {

    char side;

    cout << "Выберите сторону (B для Черных, W для Белых): ";

    cin >> side;

    if (side == 'B' || side == 'b') {

    player = Player::BLACK;

    computer = Player::WHITE;

    cout << "Вы играете за Черных (B), компьютер за Белых (W)" << endl;

    cout << "Вы ходите первым!" << endl;

    }

    else {

    player = Player::WHITE;

    computer = Player::BLACK;

    cout << "Вы играете за Белых (W), компьютер за Черных (B)" << endl;

    cout << "Компьютер ходит первым!" << endl;

    }

    }

    // Ход компьютера

    bool Game::computerTurn() {

    cout << "Ход компьютера..." << endl;

    auto move = robotAI.next(board, computer);

    if (move.first.first == -1) {

    cout << "Компьютер размещает фишку в (" << move.second.first << ", " << move.second.second << ")" << endl;

    board.place(move.second.first, move.second.second, computer);

    }

    else {

    cout << "Компьютер перемещает фишку из (" << move.first.first << ", " << move.first.second

    << ") в (" << move.second.first << ", " << move.second.second << ")" << endl;

    board.move(move.first.first, move.first.second, move.second.first, move.second.second, computer);

    }

    board.printBoard();

    return board.isGameOver();

    }

    // Обработка хода игрока (размещение фишки)

    bool Game::processPlayerMove(int x, int y, Player p) {

    while (true) {

    if (board.place(x, y, p)) {

    board.printBoard();

    return board.isGameOver();

    }

    cout << "Недопустимый ход! Попробуйте еще раз." << endl;

    cout << "Введите координаты для размещения фишки (строка столбец): ";

    cin >> x >> y;

    }

    }

    // Обработка хода игрока (перемещение фишки)

    bool Game::processPlayerMove(int fromX, int fromY, int toX, int toY, Player p) {

    while (true) {

    if (board.move(fromX, fromY, toX, toY, p)) {

    board.printBoard();

    return board.isGameOver();

    }

    cout << "Недопустимый ход! Попробуйте еще раз." << endl;

    cout << "Введите координаты фишки для перемещения (строка столбец): ";

    cin >> fromX >> fromY;

    cout << "Введите координаты для перемещения (строка столбец): ";

    cin >> toX >> toY;

    }

    }

    // Ход игрока

    bool Game::playerTurn() {

    cout << "Ваш ход!" << endl;

    board.printBoard();

    vector<pair<int, int>> playerPieces = board.getPlayerPieces(player);

    bool hasStandardMoves = board.hasStandardMoves(player);

    while (true) {

    if (hasStandardMoves) {

    cout << "Ваши варианты:" << endl;

    cout << "1. Разместить новую фишку рядом с существующими" << endl;

    if (!playerPieces.empty()) {

    cout << "2. Переместить одну из ваших фишек" << endl;

    }

    int choice;

    cout << "Введите выбор (1";

    if (!playerPieces.empty()) cout << " или 2";

    cout << "): ";

    cin >> choice;

    if (choice == 1) {

    cout << "Введите координаты для размещения фишки (строка столбец): ";

    int x, y;

    cin >> x >> y;

    return processPlayerMove(x, y, player);

    }

    else if (choice == 2 && !playerPieces.empty()) {

    cout << "Введите координаты фишки для перемещения (строка столбец): ";

    int fromX, fromY;

    cin >> fromX >> fromY;

    cout << "Введите координаты для перемещения (строка столбец): ";

    int toX, toY;

    cin >> toX >> toY;

    return processPlayerMove(fromX, fromY, toX, toY, player);

    }

    else {

    cout << "Недопустимый выбор!" << endl;

    }

    }

    else {

    cout << "У вас нет стандартных ходов. Можно поставить фишку в любую свободную клетку." << endl;

    cout << "Введите координаты для размещения фишки (строка столбец): ";

    int x, y;

    cin >> x >> y;

    return processPlayerMove(x, y, player);

    }

    }

    }

    int main() {

    setlocale(LC_ALL, "Russian"); // Установка русской локали

    Game hexGame;

    srand(static_cast<unsigned int>(time(nullptr))); // Инициализация генератора случайных чисел

    hexGame.play(); // Запуск игры

    return 0;

    }