Алгоритм Minimax для игры Specker

Я создаю минимаксный плеер для игры Specker на C++.

Правила просты:

There are p players (0 to p - 1) and n heaps (0 to n - 1)

Starting with player 0 each player takes k > 0 coins from a heap x and places m coins (0 <= m < k) on heap y

The winning player is the one which plays last when all coins from all heaps are removed

Итак, я создал игру и некоторые классы игроков (GreedyPlayer, SpartanPlayer и т. д.), Но все они немного предсказуемы в том, что они будут делать. Это не умный.

поэтому я создаю игрока, который играет в соответствии с минимаксным (pt18a038) кодом, компилируется нормально, но программа перестает отвечать на выполнение. класс умных игроков:

class pt18a038 : public Player {
private:
    string player_type;

public:
    pt18a038(const string &n) : Player(n) {
        player_type = "Asder aka theRunner";
    }

    virtual const string &getType() const override {
        return player_type;
    }

    virtual Move play(const State &s) override {
        int source_heap = 0;
        int target_heap = 0;
        int source_coins = 0;
        int target_coins = 0;
        int sum = 0;

        for (source_heap = 0; source_heap < s.getHeaps(); source_heap++) {
            for (source_coins = 1; source_coins <= s.getCoins(source_heap); source_coins++) {
                for (target_heap = 0; target_heap < s.getHeaps(); target_heap++) {
                    for (target_coins = 0; target_coins <= source_coins; target_coins++) {
                        Move m(source_heap, source_coins, target_heap, target_coins);
                        sum = minimax(s, 3, 0, m);
                        cout << "Play:" << source_heap << "," << source_coins << "," << target_heap << ","
                             << target_coins << ":" << sum << endl;
                    }
                }
            }
        }
        cout << sum << endl;

        // ///////////// for debbuging only until minimax is working...
        source_heap = 0;
        source_coins = 0;
        for (int i = 0; i < s.getHeaps(); i++) {
            if (s.getCoins(i) > source_coins) {
                source_heap = i;
                source_coins = s.getCoins(i);
            }
        }
        Move SpartanObject(source_heap, 1, 0, 0);
        return SpartanObject;
        // /////////////

    }

    static int minimax(State s, const int &players, int depth, const Move move) {


        if (s.winning()) {
            cout << "game end!" << endl;
            return 1000;
            if (depth % players == 0) return 1000; //Maximazing player
            else return -1000; //Minimazing player
        }
        if (depth > 4) {
            //cout<<"optimazing"<<endl;
            return 0;
        }
        //cout << s << endl;
        s.next(move);


        int source_heap = 0;
        int target_heap = 0;
        int source_coins = 0;
        int target_coins = 0;
        int max = -100000;
        int min = 100000;
        int result;

        for (source_heap = 0; source_heap < s.getHeaps(); source_heap++) {
            for (source_coins = 1; source_coins <= s.getCoins(source_heap); source_coins++) {
                for (target_heap = 0; target_heap < s.getHeaps(); target_heap++) {
                    for (target_coins = 0; target_coins <= source_coins; target_coins++) {
                        //cout << "Move:" << source_heap << "," << source_coins << "," << target_heap << ","<< target_coins << endl;
                        Move m(source_heap, source_coins, target_heap, target_coins);
                        result = minimax(s, players, depth + 1, m);
                        if (depth % players == 0) {
                            max = result ? (result > max) : result;
                        } else {
                            min = result ? (result < min) : result;
                        }

                    }
                }
            }
        }

        return max ? (depth % players == 0) : min;

    }

};

Вот мой код для остальной части игры (он протестирован и отлично работает)

#include <iostream>
#include <stdexcept>

using namespace std;

class Move {
private:
    int source_heap, source_coins, target_heap, target_coins;

public:
    Move(int sh, int sc, int th, int tc) {
        source_heap = sh;
        source_coins = sc;
        target_heap = th;
        target_coins = tc;
    }

    int getSource() const {
        return source_heap;
    }
    int getSourceCoins() const {
        return source_coins;
    }
    int getTarget() const {
        return target_heap;
    }
    int getTargetCoins() const {
        return target_coins;
    }

