Футуристичний поєдинок з гарматами


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клас.
  • Non C ++ записи: виберіть один інтерфейс в розділі Інтерфейс для подання Non-C ++ .
  • На даний момент дозволені мови, що не містять мови C ++: Python 3, Java.

Поєдинок

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

Гравець програє дуель гармати, якщо вони

  • Можливо , НЕ використовувати металевий екран для захисту вхідної кулі.
  • Можливо , НЕ використовуйте теплової дефлектор , щоб захистити входить плазму.
  • Стріляйте з пістолета, не завантажуючи достатньо боєприпасів, в якому їхнє пістолет самовибухне та вб'є власника.

Коваджі

Відповідно до Посібника для власників футуристичних гармат :

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

Також примітно, що:

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

Тому в цілому є 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

Згідно з правилами вище, я програв. Ви бачите, чому? Це тому, що я вистрілив остаточний промінь плазми, коли в мене було лише 1 завантажений боєприпас, внаслідок чого мій пістолет вибухнув.


Програвач C ++

Ви , як цивілізований футуристичний програміст, не будете безпосередньо керувати зброєю. Натомість ви кодуєте 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.

Ви можете коментувати, щоб попросити роз'яснення або помітити лазівки. Сподіваємось, вам сподобається цей футуристичний поєдинок з гарматами та бажаю вам щасливого нового року!

Уточнення

  • Вам дозволяється рандомізована поведінка.
  • Недійсні дії (стрілянина при навантаженні боєприпасів недостатня) дозволені.
  • Якщо гравець зробить недійсний вхід, його пістолет вибухне негайно.
  • Вам дозволяється вивчити відповіді.
  • Вам явно дозволяється фіксувати поведінку суперника в рамках кожного турніру.
  • Кожен раунд ви будете грати по 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

(Кредит H Walters)

Подання буде проведено один раз на турнір.

Існує фіксована вимога до всіх записів щодо того, як робити введення-виведення, оскільки і 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 Walters

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

The BlackHatPlayer

Гравець BlackHat знає, що кулі та щити - це минуле; фактичні війни виграють ті, хто може зламати програми противника.

Отже, він надягає нерухомий металевий щит і починає робити свою справу.

Перший раз, коли його просять fight, він намагається локалізувати свого ворога в пам'яті. Враховуючи структуру бойової арени, майже впевнений, що компілятор в кінцевому підсумку поставить свою адресу (загорнуту в антену unique_ptr), а супротивник - лише один поруч.

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

Якщо йому вдасться його знайти, він висмоктує свої мізки і замінює їх на ідіот-підступник. На практиці це робиться шляхом заміни вказівника противника на vtable на адресу Idiotvtable - німого гравця, який завжди стріляє.

Якщо все це вдасться (і в моїх тестах - gcc 6 на Linux 64 біт, MinGW 4.8 на вині 32 біт - це працює досить надійно), війна виграна. Що б суперник робив у першому раунді, не важливо - в гіршому випадку він стріляв по нас, і ми мали металевий щит.

Відтепер у нас ідіот просто стріляє; у нас завжди є щит, тому ми захищені, і він підірветься за 1–3 раунди (залежно від того, що робив оригінальний бот у своєму першому fightдзвінку).


Зараз: я майже впевнений, що це слід негайно дискваліфікувати, але смішно, що я не порушую явно жодне з вищезазначених правил:

Чого НЕ треба робити

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

BlackHat не намагається розпізнати опонента - насправді абсолютно неважливо, хто такий противник, враховуючи, що його мозок замінюється негайно.

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

Все відбувається локально у 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 це оригінально (принаймні, я ніколи нічого подібного тут не бачив) і пристойно цікавий, інженерно-розумний; тому я поділився цим тут.
Маттео Італія

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 інших ботів , його ...

The BotRobot

BotRobot був названий, навчений і побудований автоматично за допомогою дуже базового генетичного алгоритму.

Дві команди з 9 були створені проти кожного, у кожному поколінні кожен робот з команди 1 протиставляється кожному роботу команди 2. Роботи з більшою кількістю перемог, ніж втрат зберегли пам’ять, інша повернулася назад до останнього кроку , і мав шанс щось забути, сподіваюся, поганого. Самі боти - це прославлені таблиці пошуку, де, якби вони знайшли щось, чого раніше не бачили, просто вибрали б випадковий дійсний варіант і збережуть його в пам'яті. Версія C ++ цього не робить, вона повинна була навчитися . Як було сказано раніше, виграючі боти зберігають цю нову знайдену пам’ять, наскільки явно вона працювала. Втратити ботів не варто, і продовжуйте те, з чого вони почали.

Зрештою, бот-бої були досить близькими, рідко сталевими. Переможець був вибраний з пулу двох команд після еволюції, який налічував 100000 поколінь.

BotRobot з його випадково генерованим та КРАСИМИм ім'ям був щасливчиком.

Генератор

бот.луа

Ревізія: Хоча робот був досить розумний проти себе та інших аналогічних генерованих роботів, він виявився досить марним у фактичних битвах. Отже, я регенерував його мозок проти деяких вже створених ботів.

Результати, як легко видно, - це набагато складніший мозок, з варіантами до того, як противник має 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 Не впевнений, як я цього не помітив, виправляючи це зараз.
Атако

