Футуристический Gun Duel


73

Фоновая будущее

В 2017 году вы и ваш противник встретитесь в футуристическом бою, в котором выживет только один. Действительно ли вы достаточно опытный , чтобы победить вашего противника? Сейчас самое время отточить свои навыки владения оружием на вашем любимом языке программирования и бороться со всеми трудностями!

Результаты турнира

Этот турнир завершился в UTC утром Feburary 2 - й , 2017. Благодаря нашим конкурсанток, мы имели захватывающий футуристический турнир!

MontePlayer - окончательный победитель после близких сражений с CBetaPlayer и StudiousPlayer. Три лучших дуэлянта Гена сделали памятную фотографию:

                MontePlayer                         - by TheNumberOne
              +------------+
  CBetaPlayer |            |                        - by George V. Williams
 +------------+    #  1    | StudiousPlayer         - by H Walters
 |                         +----------------+
 |    #  2                        #  3      |       
 +------------------------------------------+
    The Futurustic Gun Duel @ PPCG.SE 2017

Поздравляем победителей! Подробная таблица лидеров видна в конце этого поста.

Общее руководство

  • Посетите официальный репозиторий для исходного кода, используемого в этом турнире.
  • Записи C ++: пожалуйста, наследуйте Playerкласс.
  • Записи не на C ++: выберите один интерфейс в разделе Интерфейс для заявок, не относящихся к C ++ .
  • В настоящее время разрешены не языки C ++: Python 3, Java.

Дуэль

  • Каждый игрок начинает с незаряженного оружия, которое может загрузить бесконечное количество патронов.
  • Каждый ход игроки одновременно выбирают одно из следующих действий:
    • 0 - Загрузите 1 боеприпас в пистолет.
    • 1- выстрелить в противника; стоит 1 заряженный патрон.
    • 2- выстрелить плазменным лучом в противника; стоит 2 заряженных патрона.
    • - - Защищать входящую пулю с помощью металлического щита.
    • = - Защитить входящий плазменный луч с помощью теплового дефлектора.
  • Если оба игрока выживают после 100- го хода, они истощаются до смерти, что приводит к ничьей .

Игрок проигрывает дуэль с оружием, если он

  • Возможно , НЕ использовать металлический экран для защиты входящей пули.
  • Возможно , НЕ используйте тепловой дефлектор , чтобы защитить входящую плазму.
  • Стреляйте из пистолета, не загружая достаточно патронов, в результате чего их пистолет сам взорвется и убьет владельца.

Предостережения

Согласно Руководству для владельцев футуристических ружей :

  • Металлический щит НЕ МОЖЕТ защищать от поступающего плазменного луча. Аналогично, тепловой дефлектор НЕ МОЖЕТ защищать от входящей пули.
  • Плазменный луч подавляет пулю (потому что первый требует больше заряженных боеприпасов). Поэтому, если игрок стреляет плазменным лучом по противнику, который стреляет пулей в тот же ход, противник убивается.
  • Если оба игрока одновременно выпускают пули друг в друга, пули отменяются, и оба игрока выживают. Аналогично, если оба игрока стреляют друг в друга плазменным лучом в один ход, оба игрока выживают.

Также стоит отметить, что:

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

Таким образом, в каждом ходу есть всего 25 действительных комбинаций действий:

+-------------+---------------------------------------------+
|   Outcome   |               P L A Y E R   B               |
|    Table    +--------+-----------------+------------------+
| for Players | Load   | Bullet   Plasma | Metal    Thermal |
+---+---------+--------+--------+--------+--------+---------+
| P | Load    |        | B wins | B wins |        |         |
| L +---------+--------+--------+--------+--------+---------+
| A | Bullet  | A wins |        | B wins |        | A wins  |
| Y |         +--------+--------+--------+--------+---------+
| E | Plasma  | A wins | A wins |        | A wins |         |
| R +---------+--------+--------+--------+--------+---------+
|   | Metal   |        |        | B wins |        |         |
|   |         +--------+--------+--------+--------+---------+
| A | Thermal |        | B wins |        |        |         |
+---+---------+--------+--------+---------------------------+

Note: Blank cells indicate that both players survive to the next turn.

Пример дуэли

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

    Me: 001-000-1201101001----2
Friend: 00-10-=1-==--0100-1---1

По правилам выше я проиграл. Вы понимаете почему? Это потому, что я выпустил последний плазменный луч, когда у меня был только один заряженный патрон, и мой пистолет взорвался.


C ++ Player

Вы , как цивилизованный футуристический программист, не будете напрямую обращаться с оружием. Вместо этого вы пишете код, Playerкоторый борется против других. Публично наследуя класс в проекте GitHub, вы можете начать писать свою городскую легенду.

Player.hpp can be found in Tournament\Player.hpp
An example of a derived class can be found in Tournament\CustomPlayer.hpp

Что вы должны или можете сделать

  • Вы должны наследовать Playerкласс через публичное наследование и объявить свой класс финальным.
  • Вы должны переопределить Player::fight, который возвращает действительный Player::Actionкаждый раз, когда он вызывается.
  • При желании, переопределить Player::perceiveи Player::declaredследить за действиями вашего противника и отслеживать ваши победы.
  • При желании используйте частные статические члены и методы в производном классе для выполнения более сложных вычислений.
  • При желании используйте другие стандартные библиотеки C ++.

Что вы не должны делать

  • Вы НЕ ДОЛЖНЫ использовать какой-либо прямой метод для распознавания своего противника, кроме указанного идентификатора противника, который перемешивается в начале каждого турнира. Вы можете только догадываться, кто является игроком через их игру в турнире.
  • Вы должны НЕ переопределить любые методы в Playerклассе , который не объявлен виртуальным.
  • Вы должны НЕ объявлять или инициализировать что - либо в глобальном масштабе.
  • Начиная с дебюта (теперь дисквалифицированного) BlackHatPlayer, игрокам НЕ разрешается заглядывать или изменять состояние вашего оппонента.

Пример дуэли

Процесс оружейного поединка проводится с использованием GunDuelкласса. Пример боя смотрите Source.cppв разделе « Инициирование поединка» .

Мы витрина GunClubPlayer, HumanPlayerи GunDuelкласс, который можно найти в Tournament\каталоге репозитория.

В каждом поединке GunClubPlayerбудет загружаться пуля; уволить его; промыть и повторить. Во время каждого хода HumanPlayerвам будет предложено сыграть против вашего оппонента. Ваши клавиши управления символы 0, 1, 2, -и =. В Windows вы можете использовать HumanPlayerдля отладки вашего представления.

Начало дуэли

Вот как вы можете отлаживать плеер через консоль.

// Source.cpp
// An example duel between a HumanPlayer and GunClubPlayer.

#include "HumanPlayer.hpp"
#include "GunClubPlayer.hpp"
#include "GunDuel.hpp"

int main()
{
    // Total number of turns per duel.
    size_t duelLength = 100;

    // Player identifier 1: HumanPlayer.
    HumanPlayer human(2);
    // Player identifier 2: GunClubPlayer.
    GunClubPlayer gunClub(1);

    // Prepares a duel.
    GunDuel duel(human, gunClub, duelLength);
    // Start a duel.
    duel.fight();
}

Примеры игр

Наименьшее количество ходов, которое вам нужно победить, GunClubPlayerсоставляет 3. Вот повтор игры 0-1против GunClubPlayer. Число в парантезе - это количество заряженных боеприпасов для каждого игрока, когда ход заканчивается.

 :: Turn 0
    You [0/12/-=] >> [0] load ammo (1 ammo)
    Opponent selects [0] load ammo (1 ammo)
 :: Turn 1
    You [0/12/-=] >> [-] defend using metal shield (1 ammo)
    Opponent selects [1] fire a bullet (0 ammo)
 :: Turn 2
    You [0/12/-=] >> [1] fire a bullet (0 ammo)
    Opponent selects [0] load ammo (1 ammo)
 :: You won after 3 turns!
 :: Replay
    YOU 0-1
    FOE 010
Press any key to continue . . .

Самый быстрый способ быть побежденным, GunClubPlayerне делая недопустимых ходов, - это последовательность действий 0=, потому что пуля стреляет прямо через тепловой отражатель. Переигровка

 :: Turn 0
    You [0/12/-=] >> [0] load ammo (1 ammo)
    Opponent selects [0] load ammo (1 ammo)
 :: Turn 1
    You [0/12/-=] >> [=] defend using thermal deflector (1 ammo)
    Opponent selects [1] fire a bullet (0 ammo)
 :: You lost after 2 turns!
 :: Replay
    YOU 0=
    FOE 01
Press any key to continue . . .

Турнир

Турнир проводится в формате «Последний игрок». В турнире все действительные материалы (включая GunClubPlayer) помещаются в пул. Каждой заявке присваивается рандомизированный, но уникальный идентификатор, который остается неизменным в течение всего турнира. Во время каждого раунда:

  • Каждое представление начинается с 0 очков и сыграет 100 поединков против каждого другого представления.
  • Каждый победный поединок дает 1 очко; Рисование и проигрыш дают 0 очков.
  • В конце раунда заявки с минимальным количеством очков покидают турнир. В случае ничьей, игрок с наименьшим количеством очков, заработанных с начала турнира, уйдет.
  • Если осталось более одного игрока, начинается следующий раунд.
  • Очки НЕ переносятся на следующий раунд.

представление

Вы отправите одного игрока за ответ. Вы можете отправить несколько файлов для игрока, если они не мешают другим представлениям. Чтобы все было гладко, пожалуйста:

  • Назовите ваш основной заголовочный файл как <Custom>Player.hpp,
  • Назовите другие файлы как <Custom>Player*.*, например, MyLittlePlayer.txtесли ваше имя класса MyLittlePlayer, или EmoPlayerHates.cppесли ваше имя класса EmoPlayer.
  • Если ваше имя содержит Shooterили похожие слова, которые соответствуют контексту этого турнира, вам не нужно добавлять Playerв конце. Если вы твердо уверены, что ваше имя работает лучше без суффикса Player, вам также не нужно добавлять Player.
  • Убедитесь, что ваш код можно скомпилировать и связать под Windows.

Вы можете комментировать, чтобы попросить разъяснений или определить лазейки. Надеюсь, вам понравится этот футуристический Gun Duel и желаю вам счастливого Нового года!

осветление

  • Вам разрешено иметь случайное поведение.
  • Недопустимые действия (стрельба при загруженных боеприпасах недостаточно) разрешены.
  • Если игрок делает неверный ввод, его пистолет взорвется немедленно.
  • Вам разрешено изучать ответы.
  • Вам явно разрешено записывать поведение противника в каждом турнире.
  • Каждый раунд вы будете играть 100 поединков против каждого противника; порядок 100 поединков, однако, рандомизирован - вам не гарантировано, что вы будете сражаться с одним и тем же противником 100 поединков подряд.

Дополнительные ресурсы

@flawr перевел предоставленный источник C ++ на Java в качестве ссылки, если вы хотите отправить записи C ++.

Интерфейс для не-C ++ представлений

В настоящее время принимаются: Python 3, Java.

Пожалуйста, следуйте одной из спецификаций ниже:

Спецификация интерфейса 1: код выхода

Ваша подача будет выполняться один раз за ход.

Expected Command Line Argument Format:
    <opponent-id> <turn> <status> <ammo> <ammo-opponent> <history> <history-opponent>

Expected Return Code: The ASCII value of a valid action character.
    '0' = 48, '1' = 49, '2' = 50, '-' = 45, '=' = 61

<opponent-id> is an integer in [0, N), where N is size of tournament.
<turn> is 0-based.
If duel is in progress, <status> is 3.
If duel is draw / won / lost, <status> is 0 / 1 / 2.
<history> and <history-opponent> are strings of actions, e.g. 002 0-=
If turn is 0, <history> and <history-opponent> are not provided.
You can ignore arguments you don't particularly need.

Вы можете проверить свои представления PythonPlayer\и JavaPlayer\каталоги.

Спецификация интерфейса 2: стандартный ввод / вывод