    // Let's do some operator overloading
    friend ostream &operator<<(ostream &out, const Move &move) {
        if (move.getTargetCoins()) {
            out << "takes " << move.getSourceCoins() << " coins from heap "
                << move.getSource() << " and puts " << move.getTargetCoins()
                << " coins to heap " << move.getTarget();

        } else {
            out << "takes " << move.getSourceCoins() << " coins from heap "
                << move.getSource() << " and puts nothing";
        }
    }
};

class State {
    // State with h heaps, where the i-th heap starts with c[i] coins.
private:
    int heaps, *heap_coins;

public:
    State(int h, const int c[]) {
        heaps = h;
        heap_coins = new int[heaps];
        for (int i = 0; i < heaps; i++)
            heap_coins[i] = c[i];
    }

    ~State() {
        delete[] heap_coins;
        return;
    }

    int getCoins(int h) const throw(logic_error) {
        if (h < 0 || h > heaps) {
            throw logic_error(
                "Invalid heap number, enter a number between 1 and heaps!");
            return 1;
        } else {
            return heap_coins[h];
        }
    }
    void next(const Move &move) throw(logic_error) {
        if ((move.getSource() < 0) || (move.getSource() > heaps) ||
            (move.getTarget() < 0) || (move.getTarget() > heaps)) {
            throw logic_error("Invalid Heap!");
            return;
        } else if (
            (move.getSourceCoins() < 1) || (move.getTargetCoins() < 0) ||
            (move.getSourceCoins() <= move.getTargetCoins()) ||
            (move.getSourceCoins() > getCoins(move.getSource()))) {
            throw logic_error("Invalid Coin number!");
        } else {
            heap_coins[move.getSource()] -= move.getSourceCoins();
            heap_coins[move.getTarget()] += move.getTargetCoins();
        }
    }

    bool winning() const {
        int s = 0;
        for (int i = 0; i < heaps; i++)
            s += getCoins(i);
        return not s; // yeah i know how booleans work :P
    }

    int getHeaps() const {
        return heaps;
    }

    friend ostream &operator<<(ostream &out, const State &state) {
        for (int i = 0; i < state.getHeaps(); i++) {
            out << state.heap_coins[i];
            if (i != state.getHeaps() - 1)
                out << ", ";
        }
        return out;
    }
};

class Player {
public:
    Player(const string &n);
    virtual ~Player();

    virtual const string &getType() const = 0;
    virtual Move play(const State &s) = 0;

    friend ostream &operator<<(ostream &out, const Player &player);

protected:
    string player_name;
};

class GreedyPlayer : public Player {
private:
    string player_type;

public:
    GreedyPlayer(const string &n) : Player(n) {
        player_type = "Greedy";
    }
    virtual const string &getType() const override {
        return player_type;
    }
    virtual Move play(const State &s) override {
        int source_heap = 0;
        int source_coins = 0;
        for (int i = 0; i < s.getHeaps(); i++) {
            if (s.getCoins(i) > source_coins) {
                source_heap = i;
                source_coins = s.getCoins(i);
            }
        }
        Move GreedyObject(source_heap, source_coins, 0, 0);
        return GreedyObject;
    }
};

class SpartanPlayer : public Player {
public:
    SpartanPlayer(const string &n) : Player(n) {
        player_type = "Spartan";
    }
    virtual const string &getType() const override {
        return player_type;
    }

    virtual Move play(const State &s) override {
        int source_heap = 0;
        int source_coins = 0;
        for (int i = 0; i < s.getHeaps(); i++) {
            if (s.getCoins(i) > source_coins) {
                source_heap = i;
                source_coins = s.getCoins(i);
            }
        }
        Move SpartanObject(source_heap, 1, 0, 0);
        return SpartanObject;
    }

private:
    string player_type;
};

class SneakyPlayer : public Player {
public:
    SneakyPlayer(const string &n) : Player(n) {
        player_type = "Sneaky";
    }
    virtual const string &getType() const override {
        return player_type;
    }