Ну, після цього оновлення, бот, здається, має фіксований отвір 00.
Frenzy Li

Я бачу, чому зараз ... "1: 1" дає "0".
Шаленство Лі

1
кілька гравців тут зафіксували всю свою гру , засновану на поворотах, так що я не думаю , що фіксоване отвір повинен бути проблемою
ейс

10

CBetaPlayer (cβ)

Орієнтовна рівновага Неша.

Цей бот - просто фантазія з обгорткою коду.

Ми можемо переосмислити це як проблему теорії ігор. Позначте виграш на +1, а програш - на -1. Тепер нехай B (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 або більше патронів окремо, оскільки кожен відкриває різні варіанти. Також я подумав, що геометрична функція виживання буде найбільш підходящою, тому що оборонний гравець, по суті, здогадується, який хід зробити. Я подумав, що 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.
Frenzy Li

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

Не хотіли б ви додати трохи більше інформації про плеєр, наприклад, як ви досягли ймовірності ... тензор?
Frenzy Li

2
Я люблю цього бота. Я думаю, що СП має перевагу поки що лише завдяки детермінованості інших записів; чим більше (неоптимально зважених) додаються випадкові боти, тим кращі ціни на CBP. Це підкріплюється тестуванням; у моїх внутрішніх тестах із звичайними підозрюваними СП завжди перемагає з CBP другим ... однак, у міні-змаганні з участю CBP, SP та FP, CBP випереджає 55% часу, при цьому SP та FP роблять рівномірно.
H Walters

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

8

Мені бракує коментарів скрізь правильно, тому я ще не можу задавати свої запитання. Отже, це дуже базовий гравець, який повинен виграти проти першого бота.

[Редагувати] Дякую, тепер попередній статус вже не відповідає дійсності, але я думаю, що краще зберегти його, щоб ми могли зрозуміти контекст цього бота.

The 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

The 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 (або менше).
Заєць

1
Дякую за всі пропозиції, я багато редагував клас. @SouthpawHare
devRicher

2
Це getAmmoOpponentне так getOpponentAmmo. Ви також пропали#endif // !__BARRICADE_PLAYER_HPP__
Синій

7

The 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
Я не мав ідеї, що це називається одинарним стилем Майєрса, поки я не побачив цього!
Frenzy Li

1
Не сприймайте термін занадто серйозно - це свого роду зловживання термінами, оскільки "синглтон" - це шаблон інстанції, а не декларована структура, але це та сама техніка.
H Walters

6

The 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
Інше вам не потрібно після повернення заяви, правда? Я знаю, що це не код гольфу, але він почуває себе неправильно.
Павло

2
@Pavel Ну, гаразд, так ... це ... щось на зразок гольфу зараз.
Шаленство Лі

5

The 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 ++ трохи іржавий, і у мене немає компілятора на цій машині.
Брайан J

Ласкаво просимо! Я все ще додаю в проект більше коду (табло для друку, читання зовнішнього сценарію тощо), і мені дуже пощастило, що жодне з матеріалів ще не порушено.
Frenzy Li

Це добре підійде для будь-якого суперника, крім 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

The TurtlePlayer

TurtlePlayer- боягуз. Він проводить більшу частину часу, ховаючись за своїми щитами - звідси і назва. Іноді він може вийти зі своєї снаряда (каламбур не призначений) і постріляти, але він зазвичай лежить низько, поки у ворога є боєприпаси.


Цей бот не особливо великий - однак, кожен КОТИН потребує деяких початкових записів, щоб запустити його :)

Місцеве тестування виявило, що це виграє як проти, так 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

The 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 Так, мені це подобається, я зміню ім'я
Sxntk

1
@Sxntk Мені подобається іронія, де цей гравець очікує, що люди з двома боєприпасами стрілятимуть у плазму, але сам триматиме два боєприпаси та стрілятиме кулею.
Брайан J

@Sxntk У вас немає можливості нічого не повернути наразі. У гравця дозволено більше двох патронів. Тож якщо у вашого суперника є 3+ боєприпасів, ви не вживаєте жодних дій. Можливо, ви десь згорнетесь із підірваним пістолетом. (звичайно, це може бути ваш генеральний план у будь-якому випадку :))
Брайан J

@BrianJ Дякую, я подумаю про це, тим часом я дозволю Обманщику зійти з розуму і вирішувати, що робити, коли у oponnent є 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

The 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__
Синій

@muddyfish Дякую за те, що ти сказав мені, що у мене є різні менш символи, які зупинили показ коду! XD
Бенджамін Філіп

Ще не з’являється. Я рекомендую взагалі скинути теги HTML і просто використовувати розмітку (кнопка "Зразок коду", на якій є "{}"). Цитування вручну <>&- це біль.
H Walters

@HWalters Дякую за пораду!
Бенджамін Філіп

Дякую за участь І одне: будь ласка, видаліть, using namespace stdоскільки це стосується турніру. Якщо ви хочете налагоджувати, ви можете скористатися std::coutі т. Д.
Frenzy Li

1

The SurvivorPlayer

Гравець "Survivor" поводиться у подібному руслі, як гравець Черепаха та барикад. Він ніколи не вживатиме дій, які могли б призвести до його смерті і скоріше примусово здійснить жеребкування, ніж програти бій.

// 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

The 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.