(Кредит Х Уолтерсу)

Ваша заявка будет проходить один раз за турнир.

Для всех записей о том, как выполнять ввод / вывод, существует фиксированное требование, поскольку и stdin, и stdout подключены к драйверу турнира. Нарушение этого может привести к тупику. Все записи ДОЛЖНЫ следовать этому алгоритму EXACT (в псевдокоде):

LOOP FOREVER
    READ LINE INTO L
    IF (LEFT(L,1) == 'I')
        INITIALIZE ROUND
        // i.e., set your/opponent ammo to 0, if tracking them
        // Note: The entire line at this point is a unique id per opponent;
        // optionally track this as well.
        CONTINUE LOOP
    ELSE IF (LEFT(L,1) == 'F')
        WRITELN F // where F is your move
    ELSE IF (LEFT(L,1) == 'P')
        PROCESS MID(L,2,1) // optionally perceive your opponent's action.
    END IF
CONTINUE LOOP
QUIT

Здесь F является одним из 0, 1, 2, -, или =для load / bullet / plasma / metal / thermal. ПРОЦЕСС означает опционально реагировать на действия противника (включая отслеживание патронов противника, если вы это делаете). Обратите внимание, что действие противника также является одним из «0», «1», «2», «-» или «=» и относится ко второму символу.

Финальное табло

08:02 AM Tuesday, February 2, 2017 Coordinated Universal Time (UTC)
| Player             | Language   | Points |     1 |     2 |     3 |     4 |     5 |     6 |     7 |     8 |     9 |    10 |    11 |    12 |    13 |    14 |    15 |    16 |
|:------------------ |:---------- | ------:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:|
| MontePlayer        | C++        |  11413 |  1415 |  1326 |  1247 |  1106 |  1049 |   942 |   845 |   754 |   685 |   555 |   482 |   381 |   287 |   163 |   115 |    61 |
| CBetaPlayer        | C++        |   7014 |   855 |   755 |   706 |   683 |   611 |   593 |   513 |   470 |   414 |   371 |   309 |   251 |   192 |   143 |   109 |    39 |
| StudiousPlayer     | C++        |  10014 |  1324 |  1233 |  1125 |  1015 |   907 |   843 |   763 |   635 |   555 |   478 |   403 |   300 |   201 |   156 |    76 |
| FatedPlayer        | C++        |   6222 |   745 |   683 |   621 |   655 |   605 |   508 |   494 |   456 |   395 |   317 |   241 |   197 |   167 |   138 |
| HanSoloPlayer      | C++        |   5524 |   748 |   668 |   584 |   523 |   490 |   477 |   455 |   403 |   335 |   293 |   209 |   186 |   153 |
| SurvivorPlayer     | C++        |   5384 |   769 |   790 |   667 |   574 |   465 |   402 |   354 |   338 |   294 |   290 |   256 |   185 |
| SpecificPlayer     | C++        |   5316 |   845 |   752 |   669 |   559 |   488 |   427 |   387 |   386 |   340 |   263 |   200 |
| DeceptivePlayer    | C++        |   4187 |   559 |   445 |   464 |   474 |   462 |   442 |   438 |   369 |   301 |   233 |
| NotSoPatientPlayer | C++        |   5105 |   931 |   832 |   742 |   626 |   515 |   469 |   352 |   357 |   281 |
| BarricadePlayer    | C++        |   4171 |   661 |   677 |   614 |   567 |   527 |   415 |   378 |   332 |
| BotRobotPlayer     | C++        |   3381 |   607 |   510 |   523 |   499 |   496 |   425 |   321 |
| SadisticShooter    | C++        |   3826 |   905 |   780 |   686 |   590 |   475 |   390 |
| TurtlePlayer       | C++        |   3047 |   754 |   722 |   608 |   539 |   424 |
| CamtoPlayer        | C++        |   2308 |   725 |   641 |   537 |   405 |
| OpportunistPlayer  | C++        |   1173 |   426 |   420 |   327 |
| GunClubPlayer      | C++        |    888 |   500 |   388 |
| PlasmaPlayer       | C++        |    399 |   399 |

Турнир продлится до 1 февраля 2017 года, если не указано иное.


15
Впечатляющий первый вызов, кстати!
Мартин Эндер

3
Если вы хотите запустить некоторые другие языки, вы можете разрешить Playerреализации, которая вызывает другой процесс, чтобы вычислить текущий ход. Это позволило бы людям участвовать на любом языке, который вы с удовольствием используете на своей машине.
Мартин Эндер

5
Случайность разрешена? (Не совсем случайные повороты, просто выбор действий 50/50 в определенной ситуации)
FlipTack

2
Технический пункт; «Вы должны наследовать Player::fight» / «Вы можете наследовать Player::perceive» ... в обоих случаях термин переопределяет , а не наследует .
H Уолтерс

3
Я думаю, что у вас есть ошибка GunDuel.hpp, как validAи validBиспользоватьactionA
AlexRacer

Ответы:


9

MontePlayer

Этот игрок использует алгоритм поиска дерева «Развязанный UCT Монте-Карло», чтобы решить, какой выбор он должен сделать. Он отслеживает, что делает враг, чтобы предсказать его действия. Он имитирует врага как самого себя, если ему не хватает данных.

Этот бот действительно хорошо работает против любого другого бота, кроме cβ. В 10000 поединках против cβ Монте выиграл 5246 поединков. С небольшим количеством математики это означает, что Монте выиграет поединок против cβ 51,17% до 53,74% времени (уверенность 99%).

#ifndef __Monte_PLAYER_HPP__
#define __Monte_PLAYER_HPP__

#include "Player.hpp"
#include <cstdlib>
#include <ctime>
#include <memory>
#include <iostream>


class MontePlayer final : public Player
{
    static const int MAX_TURNS = 100;
    static const int TOTAL_ACTIONS = 5;

    //Increase this if number of players goes above 20.
    static const int MAX_PLAYERS = 20;

    //The number of simulated games we run every time our program is called.
    static const int MONTE_ROUNDS = 1000;


    /**
    * Represents the current state of the game.
    */
    struct Game
    {
        int turn;
        int ammo;
        int opponentAmmo;
        bool alive;
        bool opponentAlive;

        Game(int turn, int ammo, int opponentAmmo, bool alive, bool opponentAlive)
            : turn(turn), ammo(ammo), opponentAmmo(opponentAmmo), alive(alive), opponentAlive(opponentAlive) {}
        Game() : turn(0), ammo(0), opponentAmmo(0), alive(false), opponentAlive(false) {}
    };

    struct Stat
    {
        int wins;
        int attempts;

        Stat() : wins(0), attempts(0) {}
    };

    /**
    * A Monte tree data structure.
    */
    struct MonteTree
    {
        //The state of the game.
        Game game;

        //myStats[i] returns the statistic for doing the i action in this state.
        Stat myStats[TOTAL_ACTIONS];
        //opponentStats[i] returns the statistic for the opponent doing the i action in this state.
        Stat opponentStats[TOTAL_ACTIONS];
        //Total number of times we've created statistics from this tree.
        int totalPlays = 0;
        //The action that led to this tree.
        int myAction;
        //The opponent action that led to this tree.
        int opponentAction;

        //The tree preceding this one.
        MonteTree *parent = NULL;

        //subtrees[i][j] is the tree that would follow if I did action i and the
        //opponent did action j.
        MonteTree *subtrees[TOTAL_ACTIONS][TOTAL_ACTIONS] = { { NULL } };

        MonteTree(int turn, int ammo, int opponentAmmo) :
            game(turn, ammo, opponentAmmo, true, true) {}


        MonteTree(Game game, MonteTree *parent, int myAction, int opponentAction) :
            game(game), parent(parent), myAction(myAction), opponentAction(opponentAction)
        {
            //Make sure the parent tree keeps track of this tree.
            parent->subtrees[myAction][opponentAction] = this;
        }

        //The destructor so we can avoid slow ptr types and memory leaks.
        ~MonteTree()
        {
            //Delete all subtrees.
            for (int i = 0; i < TOTAL_ACTIONS; i++)
            {
                for (int j = 0; j < TOTAL_ACTIONS; j++)
                {
                    auto branch = subtrees[i][j];

                    if (branch)
                    {
                        branch->parent = NULL;
                        delete branch;
                    }
                }
            }
        }
    };

    //The previous state.
    Game prevGame;
    //The id of the opponent.
    int opponent;
    //opponentHistory[a][b][c][d] returns the number of times
    //that opponent a did action d when I had b ammo and he had c ammo.
    static int opponentHistory[MAX_PLAYERS][MAX_TURNS][MAX_TURNS][TOTAL_ACTIONS];

public:
    MontePlayer(size_t opponent = -1) : Player(opponent)
    {
        srand(time(NULL));
        this->opponent = opponent;
    }

public:

    virtual Action fight()
    {
        //Create the root tree. Will be auto-destroyed after this function ends.
        MonteTree current(getTurn(), getAmmo(), getAmmoOpponent());

        //Set the previous game to this one.
        prevGame = current.game;

        //Get these variables so we can log later if nessecarry.
        int turn = getTurn(),
            ammo = getAmmo(),
            opponentAmmo = getAmmoOpponent();

        for (int i = 0; i < MONTE_ROUNDS; i++)
        {
            //Go down the tree until we find a leaf we haven't visites yet.
            MonteTree *leaf = selection(&current);

            //Randomly simulate the game at the leaf and get the result.
            int score = simulate(leaf->game);

            //Propagate the scores back up the root.
            update(leaf, score);
        }

        //Get the best move.
        int move = bestMove(current);

        //Move string for debugging purposes.
        const char* m;

        //We have to do this so our bots state is updated.
        switch (move)
        {
        case Action::LOAD:
            load();
            m = "load";
            break;
        case Action::BULLET:
            bullet();
            m = "bullet";
            break;
        case Action::PLASMA:
            plasma();
            m = "plasma";
            break;
        case Action::METAL:
            metal();
            m = "metal";
            break;
        case Action::THERMAL:
            thermal();
            m = "thermal";
            break;
        default: //???
            std::cout << move << " ???????\n";
            throw move;
        }

        return (Action)move;
    }

    /**
    * Record what the enemy does so we can predict him.
    */
    virtual void perceive(Action action)
    {
        Player::perceive(action);
        opponentHistory[opponent][prevGame.ammo][prevGame.opponentAmmo][action]++;
    }
private:

    /**
    * Trickle down root until we have to create a new leaf MonteTree or we hit the end of a game.
    */
    MonteTree * selection(MonteTree *root)
    {
        while (!atEnd(root->game))
        {
            //First pick the move that my bot will do.

            //The action my bot will do.
            int myAction;
            //The number of actions with the same bestScore.
            int same = 0;
            //The bestScore
            double bestScore = -1;

            for (int i = 0; i < TOTAL_ACTIONS; i++)
            {
                //Ignore invalid or idiot moves.
                if (!isValidMove(root->game, i, true))
                {
                    continue;
                }

                //Get the score for doing move i. Uses
                double score = computeScore(*root, i, true);

                //Randomly select one score if multiple actions have the same score.
                //Why this works is boring to explain.
                if (score == bestScore)
                {
                    same++;
                    if (Random(same) == 0)
                    {
                        myAction = i;
                    }
                }
                //Yay! We found a better action.
                else if (score > bestScore)
                {
                    same = 1;
                    myAction = i;
                    bestScore = score;
                }
            }

            //The action the enemy will do.
            int enemyAction;

            //The number of times the enemy has been in this same situation.
            int totalEnemyEncounters = 0;
            for (int i = 0; i < TOTAL_ACTIONS; i++)
            {
                totalEnemyEncounters += opponentHistory[opponent][root->game.ammo][root->game.opponentAmmo][i];
            }

            //Assume the enemy will choose an action it has chosen before if we've
            //seen it in this situation before. Otherwise we assume that the enemy is ourselves.
            if (totalEnemyEncounters > 0)
            {
                //Randomly select an action that the enemy has done with
                //weighted by the number of times that action has been done.
                int selection = Random(totalEnemyEncounters);
                for (int i = 0; i < TOTAL_ACTIONS; i++)
                {
                    selection -= opponentHistory[opponent][root->game.ammo][root->game.opponentAmmo][i];
                    if (selection < 0)
                    {
                        enemyAction = i;
                        break;
                    }
                }
            }
            else
            {
                //Use the same algorithm to pick the enemies move we use for ourselves.
                same = 0;
                bestScore = -1;
                for (int i = 0; i < TOTAL_ACTIONS; i++)
                {
                    if (!isValidMove(root->game, i, false))
                    {
                        continue;
                    }

                    double score = computeScore(*root, i, false);
                    if (score == bestScore)
                    {
                        same++;
                        if (Random(same) == 0)
                        {
                            enemyAction = i;
                        }
                    }
                    else if (score > bestScore)
                    {
                        same = 1;
                        enemyAction = i;
                        bestScore = score;
                    }
                }
            }

            //If this combination of actions hasn't been explored yet, create a new subtree to explore.
            if (!(*root).subtrees[myAction][enemyAction])
            {
                return expand(root, myAction, enemyAction);
            }

            //Do these actions and explore the next subtree.
            root = (*root).subtrees[myAction][enemyAction];
        }
        return root;
    }