    virtual Move play(const State &s) override {
        int j = 0;
        while (s.getCoins(j) == 0) {
            j++;
        }
        int source_heap = j;
        int source_coins = s.getCoins(j);
        for (int i = j + 1; i < s.getHeaps(); i++) {
            if ((s.getCoins(i) < source_coins) && (s.getCoins(i) > 0)) {
                source_heap = i;
                source_coins = s.getCoins(i);
            }
        }
        Move SneakyObject(source_heap, source_coins, 0, 0);
        return SneakyObject;
    }

private:
    string player_type;
};

class RighteousPlayer : public Player {
public:
    RighteousPlayer(const string &n) : Player(n) {
        player_type = "Righteous";
    }
    virtual const string &getType() const override {
        return player_type;
    }

    virtual Move play(const State &s) override {
        int target_heap = 0;
        int source_heap = 0;
        int source_coins = s.getCoins(0);
        int target_coins = source_coins;

        for (int i = 1; i < s.getHeaps(); i++) {
            if (s.getCoins(i) > source_coins) {
                source_heap = i;
                source_coins = s.getCoins(i);
            } else if (s.getCoins(i) < target_coins) {
                target_heap = i;
                target_coins = s.getCoins(i);
            }
        }
        source_coins -= source_coins / 2;
        Move RighteousObject(
            source_heap, source_coins, target_heap, source_coins - 1);
        return RighteousObject;
    }

private:
    string player_type;
};

Player::Player(const string &n) {
    player_name = n;
}

Player::~Player() {
    player_name.clear();
}

ostream &operator<<(ostream &out, const Player &player) {
    out << player.getType() << " player " << player.player_name;
    return out;
}

class Game {
private:
    int game_heaps, game_players, current_heap, current_player;
    int *heap_coins;
    Player **players_list;

public:
    Game(int heaps, int players) {
        heap_coins= new int [heaps];
        game_heaps = heaps;
        game_players = players;
        current_heap = 0;
        current_player = 0;
        players_list = new Player*[players];
    }
    ~Game() {
        delete[] heap_coins;
        delete[] players_list;
    }
    void addHeap(int coins) throw(logic_error) {
        if (current_heap > game_heaps)
            throw logic_error("All heaps are full with coins!");
        else if (coins < 0)
            throw logic_error("Coins must be a positive number!"); 
        else {
                heap_coins[current_heap++] = coins;
            }
    }
    void addPlayer(Player *player) throw(logic_error) {
        if (current_player > game_players)
            throw logic_error("All players are added!");
        else {
            players_list[current_player++] = player;
        }
    }
    void play(ostream &out) throw(logic_error) {
        if ((current_player != game_players) && (current_heap != game_heaps)) {
            throw logic_error("Have you added all heaps and players?");
        } else {
            int i = 0;
            State currentState(game_heaps, heap_coins);
            while (!currentState.winning()) {
                out << "State: " << currentState << endl;
                out << *players_list[i % game_players] << " "
                    << players_list[i % game_players]->play(currentState) << endl;
                currentState.next(
                    players_list[i % game_players]->play(currentState));

                i++;
            }
            out << "State: " << currentState << endl;
            i--;
            out << *players_list[i % game_players] << " wins" << endl;
        }
    }
};


int main() {
Game specker(6, 5);
    specker.addHeap(10);
    specker.addHeap(20);
    specker.addHeap(17);
    specker.addHeap(17);
    specker.addHeap(17);
    specker.addHeap(17);

    specker.addPlayer(new GreedyPlayer("Alan"));
    specker.addPlayer(new SneakyPlayer("Tom"));
    specker.addPlayer(new SpartanPlayer("Mary"));
    specker.addPlayer(new RighteousPlayer("Robin"));

    specker.addPlayer(new pt18a038("Stavros"));
    specker.play(cout);

}

Обновлено

А также? Где висит?

Beta 18.03.2018 02:10

Не знаю, но точно в минимаксном исполнении

Stavros Avramidis 18.03.2018 02:14

Если по какой-то причине вы не хотите использовать отладчик, вы можете добавить несколько диагностических операторов cout.

Beta 18.03.2018 02:19

@Beta При отладке я вижу, что Minimax запускает пару рекурсий, поэтому, если мне нужно угадать, это будет первый оператор if (??)

Stavros Avramidis 18.03.2018 02:19