    /**
    * Creates a new leaf under root for the actions.
    */
    MonteTree * expand(MonteTree *root, int myAction, int enemyAction)
    {
        return new MonteTree(
            doTurn(root->game, myAction, enemyAction),
            root,
            myAction,
            enemyAction);
    }

    /**
    * Computes the score of the given move in the given position.
    * Uses the UCB1 algorithm and returns infinity for moves not tried yet.
    */
    double computeScore(const MonteTree &root, int move, bool me)
    {
        const Stat &stat = me ? root.myStats[move] : root.opponentStats[move];
        return stat.attempts == 0 ?
            HUGE_VAL :
            double(stat.wins) / stat.attempts + sqrt(2 * log(root.totalPlays) / stat.attempts);
    }

    /**
    * Randomly simulates the given game.
    * Has me do random moves that are not stupid.
    * Has opponent do what it has done in similar positions or random moves if not
    * observed in those positions yet.
    *
    * Returns 1 for win. 0 for loss. -1 for draw.
    */
    int simulate(Game game)
    {
        while (!atEnd(game))
        {
            game = doRandomTurn(game);
        }

        if (game.alive > game.opponentAlive)
        {
            return 1;
        }
        else if (game.opponentAlive > game.alive)
        {
            return 0;
        }
        else //Draw
        {
            return -1;
        }
    }

    /**
    * Returns whether the game is over or not.
    */
    bool atEnd(Game game)
    {
        return !game.alive || !game.opponentAlive || game.turn > MAX_TURNS;
    }

    /**
    * Simulates the given actions on the game.
    */
    Game doTurn(Game game, int myAction, int enemyAction)
    {
        game.turn++;

        switch (myAction)
        {
        case Action::LOAD:
            game.ammo++;
            break;
        case Action::BULLET:
            if (game.ammo < 1)
            {
                game.alive = false;
                break;
            }
            game.ammo--;
            if (enemyAction == Action::LOAD || enemyAction == Action::THERMAL)
            {
                game.opponentAlive = false;
            }
            break;
        case Action::PLASMA:
            if (game.ammo < 2)
            {
                game.alive = false;
                break;
            }
            game.ammo -= 2;
            if (enemyAction != Action::PLASMA && enemyAction != Action::THERMAL)
            {
                game.opponentAlive = false;
            }
            break;
        }

        switch (enemyAction)
        {
        case Action::LOAD:
            game.opponentAmmo++;
            break;
        case Action::BULLET:
            if (game.opponentAmmo < 1)
            {
                game.opponentAlive = false;
                break;
            }
            game.opponentAmmo--;
            if (myAction == Action::LOAD || myAction == Action::THERMAL)
            {
                game.alive = false;
            }
            break;
        case Action::PLASMA:
            if (game.opponentAmmo < 2)
            {
                game.opponentAlive = false;
            }
            game.opponentAmmo -= 2;
            if (myAction != Action::PLASMA && myAction != Action::THERMAL)
            {
                game.alive = false;
            }
            break;
        }

        return game;
    }

    /**
    * Chooses a random move for me and my opponent and does it.
    */
    Game doRandomTurn(Game &game)
    {
        //Select my random move.
        int myAction;
        int validMoves = 0;

        for (int i = 0; i < TOTAL_ACTIONS; i++)
        {
            //Don't do idiotic moves.
            //Select one at random.
            if (isValidMove(game, i, true))
            {
                validMoves++;
                if (Random(validMoves) == 0)
                {
                    myAction = i;
                }
            }
        }

        //Choose random opponent action.
        int opponentAction;

        //Whether the enemy has encountered this situation before
        bool enemyEncountered = false;

        validMoves = 0;

        //Weird algorithm that works and I don't want to explain.
        //What it does:
        //If the enemy has encountered this position before,
        //then it chooses a random action weighted by how often it did that action.
        //If they haven't, makes the enemy choose a random not idiot move.
        for (int i = 0; i < TOTAL_ACTIONS; i++)
        {
            int weight = opponentHistory[opponent][game.ammo][game.opponentAmmo][i];
            if (weight > 0)
            {
                if (!enemyEncountered)
                {
                    enemyEncountered = true;
                    validMoves = 0;
                }
                validMoves += weight;
                if (Random(validMoves) < weight)
                {
                    opponentAction = i;
                }
            }
            else if (!enemyEncountered && isValidMove(game, i, false))
            {
                validMoves++;
                if (Random(validMoves) == 0)
                {
                    opponentAction = i;
                }
            }
        }

        return doTurn(game, myAction, opponentAction);
    }

    /**
    * Returns whether the given move is valid/not idiotic for the game.
    */
    bool isValidMove(Game game, int move, bool me)
    {
        switch (move)
        {
        case Action::LOAD:
            return true;
        case Action::BULLET:
            return me ? game.ammo > 0 : game.opponentAmmo > 0;
        case Action::PLASMA:
            return me ? game.ammo > 1 : game.opponentAmmo > 1;
        case Action::METAL:
            return me ? game.opponentAmmo > 0 : game.ammo > 0;
        case Action::THERMAL:
            return me ? game.opponentAmmo > 1 : game.ammo > 1;
        default:
            return false;
        }
    }

    /**
    * Propagates the score up the MonteTree from the leaf.
    */
    void update(MonteTree *leaf, int score)
    {
        while (true)
        {
            MonteTree *parent = leaf->parent;
            if (parent)
            {
                //-1 = draw, 1 = win for me, 0 = win for opponent
                if (score != -1)
                {
                    parent->myStats[leaf->myAction].wins += score;
                    parent->opponentStats[leaf->opponentAction].wins += 1 - score;
                }
                parent->myStats[leaf->myAction].attempts++;
                parent->opponentStats[leaf->opponentAction].attempts++;
                parent->totalPlays++;
                leaf = parent;
            }
            else
            {
                break;
            }
        }
    }

    /**
    * There are three different strategies in here.
    * The first is not random, the second more, the third most.
    */
    int bestMove(const MonteTree &root)
    {
        //Select the move with the highest win rate.
        int best;
        double bestScore = -1;
        for (int i = 0; i < TOTAL_ACTIONS; i++)
        {
            if (root.myStats[i].attempts == 0)
            {
                continue;
            }

            double score = double(root.myStats[i].wins) / root.myStats[i].attempts;
            if (score > bestScore)
            {
                bestScore = score;
                best = i;
            }
        }

        return best;

        ////Select a move weighted by the number of times it has won the game.
        //int totalScore = 0;
        //for (int i = 0; i < TOTAL_ACTIONS; i++)
        //{
        //  totalScore += root.myStats[i].wins;
        //}
        //int selection = Random(totalScore);
        //for (int i = 0; i < TOTAL_ACTIONS; i++)
        //{
        //  selection -= root.myStats[i].wins;
        //  if (selection < 0)
        //  {
        //      return i;
        //  }
        //}

        ////Select a random move weighted by win ratio.
        //double totalScore = 0;
        //for (int i = 0; i < TOTAL_ACTIONS; i++)
        //{
        //  if (root.myStats[i].attempts == 0)
        //  {
        //      continue;
        //  }
        //  totalScore += double(root.myStats[i].wins) / root.myStats[i].attempts;
        //}
        //double selection = Random(totalScore);
        //for (int i = 0; i < TOTAL_ACTIONS; i++)
        //{
        //  if (root.myStats[i].attempts == 0)
        //  {
        //      continue;
        //  }
        //  selection -= double(root.myStats[i].wins) / root.myStats[i].attempts;
        //  if (selection < 0)
        //  {
        //      return i;
        //  }
        //}
    }

    //My own random functions.
    int Random(int max)
    {
        return GetRandomInteger(max - 1);
    }
    double Random(double max)
    {
        static auto seed = std::chrono::system_clock::now().time_since_epoch().count();
        static std::default_random_engine generator((unsigned)seed);
        std::uniform_real_distribution<double> distribution(0.0, max);
        return distribution(generator);
    }
};
//We have to initialize this here for some reason.
int MontePlayer::opponentHistory[MAX_PLAYERS][MAX_TURNS][MAX_TURNS][TOTAL_ACTIONS]{ { { { 0 } } } };

#endif // !__Monte_PLAYER_HPP__

25

BlackHatPlayer

Игрок BlackHat знает, что пули и щиты остались в прошлом; настоящие войны выигрывают те, кто может взломать программы противника.

Итак, он надевает неподвижный металлический щит и начинает делать свое дело.

В первый раз, когда его просят fight, он пытается локализовать своего врага в памяти. Учитывая структуру боевой арены, он почти уверен, что компилятор в конечном итоге поместит его адрес (завернутый в unique_ptr) и адрес противника просто один рядом с другим.

Итак, BlackHat осторожно обходит стек, используя некоторую простую эвристику, чтобы не потерять его, пока не найдет указатель на себя; затем проверяет, являются ли значения в соседних позициях правдоподобным его оппонентом - аналогичный адрес, аналогичный адрес vtable, правдоподобный typeid.

Если ему удается найти его, он высасывает его мозги и заменяет их мозгами горячего идиота. На практике это делается путем замены указателя оппонента на vtable адресом Idiotvtable - тупого игрока, который всегда стреляет.

Если это все удастся (и в моих тестах - gcc 6 на 64-битной Linux, MinGW 4.8 на 32-битной Wine - это работает довольно надежно), война выиграна. Что бы ни делал противник в первом раунде, это не важно - в худшем случае он выстрелил в нас, и у нас был металлический щит.

Отныне у нас идиот просто стреляет; у нас всегда есть наш щит, поэтому мы защищены, и он взорвется за 1-3 раунда (в зависимости от того, что сделал первый бот в своем первом fightколле).


Теперь: я почти уверен, что это должно быть немедленно дисквалифицировано, но забавно, что я явно не нарушаю ни одно из правил, изложенных выше:

Что вы не должны делать

  • Вы НЕ ДОЛЖНЫ использовать какой-либо прямой метод для распознавания своего противника, кроме данного идентификатора противника, который полностью рандомизируется в начале каждого турнира. Вы можете только догадываться, кто является игроком через их игровой процесс в турнире.

BlackHat не пытается распознать противника - фактически, совершенно не имеет значения, кто является противником, учитывая, что его мозг заменяется немедленно.

  • Вы не должны переопределять любые методы в классе Player, которые не объявлены виртуальными.
  • Вы не должны объявлять или инициализировать что-либо в глобальной области видимости.

Все происходит локально с fightвиртуальной функцией.


// BlackHatPlayer.hpp

#ifndef __BLACKHAT_PLAYER_HPP__
#define __BLACKHAT_PLAYER_HPP__

#include "Player.hpp"
#include <stddef.h>
#include <typeinfo>
#include <algorithm>
#include <string.h>

class BlackHatPlayer final : public Player
{
public:
    using Player::Player;