@Beta Я добавил cout<<s<<endl; поверх минимакса и шо, что каждый запуск он останавливается на другом State

Stavros Avramidis 18.03.2018 02:34

Если элемент управления передает этот первый оператор if (т.е. если s не является winning), тогда нет заявления о возврате. Это может быть неопределенное поведение, мне придется его поискать, но в любом случае эта функция не может делать то, что вы намеревались.

Beta 18.03.2018 02:41

@Beta Это правда, но все равно давит

Stavros Avramidis 18.03.2018 02:43

также удаление совершенно не нужно

Stavros Avramidis 18.03.2018 02:47

Я думаю, что добился некоторого прогресса, я следовал рекомендациям @seccpur о глубине, но программы возвращают logic error invalid coin number!

Stavros Avramidis 18.03.2018 11:36

После возврата первого pt18a038::minmax любой доступ к s.heap_coins будет UB. Оформить заказ на правило трех / пяти / нуля.

BeyelerStudios 18.03.2018 12:00

@BeyelerStudios так вы предлагаете сделать копию оперетера ?? Я не уверен, что это так, потому что он запускается после первого минимакса (за исключением, вы не имеете в виду дочерний минимакс, но второй минимакс вызывается из Play ??)

Stavros Avramidis 18.03.2018 13:03

Тогда воспользуйтесь отладчиком, и вы будете уверены.

BeyelerStudios 18.03.2018 13:19

Я использую остановки программы CLion на Move (0,1,0,0) и State (1,0,0,0)

Stavros Avramidis 18.03.2018 13:25

@BeyelerStudios Я думаю, вы были правы насчет конструктора копирования

Stavros Avramidis 18.03.2018 23:34

Я обновил код

Stavros Avramidis 19.03.2018 10:30
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
15
615
1

Ответы 1

Я вижу несколько проблем в вашей программе Minimax, некоторые из них серьезны по своему характеру:

1) Используйте обрезку Alpha Beta, чтобы уменьшить размер дерева поиска.

2) В минимаксном рекурсивном вызове нет надлежащего граничного условия (если глубина> 5 или что-то в этом роде) (подробности см. В моем фрагменте кода), ЦП может зависнуть во время вызова.

3) Оценка вашего листового узла слабая, поэтому оцениваемые ходы, несмотря на использование алгоритма минимакса, вряд ли будут разумными.

4) Для увеличения скорости поиска вы можете использовать многопоточность только на ветке верхнего уровня.

5) Если листовой узел дает выигрышный ход при максимизации, вы можете пропустить дальнейшую оценку, вернув высокий балл.

6) После того, как ваш код заработает, обратитесь к нему при проверке кода с тегом «ai», а не на SO, для более подробного анализа.

int EvaluateLeafNode(State s, const int &players)
{
  //TODO analyze here
  return score;
}

int minimax(State s, const int &players, int depth , const Move move, int alpha, int beta)
{
   if ( depth >= 5) return EvaluateLeafNode(s,players);    // this was missing in your code

   //don't analyze here 

   //rest of minimax recursive code
}

Мне не досталась деталь EvaluateLeafNode .. что она делает?

Stavros Avramidis 18.03.2018 03:51

@StavrosAvramidis: он должен анализировать каждый листовой узел на основе позиционной силы и количества штук. В этом и проявляется главный интеллект. Слишком многословие, и программа может длиться вечно.

seccpur 18.03.2018 03:57

Я не понимаю разделения между глубиной <5 и глубиной> 5 .. Я понимаю ваше предложение о временных затратах), но я не понимаю разницы в реализации

Stavros Avramidis 18.03.2018 04:04

@StavrosAvramidis: ваш минимаксный рекурсивный вызов начинается с глубины 0 и постепенно увеличивается с глубиной (подсчитайте количество листовых узлов на глубине 5-6, оно должно быть более 100000 в зависимости от сложности игры, поэтому, возможно, пришло время вернуться вернуться к вызывающей программе, когда глубина достигнет 5-6 (однако вы можете увеличить значение глубины дальше, только если ваша игра позволяет).

seccpur 18.03.2018 04:12

Другие вопросы по теме