    virtual Action fight()
    {
        // Always metal; if the other is an Idiot, he only shoots,
        // and if he isn't an Idiot yet (=first round) it's the only move that
        // is always safe
        if(tricked) return metal();
        // Mark that at the next iterations we don't have to do all this stuff
        tricked = true;

        typedef uintptr_t word;
        typedef uintptr_t *pword;
        typedef uint8_t *pbyte;

        // Size of one memory page; we use it to walk the stack carefully
        const size_t pageSize = 4096;
        // Maximum allowed difference between the vtables
        const ptrdiff_t maxVTblDelta = 65536;
        // Maximum allowed difference between this and the other player
        ptrdiff_t maxObjsDelta = 131072;

        // Our adversary
        Player *c = nullptr;

        // Gets the start address of the memory page for the given object
        auto getPage = [&](void *obj) {
            return pword(word(obj) & (~word(pageSize-1)));
        };
        // Gets the start address of the memory page *next* to the one of the given object
        auto getNextPage = [&](void *obj) {
            return pword(pbyte(getPage(obj)) + pageSize);
        };

        // Gets a pointer to the first element of the vtable
        auto getVTbl = [](void *obj) {
            return pword(pword(obj)[0]);
        };

        // Let's make some mess to make sure that:
        // - we have an actual variable on the stack;
        // - we call an external (non-inline) function that ensures everything
        //   is spilled on the stack
        // - the compiler actually generates the full vtables (in the current
        //   tournament this shouldn't be an issue, but in earlier sketches
        //   the compiler inlined everything and killed the vtables)
        volatile word i = 0;
        for(const char *sz = typeid(*(this+i)).name(); *sz; ++sz) i+=*sz;

        // Grab my vtable
        word *myVTbl = getVTbl(this);

        // Do the stack walk
        // Limit for the stack walk; use i as a reference
        word *stackEnd = getNextPage((pword)(&i));
        for(word *sp = pword(&i);       // start from the location of i
            sp!=stackEnd && c==nullptr;
            ++sp) {                     // assume that the stack grows downwards
            // If we find something that looks like a pointer to memory
            // in a page just further on the stack, take it as a clue that the
            // stack in facts does go on
            if(getPage(pword(*sp))==stackEnd) {
                stackEnd = getNextPage(pword(*sp));
            }
            // We are looking for our own address on the stack
            if(*sp!=(word)this) continue;

            auto checkCandidate = [&](void *candidate) -> Player* {
                // Don't even try with NULLs and the like
                if(getPage(candidate)==nullptr) return nullptr;
                // Don't trust objects too far away from us - it's probably something else
                if(abs(pbyte(candidate)-pbyte(this))>maxObjsDelta) return nullptr;
                // Grab the vtable, check if it actually looks like one (it should be
                // decently near to ours)
                pword vtbl = getVTbl(candidate);
                if(abs(vtbl-myVTbl)>maxVTblDelta) return nullptr;
                // Final check: try to see if its name looks like a "Player"
                Player *p = (Player *)candidate;
                if(strstr(typeid(*p).name(), "layer")==0) return nullptr;
                // Jackpot!
                return p;
            };

            // Look around us - a pointer to our opponent should be just near
            c = checkCandidate((void *)sp[-1]);
            if(c==nullptr) c=checkCandidate((void *)sp[1]);
        }

        if(c!=nullptr) {
            // We found it! Suck his brains out and put there the brains of a hothead idiot
            struct Idiot : Player {
                virtual Action fight() {
                    // Always fire, never reload; blow up in two turns
                    // (while we are always using the metal shield to protect ourselves)
                    return bullet();
                }
            };
            Idiot idiot;
            // replace the vptr
            (*(word *)(c)) = word(getVTbl(&idiot));
        }
        // Always metal shield to be protected from the Idiot
        return metal();
    }
private:
    bool tricked = false;
};

#endif // !__BLACKHAT_PLAYER_HPP__

6
@TheNumberOne: также, согласно первому (и наиболее одобренному) комментарию к теме лазеек: «Лазейки - это часть того, что делает игру интересной. Даже общие могут быть забавными или умными, в зависимости от контекста». IMO, это оригинально (по крайней мере, я никогда не видел здесь ничего подобного) и прилично интересно, с технической точки зрения; Вот почему я поделился этим здесь.
Matteo Italia

3
#ifdef __BLACKHAT_PLAYER_HPP__#error "Dependency issue; to compile, please include this file before BlackHatPlayer.hpp"#else#define __BLACKHAT_PLAYER_HPP__#endif
H Walters

1
@MatteoItalia BlackHat всегда расширяет наши знания о стандартных лазейках :-)
Frenzy Li

2
@HWalters: я думаю, мне придется переключиться на #pragma once;-)
Matteo Italia

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

19

Далее, самый страшный из всех существ, он был в аду и обратно и сражался буквально с 900000 других ботов , его ...

BotRobot

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

Две команды из 9 были настроены против друг друга, в каждом поколении каждый робот из команды 1 противостоит каждому роботу из команды 2. Роботы с большим количеством побед, чем поражений, сохранили свою память, а другие вернулись к последнему шагу и был шанс что-то забыть, надеюсь, плохо. Сами боты - прославленные таблицы поиска, где, если они найдут то, чего не видели раньше, они просто выберут случайную действительную опцию и сохранят ее в памяти. Версия C ++ не делает этого, она должна была выучить . Как указывалось ранее, победившие боты сохраняют эту новую найденную память, так же ясно, как она работала. Потерять ботов не надо, и сохраните то, с чего они начали.

В конце концов, бои с ботами проходили довольно близко, из-за этого они редко оказывались в тупике. Победитель был выбран из пула двух команд после эволюции, который составлял 100000 поколений.

BotRobot с его случайно сгенерированным и КРАСИВЫМ именем был счастливчиком.

Генератор

bot.lua

Пересмотр: Хотя робот был довольно умным против себя и других похожих роботов, он оказался довольно бесполезным в реальных битвах. Итак, я восстановил его мозг против некоторых из уже созданных ботов.

Результаты, как легко увидеть, представляют собой гораздо более сложный мозг, в котором у противника есть 12 патронов.

Я не уверен, что он боролся против того, чтобы получить до 12 патронов, но что-то сделал.

И конечно же, готовый продукт ...

// BotRobot
// ONE HUNDRED THOUSAND GENERATIONS TO MAKE THE ULTIMATE LIFEFORM!

#ifndef __BOT_ROBOT_PLAYER_HPP__
#define __BOT_ROBOT_PLAYER_HPP__

#include "Player.hpp"

class BotRobotPlayer final : public Player
{
public:
    BotRobotPlayer(size_t opponent = -1) : Player(opponent) {}

public:
    virtual Action fight()
    {
        std::string action = "";
        action += std::to_string(getAmmo());
        action += ":";
        action += std::to_string(getAmmoOpponent());

        int toDo = 3;

        for (int i = 0; i < int(sizeof(options)/sizeof(*options)); i++) {
            if (options[i].compare(action)==0) {
                toDo = outputs[i];
                break;
            }
        }

        switch (toDo) {
            case 0:
                return load();
            case 1:
                return bullet();
            case 2:
                return plasma();
            case 3:
                return metal();
            default:
                return thermal();
        }
    }

private:
    std::string options[29] =
    {
        "0:9",
        "1:12",
        "1:10",
        "0:10",
        "1:11",
        "0:11",
        "0:6",
        "2:2",
        "0:2",
        "2:6",
        "3:6",
        "0:7",
        "1:3",
        "2:3",
        "0:3",
        "2:0",
        "1:0",
        "0:4",
        "1:4",
        "2:4",
        "0:0",
        "3:0",
        "1:1",
        "2:1",
        "2:9",
        "0:5",
        "0:8",
        "3:1",
        "0:1"
    };

    int outputs[29] =
    {
        0,
        1,
        1,
        4,
        1,
        0,
        0,
        4,
        4,
        0,
        0,
        3,
        0,
        1,
        3,
        0,
        1,
        4,
        0,
        1,
        0,
        1,
        0,
        3,
        4,
        3,
        0,
        1,
        0
    };
};

#endif // !__BOT_ROBOT_PLAYER_HPP__

Я ненавижу C ++ сейчас ...


@FrenzyLi Не знаю, как я этого не заметил, сейчас поправляю.
ATaco

Что ж, после этого обновления бот, похоже, имеет фиксированное открытие 00.
Безумие Ли

Теперь я понимаю, почему ... "1: 1" дает "0".
Безумие Ли

1
несколько игроков здесь починили всю свою игру, основываясь на ходах, поэтому я не думаю, что фиксированное открытие должно быть проблемой
eis

10

CBetaPlayer (Cβ)

Приблизительное равновесие по Нэшу.

Этот бот просто модная математика с оберткой кода.

Мы можем переосмыслить это как проблему теории игр. Обозначим победу +1, а проигрыш -1. Теперь пусть B (x, y) будет значением игры, в которой у нас есть боеприпасы x, а у нашего противника - боеприпасы y. Обратите внимание, что B (a, b) = -B (b, a) и, следовательно, B (a, a) = 0. Чтобы найти значения B в терминах других значений B, мы можем вычислить значение матрицы выплат. Например, у нас есть B (1, 0), заданное значением следующей подигры:

       load      metal
load    B(0, 1)   B(2, 0)
bullet  +1        B(0, 0)

(Я удалил «плохие» варианты, то есть те, в которых строго доминируют существующие решения. Например, мы не будем пытаться стрелять плазмой, поскольку у нас всего 1 патрон. Аналогично, наш противник никогда не будет использовать тепловой дефлектор, так как мы никогда не будем стрелять плазмой.)

Теория игр позволяет нам узнать, как найти значение этой матрицы выплат, исходя из определенных технических условий. Мы получаем, что значение вышеуказанной матрицы:

                B(2, 0)
B(1, 0) = ---------------------
          1 + B(2, 0) - B(2, 1)

Продолжая все возможные игры и отмечая, что B (x, y) -> 1 при x -> бесконечности с фиксированным y, мы можем найти все значения B, что, в свою очередь, позволяет нам вычислить идеальные ходы!

Конечно, теория редко совпадает с реальностью. Решение уравнения даже для небольших значений x и y быстро становится слишком сложным. Чтобы справиться с этим, я ввел то, что я называю cβ-приближением. В этом приближении есть 7 параметров: c0, β0, c1, β1, c, β и k. Я предположил, что значения B приняли следующую форму (сначала наиболее конкретные формы):

B(1, 0) = k
B(x, 0) = 1 - c0 β0^x
B(x, 1) = 1 - c1 β1^x
B(x, y) = 1 - c β^(x - y)   (if x > y)

Некоторые грубые рассуждения о том, почему я выбрал эти параметры. Сначала я понял, что определенно хочу иметь дело с 0, 1 и 2 или более патронами по отдельности, поскольку каждый открывает разные варианты. Также я подумал, что геометрическая функция выживания будет наиболее подходящей, потому что защитник по сути угадывает, что делать. Я подумал, что наличие двух или более боеприпасов было в основном одинаковым, поэтому вместо этого я сосредоточился на разнице. Я также хотел рассматривать B (1, 0) как супер особый случай, потому что я думал, что это будет много. Использование этих приближенных форм значительно упростило вычисление значений B.

Я приблизительно решил результирующие уравнения, чтобы получить каждое значение B, которое затем поместил обратно в матрицу, чтобы получить матрицы выплат. Затем, используя решатель линейного программирования, я нашел оптимальные вероятности для каждого движения и запихнул их в программу.

Программа представляет собой прославленную таблицу поиска. Если у обоих игроков есть от 0 до 4 патронов, он использует матрицу вероятности, чтобы случайным образом определить, какой ход он должен сделать. В противном случае он пытается экстраполировать на основе своей таблицы.

У него есть проблемы против глупых детерминированных ботов, но он довольно хорош против рациональных ботов. Из-за всего этого приближения это иногда проигрывает StudiousPlayer, когда это действительно не должно.

Конечно, если бы я сделал это снова, я бы, вероятно, попытался бы добавить больше независимых параметров или, возможно, лучшую форму анзаца и найти более точное решение. Также я (нарочно) проигнорировал предел хода, потому что это усложняло ситуацию. Быстрая модификация может быть сделана так, чтобы всегда стрелять из плазмы, если у нас достаточно патронов и не хватает ходов.

// CBetaPlayer (cβ)
// PPCG: George V. Williams

#ifndef __CBETA_PLAYER_HPP__
#define __CBETA_PLAYER_HPP__

#include "Player.hpp"
#include <iostream>

class CBetaPlayer final : public Player
{
public:
    CBetaPlayer(size_t opponent = -1) : Player(opponent)
    {
    }

public:
    virtual Action fight()
    {
        int my_ammo = getAmmo(), opp_ammo = getAmmoOpponent();

        while (my_ammo >= MAX_AMMO || opp_ammo >= MAX_AMMO) {
            my_ammo--;
            opp_ammo--;
        }

        if (my_ammo < 0) my_ammo = 0;
        if (opp_ammo < 0) opp_ammo = 0;

        double cdf = GetRandomDouble();
        int move = -1;
        while (cdf > 0 && move < MAX_MOVES - 1)
            cdf -= probs[my_ammo][opp_ammo][++move];

        switch (move) {
            case 0: return load();
            case 1: return bullet();
            case 2: return plasma();
            case 3: return metal();
            case 4: return thermal();
            default: return fight();
        }
    }

    static double GetRandomDouble() {
        static auto seed = std::chrono::system_clock::now().time_since_epoch().count();
        static std::default_random_engine generator((unsigned)seed);
        std::uniform_real_distribution<double> distribution(0.0, 1.0);
        return distribution(generator);
    }

private:
    static const int MAX_AMMO = 5;
    static const int MAX_MOVES = 5;

    double probs[MAX_AMMO][MAX_AMMO][5] =
        {
            {{1, 0, 0, 0, 0}, {0.58359, 0, 0, 0.41641, 0}, {0.28835, 0, 0, 0.50247, 0.20918}, {0.17984, 0, 0, 0.54611, 0.27405}, {0.12707, 0, 0, 0.56275, 0.31018}},
            {{0.7377, 0.2623, 0, 0, 0}, {0.28907, 0.21569, 0, 0.49524, 0}, {0.0461, 0.06632, 0, 0.53336, 0.35422}, {0.06464, 0.05069, 0, 0.43704, 0.44763}, {0.02215, 0.038, 0, 0.33631, 0.60354}},
            {{0.47406, 0.37135, 0.1546, 0, 0}, {0.1862, 0.24577, 0.15519, 0.41284, 0}, {0, 0.28343, 0.35828, 0, 0.35828}, {0, 0.20234, 0.31224, 0, 0.48542}, {0, 0.12953, 0.26546, 0, 0.605}},
            {{0.33075, 0.44563, 0.22362, 0, 0}, {0.17867, 0.20071, 0.20071, 0.41991, 0}, {0, 0.30849, 0.43234, 0, 0.25916}, {0, 0.21836, 0.39082, 0, 0.39082}, {0, 0.14328, 0.33659, 0, 0.52013}},
            {{0.24032, 0.48974, 0.26994, 0, 0}, {0.14807, 0.15668, 0.27756, 0.41769, 0}, {0, 0.26804, 0.53575, 0, 0.19621}, {0, 0.22106, 0.48124, 0, 0.2977}, {0, 0.15411, 0.42294, 0, 0.42294}}
        };


};

#endif // !__CBETA_PLAYER_HPP__

Поскольку вы не передаете параметр в GetRandomDouble, вы можете удалить аргумент max.
Безумие Ли

@FrenzyLi, ой, спасибо!
Джордж В. Уильямс

Не могли бы вы добавить немного больше информации о вашем игроке, например, как вы пришли к вероятности ... тензор?
Безумие Ли

2
Я люблю этого бота. Я думаю, что SP имеет преимущество только благодаря детерминизму других записей; чем больше (неоптимально взвешенных) случайных ботов добавлено, тем лучше тарифы CBP. Это подтверждается тестированием; в моих внутренних тестах с обычными подозреваемыми SP всегда побеждает с CBP вторым ... однако, в мини-конкурсе, включающем CBP, SP и FP, CBP опережает 55% времени, при этом SP и FP развиваются примерно равномерно.
H Уолтерс

1
Кстати, это впечатляюще точное приближение равновесия Нэша. Монте не пытается найти стратегию равноденствия, но лучший ход против любого противника. Тот факт, что он выигрывает только 52% процентов поединков между ним и cβ, означает, что cβ довольно опасно близко к равновесию Нэша.
TheNumberOne

8

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

[Редактировать] Спасибо, теперь предыдущий статус больше не соответствует действительности, но я думаю, что лучше сохранить его, чтобы мы могли понять контекст этого бота.

Opportunist

Оппортунист часто посещает тот же оружейный клуб, что и GunClubPlayers, однако он поспорил с новичком, что он может победить каждого GunClubPlayers. Поэтому он использует привычку, которую давно заметил, и заставляет себя не стрелять, а лишь немного подождать, чтобы победить.

#ifndef __OPPORTUNIST_PLAYER_HPP__
#define __OPPORTUNIST_PLAYER_HPP__

#include <string>
#include <vector>

class OpportunistPlayer final: public Player
{
public:
    OpportunistPlayer(size_t opponent = -1) : Player(opponent) {}

public:
    virtual Action fight()
    {
        switch (getTurn() % 3)
        {
        case 0:
            return load();
            break;
        case 1:
            return metal();
            break;
        case 2:
            return bullet();
            break;
        }
        return plasma();
    }
};
#endif // !__OPPORTUNIST_PLAYER_HPP__

7

BarricadePlayer

Игрок баррикады заряжает пулю первым раундом, затем держит подходящий щит (все еще немного случайный). Он также заряжает еще один выстрел каждый 5-й раунд. Каждый раунд с 15% -ной вероятностью игнорировать алгоритм (за исключением перезарядки в первый ход) и выстрелить в пулю. Когда у противника нет боеприпасов, он заряжается. Если как-то все пойдет не так, о мальчик, он просто стреляет.

Новейшие изменения:

Улучшены случайные числа (спасибо Frenzy Li).

// BarricadePlayer by devRicher
// PPCG: http://codegolf.stackexchange.com/a/104909/11933

// BarricadePlayer.hpp
// A very tactical player.

#ifndef __BARRICADE_PLAYER_HPP__
#define __BARRICADE_PLAYER_HPP__

#include "Player.hpp"
#include <cstdlib>
#include <ctime>

class BarricadePlayer final : public Player
{
public:
    BarricadePlayer(size_t opponent = -1) : Player(opponent) {}

public:
    virtual Action fight()
    {
        srand(time(NULL));
        if (getTurn() == 0) { return load(); }
        int r = GetRandomInteger(99) + 1; //Get a random
        if ((r <= 15) && (getAmmo() > 0)) { return bullet(); } //Override any action, and just shoot
        else
        {
            if (getTurn() % 5 == 0) //Every first and fifth turn
                return load();
            if (getAmmoOpponent() == 1) return metal();
            if (getAmmoOpponent() > 1) { return r <= 50 ? metal() : thermal(); }
            if (getAmmoOpponent() == 0) return load();

        }
        return bullet();
    }
};

#endif // !__BARRICADE_PLAYER_HPP__

1
Хочешь хотя бы проверить, есть ли патроны перед стрельбой?
Павел

8
Я живу опасной жизнью. @Pavel
devRicher

1
Не бессмысленно ли использовать тепловой дефлектор на втором повороте? Вы не можете загрузить две пули в первый ход. Я думаю, что даже если вы хотите, чтобы он был случайным, вам следует избегать использования теплового щита, если пули противника равны 1 (или меньше).
Левша Hare

1
Спасибо за все предложения, я много редактировал класс. @SouthpawHare
devRicher

2
Это getAmmoOpponentне так getOpponentAmmo. Вы также пропускаете#endif // !__BARRICADE_PLAYER_HPP__
Blue

7

StudiousPlayer

Студийный игрок изучает свою добычу, моделируя каждого противника, с которым он сталкивается. Этот игрок начинает с базовой стратегии, выбираемой случайным образом, и переходит к простым адаптивным стратегиям, основанным на частых измерениях реакции противника. Он использует простую модель противников, основанную на том, как они реагируют на комбинации патронов.

#ifndef __STUDIOUS_PLAYER_H__
#define __STUDIOUS_PLAYER_H__

#include "Player.hpp"
#include <unordered_map>

class StudiousPlayer final : public Player
{
public:
   using Player::GetRandomInteger;
   // Represents an opponent's action for a specific state.
   struct OpponentAction {
      OpponentAction(){}
      unsigned l=0;
      unsigned b=0;
      unsigned p=0;
      unsigned m=0;
      unsigned t=0;
   };
   // StudiousPlayer models every opponent that it plays,
   // and factors said model into its decisions.
   //
   // There are 16 states, corresponding to
   // 4 inner states (0,1,2,3) and 4 outer states
   // (0,1,2,3). The inner states represent our
   // (SP's) ammo; the outer represents the
   // Opponent's ammo.  For the inner or outer
   // states, 0-2 represent the exact ammo; and
   // 3 represents "3 or more".
   //
   // State n is (4*outer)+inner.
   //
   // State 0 itself is ignored, since we don't care
   // what action the opponent takes (we always load);
   // thus, it's not represented here.
   //
   // os stores states 1 through 15 (index 0 through 14).
   struct Opponent {
      std::vector<OpponentAction> os;
      Opponent() : os(15) {}
   };
   StudiousPlayer(size_t opponent)
      : Player(opponent)
      , strat(storedLs()[opponent])
      , ammoOpponent()
   {
   }
   Player::Action fight() {
      // Compute the current "ammo state".
      // For convenience here (aka, readability in switch),
      // this is a two digit octal number.  The lso is the
      // inner state, and the mso the outer state.
      unsigned ss,os;
      switch (ammoOpponent) {
      default: os=030; break;
      case 2 : os=020; break;
      case 1 : os=010; break;
      case 0 : os=000; break;
      }
      switch (getAmmo()) {
      default: ss=003; break;
      case 2 : ss=002; break;
      case 1 : ss=001; break;
      case 0 : ss=000; break;
      }
      // Store the ammo state.  This has a side effect
      // of causing actn() to return an OpponentAction
      // struct, with the opponent's history during this
      // state.
      osa = os+ss;
      // Get the opponent action pointer
      const OpponentAction* a=actn(osa);
      // If there's no such action structure, assume
      // we're just supposed to load.
      if (!a) return load();
      // Apply ammo-state based strategies:
      switch (osa) {
      case 001:
         // If opponent's likely to load, shoot; else load
         if (a->l > a->m) return bullet();
         return load();
      case 002:
      case 003:
         // Shoot in the way most likely to kill (or randomly)
         if (a->t > a->m+a->l) return bullet();
         if (a->m > a->t+a->l) return plasma();
         if (GetRandomInteger(1)) return bullet();
         return plasma();
      case 010:
         // If opponent tends to load, load; else defend
         if (a->l > a->b) return load();
         return metal();
      case 011:
         // Shoot if opponent tends to load
         if (a->l > a->b+a->m) return bullet();
         // Defend if opponent tends to shoot
         if (a->b > a->l+a->m) return metal();
         // Load if opponent tends to defend
         if (a->m > a->b+a->l) return load();
         // Otherwise randomly respond
         if (!GetRandomInteger(2)) return metal();
         if (!GetRandomInteger(1)) return load(); 
         return bullet();                         
      case 012:
      case 013:
         // If opponent most often shoots, defend
         if (a->b > a->l+a->m+a->t) return metal();
         // If opponent most often thermals, use bullet
         if (a->t > a->m) return bullet();
         // If opponent most often metals, use plasma
         if (a->m > a->t) return plasma();
         // Otherwise use a random weapon
         return (GetRandomInteger(1))?bullet():plasma();
      case 020:
         // If opponent most often loads or defends, load
         if (a->l+a->m+a->t > a->b+a->p) return load();
         // If opponent most often shoots bullets, raise metal
         if (a->b > a->p) return metal();
         // If opponent most often shoots plasma, raise thermal
         if (a->p > a->b) return thermal();
         // Otherwise raise random defense
         return (GetRandomInteger(1))?metal():thermal();
      case 021:
      case 031:
         // If opponent loads more often than not,
         if (a->l > a->m+a->b+a->p) {
            // Tend to shoot (67%), but possibly load (33%)
            return (GetRandomInteger(2))?bullet():load();
         }
         // If opponent metals more often than loads or shoots, load
         if (a->m > a->l+a->b+a->p) return load();
         // If opponent thermals (shrug) more often than loads or shoots, load
         if (a->t > a->l+a->b+a->p) return load();
         // If opponent tends to shoot bullets, raise metal
         if (a->b > a->p) return metal();
         // If opponent tends to shoot plasma, raise thermal
         if (a->p > a->b) return thermal();
         // Raise random shield
         return (GetRandomInteger(2))?metal():thermal();
      case 022:
         // If opponent loads or thermals more often than not, shoot bullet
         if (a->l+a->t > a->b+a->p+a->m) return bullet();
         // If opponent loads or metals more often than not, shoot plasma
         if (a->l+a->m > a->b+a->p+a->t) return plasma();
         // If opponent shoots more than loads or defends, defend
         if (a->b+a->p > a->l+a->m+a->t) {
            if (a->b > a->p) return metal();
            if (a->p > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // If opponent defends more than opponent shoots, load
         if (a->m+a->t > a->b+a->p) return load();
         // Use random substrategy;
         // load(33%)
         if (GetRandomInteger(2)) return load();
         // defend(33%)
         if (GetRandomInteger(1)) {
            if (a->b > a->p) return metal();
            if (a->b > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // Shoot in a way that most often kills (or randomly)
         if (a->m > a->t) return plasma();
         if (a->t > a->m) return bullet();
         return (GetRandomInteger(1))?bullet():plasma();
      case 023:
         // If opponent loads or raises thermal more often than not, shoot bullets
         if (a->l+a->t > a->b+a->p+a->m) return bullet();
         // If opponent loads or raises metal more often than not, shoot plasma
         if (a->l+a->m > a->b+a->p+a->t) return plasma();
         // If opponent shoots more than loads or defends, defend
         if (a->b+a->p > a->l+a->m+a->t) {
            if (a->b > a->p) return metal();
            if (a->p > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // If opponent defends more than shoots, shoot
         if (a->m+a->t > a->b+a->p) {
            if (a->m > a->t) return plasma();
            if (a->t > a->m) return bullet();
            return GetRandomInteger(1)?bullet():plasma();
         }
         // 50% defend
         if (GetRandomInteger(1)) {
            if (a->b > a->p) return metal();
            return thermal();
         }
         // 50% shoot
         if (a->m > a->t) return plasma();
         if (a->t > a->m) return bullet();
         return (GetRandomInteger(1))?bullet():plasma();
      case 030:
         // If opponent loads or shields more often than not, load
         if (a->l+a->m+a->t > a->b+a->p) return load();
         // If opponent tends to shoot, defend
         if (a->b+a->p >= a->l+a->m+a->t) {
            if (a->b > a->p) return metal();
            if (a->p > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // Otherwise, randomly shield (50%) or load
         if (GetRandomInteger(1)) {
            return (GetRandomInteger(1))?metal():thermal();
         }
         return load();
      case 032:
         // If opponent loads or thermals more often than not, shoot bullets
         if (a->l+a->t > a->b+a->p+a->m) return bullet();
         // If opponent loads or metals more often than not, shoot plasma
         if (a->l+a->m > a->b+a->p+a->t) return plasma();
         // If opponent shoots more often than loads or shields, defend
         if (a->b+a->p > a->l+a->m+a->t) {
            if (a->b > a->p) return metal();
            if (a->p > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // If opponent shields more often than shoots, load
         if (a->m+a->t > a->b+a->p) return load();
         // Otherwise use random strategy
         if (GetRandomInteger(2)) return load();
         if (GetRandomInteger(1)) {
            if (a->b > a->p) return metal();
            return thermal();
         }
         if (a->m > a->t) return plasma();
         if (a->t > a->m) return bullet();
         return (GetRandomInteger(1))?bullet():plasma();
      case 033:
         {
            // At full 3 on 3, apply random strategy
            // weighted by opponent's histogram of this state...
            // (the extra 1 weights towards plasma)
            unsigned sr=
               GetRandomInteger
               (a->l+a->t+a->p+a->b+a->m+1);
            // Shoot bullets proportional to how much
            // opponent loads or defends using thermal
            if (sr < a->l+a->t) return bullet();
            sr-=(a->l+a->t);
            // Defend with thermal proportional to how
            // much opponent attacks with plasma (tending to
            // waste his ammo)
            if (sr < a->p) return thermal();
            // Shoot plasma proportional to how
            // much opponent shoots bullets or raises metal
            return plasma();
         }
      }
      // Should never hit this; but rather than ruin everyone's fun,
      // if we do, we just load
      return load();
   }
   // Complete override; we use our opponent's model, not history.
   void perceive(Player::Action action) {
      // We want the ammo but not the history; since
      // the framework (Player::perceive) is "all or nothing", 
      // StudiousPlayer just tracks the ammo itself
      switch (action) {
      default: break;
      case Player::LOAD:   ++ammoOpponent; break;
      case Player::BULLET: --ammoOpponent; break;
      case Player::PLASMA: ammoOpponent-=2; break;
      }
      // Now we get the opponent's action based
      // on the last (incoming) ammo state
      OpponentAction* a = actn(osa);
      // ...if it's null just bail
      if (!a) return;
      // Otherwise, count the action
      switch (action) {
      case Player::LOAD    : ++a->l; break;
      case Player::BULLET  : ++a->b; break;
      case Player::PLASMA  : ++a->p; break;
      case Player::METAL   : ++a->m; break;
      case Player::THERMAL : ++a->t; break;
      }
   }
private:
   Opponent& strat;
   OpponentAction* actn(unsigned octalOsa) {
      unsigned ndx = (octalOsa%4)+4*(octalOsa/8);
      if (ndx==0) return 0;
      --ndx;
      if (ndx<15) return &strat.os[ndx];
      return 0;
   }
   unsigned osa;
   unsigned ammoOpponent;
   // Welcome, non-C++ persons, to the "Meyers style singleton".
   // "theMap" is initialized (constructed; initially empty)
   // the first time the declaration is executed.
   static std::unordered_map<size_t, Opponent>& storedLs() {
      static std::unordered_map<size_t, Opponent> theMap;
      return theMap;
   }
};

#endif

Обратите внимание, что это отслеживает информацию о противниках в соответствии с правилами вызова; см. ниже метод «синглтон в стиле Мейерса» с ограничением объема «хранится ()». (Некоторые люди задавались вопросом, как это сделать; теперь вы знаете!)


1
Я понятия не имел, что его называли синглтоном в стиле Мейерса, пока не увидел это!
Безумие Ли

1
Не принимайте термин слишком серьезно - это своего рода злоупотребление терминами, так как «singleton» - это создание шаблона, а не объявленная структура, но это та же самая техника.
H Уолтерс

6

GunClubPlayer

Вырежьте из оригинального вопроса. Это служит примером минималистической реализации производного плеера. Этот игрок будет участвовать в турнире.

В GunClubPlayerы хотел бы пойти в клуб ружья. Во время каждой дуэли они сначала загружали патроны, затем стреляли пулями и повторяли этот процесс до конца мировой дуэли. На самом деле им все равно, выиграют они или нет, и они сосредоточены исключительно на том, чтобы получить приятный опыт сами.

// GunClubPlayer.hpp
// A gun club enthusiast. Minimalistic example of derived class

#ifndef __GUN_CLUB_PLAYER_HPP__
#define __GUN_CLUB_PLAYER_HPP__

#include "Player.hpp"

class GunClubPlayer final: public Player
{
public:
    GunClubPlayer(size_t opponent = -1) : Player(opponent) {}

public:
    virtual Action fight()
    {
        return getTurn() % 2 ? bullet() : load();
    }
};

#endif // !__GUN_CLUB_PLAYER_HPP__

1
Вам не нужно остальное после оператора return, верно? Я знаю, что это не код гольф, но это не так.
Павел

2
@Pavel Ну, ладно, так ... сейчас ... вроде как в гольфе.
Безумие Ли

5

PlasmaPlayer

Плазменный плеер словно стреляет своими плазменными болтами. Он будет пытаться заряжать и стрелять как можно больше. Однако, пока у противника есть плазменные патроны, он будет использовать свой тепловой щит (пули для слабых).

#ifndef __PLASMA_PLAYER_HPP__
#define __PLASMA_PLAYER_HPP__

#include "Player.hpp"

class PlasmaPlayer final : public Player
{
public:
    PlasmaPlayer(size_t opponent = -1) : Player(opponent) {}

    virtual Action fight()
    {
        // Imma Firin Mah Lazer!
        if (getAmmo() > 1) return plasma();

        // Imma Block Yur Lazer!
        if (getAmmoOpponent() > 1) return thermal();

        // Imma need more Lazer ammo
        return load();
    }
};

#endif // !__PLASMA_PLAYER_HPP__

@FrenzyLi спасибо за конструктор! Мой C ++ немного ржавый, и у меня нет компилятора на этой машине.
Брайан Дж

Пожалуйста! Я все еще добавляю больше кода (распечатка табло, чтение внешнего сценария и т. Д.) В проект, и мне очень повезло, что ни одно из представленных материалов еще не нарушено.
Безумие Ли

Это будет хорошо работать для любого противника, кроме GunClub. Да, это убьет SadisticShooter (лучший). @BrianJ
devRicher

5

Очень SadisticShooter

Он предпочел бы смотреть, как ты страдаешь, чем убивать тебя. Он не дурак и будет прикрывать себя по мере необходимости.

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

// SadisticShooter by muddyfish
// PPCG: http://codegolf.stackexchange.com/a/104947/11933

// SadisticShooter.hpp
// A very sad person. He likes to shoot people.

#ifndef __SAD_SHOOTER_PLAYER_HPP__
#define __SAD_SHOOTER_PLAYER_HPP__

#include <cstdlib>
#include "Player.hpp"
// #include <iostream>

class SadisticShooter final : public Player
{
public:
    SadisticShooter(size_t opponent = -1) : Player(opponent) {}
private:
    bool historySame(std::vector<Action> const &history, int elements) {
        if (history.size() < elements) return false;

        std::vector<Action> lastElements(history.end() - elements, history.end());

        for (Action const &action : lastElements)
            if (action != lastElements[0]) return false;
        return true;
    }
public:
    virtual Action fight()
    {
        int my_ammo = getAmmo();
        int opponent_ammo = getAmmoOpponent();
        int turn_number = getTurn();
        //std::cout << " :: Turn " << turn_number << " ammo: " << my_ammo << " oppo: " << opponent_ammo << std::endl;

        if (turn_number == 90) {
            // Getting impatient
            return load();
        }
        if (my_ammo == 0 && opponent_ammo == 0) {
            // It would be idiotic not to load here
            return load();
        }
        if (my_ammo >= 2 && historySame(getHistoryOpponent(), 3)) {
            if (getHistoryOpponent()[turn_number - 1] == THERMAL) return bullet();
            if (getHistoryOpponent()[turn_number - 1] == METAL) return thermal();
        }
        if (my_ammo < 2 && opponent_ammo == 1) {
            // I'd rather not die thank you very much
            return metal();
        }
        if (my_ammo == 1) {
            if (opponent_ammo == 0) {
                // You think I would just shoot you?
                return load();
            }
            if (turn_number == 2) {
                return thermal();
            }
            return bullet();
        }
        if (opponent_ammo >= 2) {
            // Your plasma weapon doesn't scare me
            return thermal();
        }
        if (my_ammo >= 2) {
            // 85% more bullet per bullet
            if (turn_number == 4) return bullet();
            return plasma();
        }
        // Just load the gun already
        return load();
    }
};

#endif // !__SAD_SHOOTER_PLAYER_HPP__

Я вижу, вы исправили это.
devRicher

4

TurtlePlayer

TurtlePlayerтрус Большую часть времени он прячется за щитами - отсюда и название. Иногда он может выйти из своего снаряда (без каламбура) и сделать выстрел, но обычно он лежит низко, пока у противника есть боеприпасы.


Этот бот не особенно хорош - однако, каждому KOTH нужны некоторые начальные записи, чтобы запустить его :)

Локальное тестирование показало, что это выигрывает против обоих GunClubPlayerи Opportunist100% времени. BotRobotPlayerКазалось, битва против всегда приводила к ничьей, поскольку оба прячутся за щитами.

#include "Player.hpp"

// For randomness:
#include <ctime>
#include <cstdlib>

class TurtlePlayer final : public Player {

public:
    TurtlePlayer(size_t opponent = -1) : Player(opponent) { srand(time(0)); }

public:
    virtual Action fight() {
        if (getAmmoOpponent() > 0) {
            // Beware! Opponent has ammo!

            if (rand() % 5 == 0 && getAmmo() > 0) 
                // YOLO it:
                return getAmmo() > 1 ? plasma() : bullet();

            // Play it safe:
            if (getAmmoOpponent() == 1) return metal();
            return rand() % 2 ? metal() : thermal();
        }

        if (getAmmo() == 0) 
            // Nobody has ammo: Time to load up.
            return load();

        else if (getAmmo() > 1) 
            // We have enough ammo for a plasma: fire it!
            return plasma();

        else 
            // Either load, or take a shot.
            return rand() % 2 ? load() : bullet();
    }
};

4

DeceptivePlayer

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

// DeceiverPlayer.hpp
// If we have two shoots, better shoot one by one

#ifndef __DECEPTIVE_PLAYER_HPP__
#define __DECEPTIVE_PLAYER_HPP__

#include "Player.hpp"

class DeceptivePlayer final: public Player
{
public:
    DeceptivePlayer(size_t opponent = -1) : Player(opponent) {}

public:
    virtual Action fight()
    {
        int ammo = getAmmo();
        int opponentAmmo = getAmmoOpponent();
        int turn = getTurn();

        // Without ammo, always load
        if (ammo == 0)
        {
            return load();
        }

        // Every 10 turns the Deceiver goes crazy
        if (turn % 10 || opponentAmmo >= 3)
        {
            // Generate random integer in [0, 5)
            int random = GetRandomInteger() % 5;
            switch (random)
            {
            case 0:
                return bullet();
            case 1:
                return metal();
            case 2:
                if (ammo == 1)
                {
                    return bullet();
                }

                return plasma();
            case 3:
                return thermal();
            case 4:
                return load();
            }
        }

        // The Deceiver shoots one bullet
        if (ammo == 2)
        {
            return bullet();
        }

        // Protect until we can get bullet 2
        if (opponentAmmo == 0)
        {
            return load();
        }

        if (opponentAmmo == 1)
        {
            return metal();
        }

        if (opponentAmmo == 2)
        {
            return thermal();
        }
    }
};

#endif // !__DECEPTIVE_PLAYER_HPP__

Я не пишу код на C ++, поэтому любые улучшения кода будут приветствоваться.


Мое редактирование относится к определению по модулю и макросу. Не уверен, что вам понравится, но, может быть DeceptivePlayer, это лучшее имя?
Безумие Ли

@FrenzyLi Да, мне это нравится, я
поменяю

1
@Sxntk Мне нравится ирония, когда этот игрок ожидает, что люди с 2 боеприпасами будут стрелять плазмой, но сам будет держать два боеприпаса и стрелять пулями.
Брайан Дж

@Sxntk У вас нет возможности ничего не возвращать в данный момент. Игроку разрешено более двух патронов. Поэтому, если у вашего противника более 3 патронов, вы не предпринимаете никаких действий. Вы можете оказаться где-нибудь с взорвавшейся пушкой. (конечно, это может быть ваш генеральный план в любом случае :))
Брайан J

@BrianJ Спасибо, я подумаю об этом, а пока позволю Обманчивому сойти с ума и решу, что делать, когда у противника более 3 патронов
Sxntk

2

HanSoloPlayer

Стреляет первым! Все еще работаю над его пересмотром, но это довольно хорошо.

// HanSoloPlayer.hpp
// A reluctant rebel. Always shoots first.

// Revision 1: [13HanSoloPlayer][17] | 6 rounds | 2863

#ifndef __HAN_SOLO_PLAYER_HPP__
#define __HAN_SOLO_PLAYER_HPP__

#include "Player.hpp"

class HanSoloPlayer final: public Player
{
public:
    HanSoloPlayer(size_t opponent = -1) : Player(opponent) {}

public:
    virtual Action fight()
    {
        if(getTurn() == 0){
            // let's do some initial work
            agenda.push_back(bullet());     // action 2--han shot first!
            agenda.push_back(load());       // action 1--load a shot
        } else if(getTurn() == 2){
            randomDefensive();
        } else if(getRandomBool(2)){
            // go on the defensive about 1/3rd of the time
            randomDefensive();
        } else if(getRandomBool(5)){
            // all-out attack!
            if(getAmmo() == 0){
                // do nothing, let the agenda work its course
            } else if(getAmmo() == 1){
                // not quite all-out... :/
                agenda.push_back(load());   // overnext
                agenda.push_back(bullet()); // next
            } else if(getAmmo() == 2){
                agenda.push_back(load());   // overnext
                agenda.push_back(plasma()); // next
            } else {
                int ammoCopy = getAmmo();
                while(ammoCopy >= 2){
                    agenda.push_back(plasma());
                    ammoCopy -= 2;
                }
            }
        }

        // execute the next item on the agenda
        if(agenda.size() > 0){
            Action nextAction = agenda.back();
            agenda.pop_back();
            return nextAction;
        } else {
            agenda.push_back(getRandomBool() ? thermal() : bullet()); // overnext
            agenda.push_back(load());                                 // next
            return load();
        }
    }
private:
    std::vector<Action> agenda;
    bool getRandomBool(int weight = 1){
        return GetRandomInteger(weight) == 0;
    }
    void randomDefensive(){
        switch(getAmmoOpponent()){
            case 0:
                // they most likely loaded and fired. load, then metal shield
                agenda.push_back(metal());  // action 4
                agenda.push_back(load());   // action 3
                break;
            case 1:
                agenda.push_back(metal());
                break;
            case 2:
                agenda.push_back(getRandomBool() ? thermal() : metal());
                break;
            default:
                agenda.push_back(getRandomBool(2) ? metal() : thermal());
                break;
        }
        return;
    }
};

#endif // !__HAN_SOLO_PLAYER_HPP__

2

CamtoPlayer

CamtoPlayer HATES рисует и вырывается из петель, независимо от того, что требуется. (кроме самоубийства)

Это моя первая программа на C ++, которая что-то делает, поэтому не судите об этом слишком сильно.

Я знаю, что это может быть лучше, но, пожалуйста, не редактируйте это.
Если вы хотите изменить код, просто прокомментируйте предложение.

#ifndef __CAMTO_HPP__
#define __CAMTO_HPP__

#include "Player.hpp"
#include <iostream>

class CamtoPlayer final : public Player
{
public:
    CamtoPlayer(size_t opponent = -1) : Player(opponent) {}
        int S = 1; // Switch between options. (like a randomness function without any randomness)
        bool ltb = false; // L.ast T.urn B.locked
        bool loop = false; // If there a loop going on.
        int histarray[10]={0,0,0,0,0,0,0,0,0,0}; // The last ten turns.
        int appears(int number) { // How many times a number appears(); in histarray, used for checking for infinite loops.
            int things = 0; // The amount of times the number appears(); is stored in things.
            for(int count = 0; count < 10; count++) { // For(every item in histarray) {if its the correct number increment thing}.
                if(histarray[count]==number) {things++;}
            }
            return things; // Return the result
        }
    virtual Action fight()
    {
        int ammo = getAmmo(); // Ammo count.
        int bad_ammo = getAmmoOpponent(); // Enemy ammo count.
        int turn = getTurn(); // Turn count.
        int pick = 0; // This turn's weapon.

        if(appears(2)>=4){loop=true;} // Simple loop detection
        if(appears(3)>=4){loop=true;} // by checking if
        if(appears(4)>=4){loop=true;} // any weapong is picked a lot
        if(appears(5)>=4){loop=true;} // except for load();

        if(ammo==0&&bad_ammo==1){pick=4;} // Block when he can shoot me.
        if(ammo==0&&bad_ammo>=2){S++;S%2?(pick=4):(pick=5);} // Block against whatever might come!
        if(ammo==0&&bad_ammo>=1&&ltb){pick=1;} // If L.ast T.urn B.locked, then reload instead.
        if(ammo==1&&bad_ammo==0){pick=2;} // Shoot when the opponent can't shoot.
        if(ammo==1&&bad_ammo==1){S++;S%2?(pick=2):(pick=4);} // No risk here.
        if(ammo==1&&bad_ammo>=2){S++;S%2?(pick=4):(pick=5);} // Block!
        if(ammo==1&&bad_ammo>=1&&ltb){pick=2;} // If ltb shoot instead.
        if(ammo>=2){S++;S%2?(pick=2):(pick=3);} // Shoot something!

        /* debugging
            std :: cout << "Turn data: turn: ";
            std :: cout << turn;
            std :: cout << " loop: ";
            std :: cout << loop;
            std :: cout << " ";
            std :: cout << "ltb: ";
            std :: cout << ltb;
            std :: cout << " ";
        */

        // Attempt to break out of the loop. (hoping there is one)
        if(ammo==0&&loop){pick=1;} // After many turns of waiting, just load();
        if(ammo==1&&bad_ammo==0&&loop){loop=false;pick=1;} // Get out of the loop by loading instead of shooting.
        if(ammo==1&&bad_ammo==1&&loop){loop=false;pick=4;} // Get out of the loop (hopefully) by blocking.
        if(ammo>=2&&loop){loop=false;S++;S%2?(pick=2):(pick=3);} // Just shoot.
        if(turn==3&&(appears(1)==2)&&(appears(2)==1)){pick=4;} // If it's just load();, shoot();, load(); then metal(); because it might be a loop.
        // End of loop breaking.

        if(turn==1){pick=2;} // Shoot right after reloading!
        if(ammo==0&&bad_ammo==0){pick=1;} // Always load when no one can shoot.

        for(int count = 0; count < 10; count++) {
            histarray[count]=histarray[count+1]; // Shift all values in histarray[] by 1.
        }
        histarray[9] = pick; // Add the picked weapon to end of histarray[].

        /*  more debugging
            std :: cout << "history: ";
            std :: cout << histarray[0];
            std :: cout << histarray[1];
            std :: cout << histarray[2];
            std :: cout << histarray[3];
            std :: cout << histarray[4];
            std :: cout << histarray[5];
            std :: cout << histarray[6];
            std :: cout << histarray[7];
            std :: cout << histarray[8];
            std :: cout << histarray[9];

            std :: cout << " pick, ammo, bammo: ";
            std :: cout << pick;
            std :: cout << " ";
            std :: cout << ammo;
            std :: cout << " ";
            std :: cout << bad_ammo;
            std :: cout << "\n";
        */
        switch(pick) {
            case 1:
                ltb = false; return load();
            case 2:
                ltb = false; return bullet();
            case 3:
                ltb = false; return plasma();
            case 4:
                ltb = true;return metal();
            case 5:
                ltb = true;return thermal();
        }

    }
};

#endif // !__CAMTO_HPP__

Вы забываете#endif // ! __CAMTO_HPP__
Blue

@muddyfish Спасибо, что сказали мне, у меня есть различные символы, которые помешали рендерингу кода! XD
Бенджамин Филипп

Все еще не появляется. Я бы порекомендовал полностью отказаться от тегов HTML и просто использовать уценку (кнопка «Образец кода» с надписью «{}»). Вручную цитировать <>&это боль.
H Уолтерс

@HWalters Спасибо за совет!
Бенджамин Филипп

Спасибо за участие. И еще одно: пожалуйста, удалите, using namespace stdпотому что это мешает турниру. Если вы хотите отладить, вы можете использовать std::coutи т.д.
Frenzy Li

1

SurvivorPlayer

Выживший игрок ведет себя так же, как черепаха и игрок с баррикадами. Он никогда не предпримет действия, которое могло бы привести к его смерти, и скорее навязал бы ничью, чем проиграл бой.

// SurvivorPlayer.hpp
// Live to fight another day

#ifndef __SURVIVOR_PLAYER_HPP__
#define __SURVIVOR_PLAYER_HPP__

#include "Player.hpp"

class SurvivorPlayer final : public Player
{
public:
SurvivorPlayer(size_t opponent = -1) : Player(opponent)
{
}

public:
    virtual Action fight()
    {
        int myAmmo = getAmmo();
        int opponentAmmo = getAmmoOpponent();
        int turn = getTurn();
        if (turn == 0) {
            return load();
        }
        switch (opponentAmmo) {
        case 0:
            if (myAmmo > 2) {
                return GetRandomInteger(1) % 2 ? bullet() : plasma();
            }
            return load();
        case 1:
            if (myAmmo > 2) {
                return plasma();
            }
            return metal();
        default:
            if (myAmmo > 2) {
                return plasma();
            }
            return GetRandomInteger(1) % 2 ? metal() : thermal();
        }
    }
};

#endif // !__SURVIVOR_PLAYER_HPP__

1

FatedPlayer

Сделано Клото, забитым Лахесисом и убитым Атропосом ; Единственная стратегия этого игрока состоит в том, чтобы использовать то, что он знает о боеприпасах, чтобы определить, какие действия являются разумными.

Тем не менее, он не может выбрать действие; эта часть оставлена ​​богам.

#ifndef __FATEDPLAYER_H__
#define __FATEDPLAYER_H__

#include "Player.hpp"
#include <functional>
class FatedPlayer final : public Player
{
public:
   FatedPlayer(size_t o) : Player(o){}
   Action fight() {
      std::vector<std::function<Action()>>c{[&]{return load();}};
      switch(getAmmo()){
      default:c.push_back([&]{return plasma();});
      case 1 :c.push_back([&]{return bullet();});
      case 0 :;}
      switch(getAmmoOpponent()){
      default:c.push_back([&]{return thermal();});
      case 1 :c.push_back([&]{return metal();});
      case 0 :;}
      return c[GetRandomInteger(c.size()-1)]();
   }
};

#endif

... потому что я хотел бы посмотреть, как ранжируется случайный игрок.


1

SpecificPlayer

SpecificPlayer следует простому плану выбора некоторых случайных (допустимых) действий. Однако его главная особенность заключается в том, что он следит за определенными ситуациями, анализируя количество боеприпасов и предыдущий ход противника.

Я впервые пишу что-нибудь на C ++, и впервые пытаюсь написать что-нибудь конкурентное для ботов. Поэтому я надеюсь, что моя скудная попытка, по крайней мере, сделает что-то интересное :)

// SpecificPlayer by Charles Jackson (Dysnomian) -- 21/01/2017
// PPCG: http://codegolf.stackexchange.com/a/104933/11933

#ifndef __SPECIFIC_PLAYER_HPP__
#define __SPECIFIC_PLAYER_HPP__

#include "Player.hpp"

class SpecificPlayer final : public Player
{
public:
    SpecificPlayer(size_t opponent = -1) : Player(opponent) {}

    //override
    virtual Action fight()
    {
        returnval = load(); //this should always be overwritten

        // if both players have no ammo we of course load
        if (oa == 0 && ma == 0) { returnval = load(); }

        // if (opponent has increased their ammo to a point they can fire something) then shield from it
        else if (oa == 1 && op == LOAD) { returnval = metal(); }
        else if (oa == 2 && op == LOAD) { returnval = thermal(); }
        else if (op == LOAD) { returnval = randomBlock(oa); }

        // if we have a master plan to follow through on do so, unless a defensive measure above is deemed necessary
        else if (nextDefined) { returnval = next; nextDefined = false; }

        // if opponent didn't fire their first shot on the second turn (turn 1) then we should block
        else if (t == 2 && oa >= 1) { returnval = randomBlock(oa); }

        //if opponent may be doing two attacks in a row
        else if (oa == 1 && op == BULLET) { returnval = metal(); }
        else if (oa == 2 && op == PLASMA) { returnval = thermal(); }

        // if we had no ammo last turn and still don't, load
        else if (ma == 0 && pa == 0) { returnval = load(); }

        // if we have just collected enough ammo to plasma, wait a turn before firing
        else if (ma == 2 && pa == 1) { 
            returnval = randomBlock(oa); next = plasma(); nextDefined = true; }

        // time for some random actions
        else
        {
            int caseval = GetRandomInteger(4) % 3; //loading is less likely than attacking or blocking
            switch (caseval) 
            {
            case 0: returnval = randomBlock(oa); break; // 40%
            case 1: returnval = randomAttack(ma); break; // 40%
            case 2: returnval = load(); break; // 20%
            }
        }

        pa = ma; //update previous ammo then update our current ammo
        switch (returnval)
        {
        case LOAD:
            ma += 1;
            break;
        case BULLET:
            ma -= 1;
            break;
        case PLASMA:
            ma -= 2;
            break;
        }
        t++; //also increment turn counter

        return returnval;
    }

    //override
     void perceive(Action action)
    {
         //record what action opponent took and update their ammo
         op = action;
         switch (action)
         {
         case LOAD:
             oa += 1;
             break;
         case BULLET:
             oa -= 1;
             break;
         case PLASMA:
             oa -= 2;
             break;
         }
    }

private:
    Action returnval; //our action to return
    Action next; //the action we want to take next turn - no matter what!
    bool nextDefined = false; //flag for if we want to be taking the "next" action.
    int t = 0; //turn number
    int ma = 0; //my ammo
    int oa = 0; //opponent ammo
    int pa = 0; //my previous ammo
    Action op; //opponent previous action

    Action randomBlock(int oa)
    {
        Action a;
        if (oa == 0) { a = load(); }
        else if (oa == 1) { a = metal(); }
        else
        {
            // more chance of ordianry block than laser block
            a = GetRandomInteger(2) % 2 ? metal() : thermal();
        }
        return a;
    }

    Action randomAttack(int ma)
    {
        Action a;
        if (ma == 0) { a = load(); }
        else if (ma == 1) { a = bullet(); }
        else
        {
            // more chance of ordianry attack than plasma
            a = GetRandomInteger(2) % 2 ? bullet() : plasma();
        }
        return a;
    }
};

#endif // !__SPECIFIC_PLAYER_HPP__

1

NotSoPatientPlayer

История его создания придет позже.

// NotSoPatientPlayer.hpp

#ifndef __NOT_SO_PATIENT_PLAYER_HPP__
#define __NOT_SO_PATIENT_PLAYER_HPP__

#include "Player.hpp"
#include <iostream>

class NotSoPatientPlayer final : public Player
{
    static const int TOTAL_PLAYERS = 50;
    static const int TOTAL_ACTIONS = 5;
    static const int MAX_TURNS = 100;
public:
    NotSoPatientPlayer(size_t opponent = -1) : Player(opponent)
    {
        this->opponent = opponent;
    }

public:
    virtual Action fight()
    {
        /*Part which is shamelessly copied from MontePlayer.*/
        int turn = getTurn(),
            ammo = getAmmo(),
            opponentAmmo = getAmmoOpponent();
        int turnsRemaining = MAX_TURNS - turn;
        //The bot starts to shoot when there is enough ammo to fire plasma at least (turnsRemaining-2) times.
        //Did you know that you cannot die when you shoot plasma?
        //Also chooses 1 or 2 move(s) in which will shoot bullet(s) or none if there is plenty of ammo.
        //Also check !burstMode because it needs to be done only once.
        if (!burstMode && ammo + 2 >= turnsRemaining * 2)
        {
            burstMode = true;
            if (!(ammo == turnsRemaining * 2)) {
                turnForBullet1 = GetRandomInteger(turnsRemaining - 1) + turn;
                if (ammo + 2 == turnsRemaining * 2) {
                    //turnForBullet1 should be excluded in range for turnForBullet2
                    turnForBullet2 = GetRandomInteger(turnsRemaining - 2) + turn;
                    if (turnForBullet2 >= turnForBullet1) turnForBullet2++;
                }
            }
        }
        if (burstMode) {
            if (turn == turnForBullet1 || turn == turnForBullet2) {
                return bullet();
            }
            else return plasma();
        }

        //if opponent defended last 3 turns, the bot tries to go with something different
        if (turn >= 3) {
            auto historyOpponent = getHistoryOpponent();
            //if opponent used metal last 3 turns
            if (METAL == historyOpponent[turn - 1] && METAL == historyOpponent[turn - 2] && METAL == historyOpponent[turn - 3]) {
                if (ammo >= 2) return plasma();
                else return load();
            }
            //if opponent used thermal last 3 turns
            if (THERMAL == historyOpponent[turn - 1] && THERMAL == historyOpponent[turn - 2] && THERMAL == historyOpponent[turn - 3]) {
                if (ammo >= 1) return bullet();
                else return load();
            }
            //if the opponent defends, but not consistently
            if ((historyOpponent[turn - 1] == METAL || historyOpponent[turn - 1] == THERMAL)
                && (historyOpponent[turn - 2] == METAL || historyOpponent[turn - 2] == THERMAL)
                && (historyOpponent[turn - 3] == METAL || historyOpponent[turn - 3] == THERMAL)) {
                if (ammo >= 2) return plasma();
                else if (ammo == 1) return bullet();
                else return load();
            }
        }

        /*else*/ {
            if (opponentAmmo == 0) return load();
            if (opponentAmmo == 1) return metal();
            //if opponent prefers bullets or plasmas, choose the appropriate defence
            if (opponentMoves[opponent][BULLET] * 2 >= opponentMoves[opponent][PLASMA]) return metal();
            else return thermal();
        }
    }

    virtual void perceive(Action action)
    {
        Player::perceive(action);
        opponentMoves[opponent][action]++;
    }

    /*virtual void declared(Result result)
    {
        currentRoundResults[opponent][result]++;
        totalResults[opponent][result]++;
        int duels = 0;
        for (int i = 0; i < 3; i++) duels += currentRoundResults[opponent][i];
        if (duels == 100) {
            std::cout << "Score against P" << opponent << ": " <<
                currentRoundResults[opponent][WIN] << "-" << currentRoundResults[opponent][DRAW] << "-" << currentRoundResults[opponent][LOSS] << "\n";
            for (int i = 0; i < 3; i++) currentRoundResults[opponent][i] = 0;
        }
    };*/

private:
    static long opponentMoves[TOTAL_PLAYERS][TOTAL_ACTIONS];
    int opponent;
    //When it becomes true, the bot starts shooting.
    bool burstMode = false;
    //turnForBullet1 and turnForBullet2,
    //the 2 turns in which the bot will shoot bullets
    int turnForBullet1 = -1, turnForBullet2 = -1;
    //For debugging purposes
    //Reminder: enum Result { DRAW, WIN, LOSS };
    static int currentRoundResults[TOTAL_PLAYERS][3], totalResults[TOTAL_PLAYERS][3];
};
long NotSoPatientPlayer::opponentMoves[TOTAL_PLAYERS][TOTAL_ACTIONS] = { { 0 } };
int NotSoPatientPlayer::currentRoundResults[TOTAL_PLAYERS][3] = { { 0 } };
int NotSoPatientPlayer::totalResults[TOTAL_PLAYERS][3] = { { 0 } };
#endif // !__NOT_SO_PATIENT_PLAYER_HPP__

«История его создания придет позже», прошло уже более 3 месяцев :)
HyperNeutrino
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.