Das futuristische Waffenduell

73

Der Hintergrund der Zukunft

Im Jahr 2017 werden Sie und Ihr Gegner in einem futuristischen Feuergefecht gegeneinander antreten, in dem nur einer überleben kann. Hast du genug Erfahrung, um deinen Gegner zu besiegen? Jetzt ist die Zeit , um Ihre polieren Pistolen Fähigkeiten in Ihrer bevorzugten Programmiersprache und den Kampf gegen alle Widrigkeiten!

Turnierergebnisse

Dieses Turnier auf dem UTC Morgen des 2. Feburar beendet nd 2017. Dank unsere Wettbewerber haben wir ein spannendes futuristisches Turnier haben!

MontePlayer ist der endgültige Gewinner nach einem engen Kampf mit CBetaPlayer und StudiousPlayer. Die drei besten Guen-Duelle haben ein Erinnerungsfoto gemacht:

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

Glückwunsch an die Gewinner! Eine detaillierte Bestenliste ist am Ende dieses Beitrags zu sehen.

Allgemeine Anleitung

  • Besuchen Sie das offizielle Repository für den in diesem Turnier verwendeten Quellcode.
  • C ++ - Einträge: Bitte erben Sie die PlayerKlasse.
  • Nicht-C ++ - Einträge: Wählen Sie eine Schnittstelle im Abschnitt Schnittstelle für Nicht-C ++ - Übermittlungen aus .
  • Derzeit erlaubte Nicht-C ++ - Sprachen: Python 3, Java.

Das Duell

  • Jeder Spieler beginnt mit einer unbeladenen Waffe, die unendlich viel Munition laden kann.
  • In jeder Runde wählen die Spieler gleichzeitig eine der folgenden Aktionen aus:
    • 0 - Laden Sie 1 Munition in die Waffe.
    • 1- Feuern Sie eine Kugel auf den Gegner ab. kostet 1 geladene Munition.
    • 2- Feuern Sie einen Plasmastrahl auf den Gegner ab. kostet 2 geladene Munition.
    • - - Verteidigen Sie die ankommende Kugel mit einem Metallschild.
    • = - Eingehenden Plasmastrahl mit einem thermischen Deflektor abwehren.
  • Wenn beide Spieler überleben nach dem 100 - ten wiederum beide Auspuff zu Tode, was dazu führt , ein Unentschieden .

Ein Spieler verliert das Waffenduell, wenn sie

  • Hat nicht das Metallschild verwenden , um eine eingehende Kugel zu verteidigen.
  • Habe nicht den thermischen Deflektor verwenden , um einen eingehenden Plasma zu verteidigen.
  • Feuern Sie eine Waffe ab, ohne genügend Munition zu laden, in der sich die Waffe selbst explodiert und den Besitzer tötet.

Vorbehalte

Nach dem Handbuch für futuristische Waffenbesitzer :

  • Ein Metallschild KANN NICHT vor einfallendem Plasmastrahl verteidigen. Ebenso kann ein thermischer Deflektor NICHT gegen einfallende Kugeln verteidigen.
  • Der Plasmastrahl überwältigt die Kugel (weil die erstere mehr geladene Munition erfordert). Wenn also ein Spieler mit einem Plasmastrahl auf den Gegner schießt, der im selben Zug eine Kugel abgefeuert hat, wird der Gegner getötet.
  • Wenn beide Spieler im selben Zug eine Kugel aufeinander abfeuern, brechen die Kugeln ab und beide Spieler überleben. Wenn sich beide Spieler im selben Zug mit einem Plasmastrahl beschießen, überleben beide Spieler.

Es ist auch bemerkenswert, dass:

  • Du wirst die Aktion deines Gegners in einer Runde NICHT kennen, bis sie endet.
  • Das Ablenken von Plasmastrahlen und Abschirmen von Kugeln schadet Ihrem Gegner NICHT .

Daher gibt es insgesamt 25 gültige Aktionskombinationen pro Runde:

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

Beispiel Duell

Hier ist ein Duell, das ich einmal mit einem Freund hatte. Damals wussten wir nicht viel über das Programmieren, deshalb verwendeten wir Handgesten und signalisierten mit einer Geschwindigkeit von zwei Umdrehungen pro Sekunde. Von links nach rechts waren unsere Aktionen wiederum:

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

Nach den obigen Regeln habe ich verloren. Siehst du warum Das liegt daran, dass ich den letzten Plasmastrahl abgefeuert habe, als ich nur 1 geladene Munition hatte, wodurch meine Waffe explodierte.


Der C ++ Player

Sie , als zivilisierter futuristischer Programmierer, nicht direkt die Waffen handhaben . Stattdessen codierst du ein Player, das gegen andere kämpft. Indem Sie die Klasse im GitHub-Projekt öffentlich erben , können Sie mit dem Schreiben Ihrer urbanen Legende beginnen.

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

Was du tun musst oder kannst

  • Sie müssen PlayerKlasse durch öffentliche Vererbung erben und Ihre Klasse für endgültig erklären.
  • Sie müssen überschreiben Player::fight, was bei Player::Actionjedem Aufruf eine gültige zurückgibt .
  • Optional können Sie die Aktionen Ihres Gegners überschreiben Player::perceiveund Player::declaredim Auge behalten und Ihre Siege im Auge behalten.
  • Verwenden Sie optional private statische Member und Methoden in Ihrer abgeleiteten Klasse, um komplexere Berechnungen durchzuführen.
  • Verwenden Sie optional andere C ++ - Standardbibliotheken.

Was du NICHT tun darfst

  • Sie dürfen KEINE andere direkte Methode anwenden, um Ihren Gegner zu erkennen, als die angegebene Gegner-ID, die zu Beginn jedes Turniers gemischt wird. Sie dürfen nur raten, wer ein Spieler während eines Turniers ist.
  • Sie dürfen KEINE Methoden in einer PlayerKlasse überschreiben , die nicht als virtuell deklariert sind.
  • Sie dürfen KEINE Elemente im globalen Bereich deklarieren oder initialisieren.
  • Seit dem Debüt von (jetzt disqualifiziert) dürfen die BlackHatPlayerSpieler den Zustand Ihres Gegners NICHT einsehen oder ändern.

Ein Beispielduell

Der Prozess eines Waffenduells wird mit der GunDuelKlasse durchgeführt. Ein Beispiel für einen Kampf finden Sie Source.cppim Abschnitt Initiieren eines Duells .

Wir präsentieren GunClubPlayer, HumanPlayerund die GunDuelKlasse, die in dem gefunden werden kann Tournament\Verzeichnis des Repository.

In jedem Duell GunClubPlayerwird eine Kugel geladen; feuere es ab; spülen und wiederholen. HumanPlayerFordert Sie in jedem Zug auf, eine Aktion gegen Ihren Gegner auszuführen. Ihre Tastatur - Steuerung sind die Zeichen 0, 1, 2, -und =. Unter Windows können Sie HumanPlayerIhre Übermittlung mit debuggen.

Ein Duell einleiten

Auf diese Weise können Sie Ihren Player über die Konsole debuggen.

// 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();
}

Beispielspiele

Die geringste Anzahl an Runden, die du besiegen musst, GunClubPlayerist 3. Hier ist die Wiederholung des Spiels 0-1gegen GunClubPlayer. Die Zahl in der Klammer ist die Anzahl geladener Munition für jeden Spieler, wenn der Zug endet.

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

Der schnellste Weg, um besiegt zu werden, GunClubPlayerohne ungültige Züge zu machen, ist die Sequenz 0=, da die Kugel direkt durch den thermischen Deflektor schießt. Die Wiederholung ist

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

Der Wettbewerb

Das Turnier folgt dem Format "Last Player Standing". In einem Turnier werden alle gültigen Einsendungen (einschließlich der GunClubPlayer) in einen Pool gestellt. Jeder Einreichung wird eine zufällige und dennoch eindeutige Kennung zugewiesen, die während des gesamten Turniers gleich bleibt. Während jeder Runde:

  • Jede Einsendung beginnt mit 0 Punkten und spielt 100 Duelle gegen jede andere Einsendung.
  • Jedes siegreiche Duell gewährt 1 Punkt; Ziehen und Verlieren geben 0 Punkte.
  • Am Ende der Runde verlassen Einsendungen mit den Mindestpunkten das Turnier. Bei einem Unentschieden verlässt der Spieler, der seit Beginn des Turniers am wenigsten Punkte gesammelt hat, das Turnier.
  • Wenn mehr als ein Spieler übrig ist, beginnt die nächste Runde.
  • Punkte werden NICHT in die nächste Runde übertragen.

Einreichung

Sie senden einen Spieler pro Antwort. Sie können mehrere Dateien für einen Player einreichen, sofern diese andere Einreichungen NICHT beeinträchtigen. Um die Dinge am Laufen zu halten, bitte:

  • Nennen Sie Ihre Haupt-Header-Datei als <Custom>Player.hpp,
  • Benennen Sie Ihre anderen Dateien wie <Custom>Player*.*z. MyLittlePlayer.txtB. Ihren Klassennamen MyLittlePlayeroder EmoPlayerHates.cppIhren Klassennamen EmoPlayer.
  • Wenn Ihr Name Shooteroder ähnliche Wörter enthält , die in den Kontext dieses Turniers passen, müssen Sie diese Playeram Ende nicht hinzufügen . Wenn Sie der Meinung sind, dass Ihr Einreichungsname ohne das Suffix besser funktioniert Player, müssen Sie auch nichts hinzufügen Player.
  • Stellen Sie sicher, dass Ihr Code unter Windows kompiliert und verlinkt werden kann.

Sie können einen Kommentar abgeben, um Klarheit zu erlangen oder Lücken zu finden. Hoffe, Sie genießen dieses futuristische Waffenduell und wünschen Ihnen ein frohes neues Jahr!

Klärung

  • Sie dürfen zufälliges Verhalten haben.
  • Ungültige Aktionen (Abfeuern, wenn geladene Munition nicht ausreicht) sind zulässig.
  • Wenn ein Spieler eine ungültige Eingabe macht, explodiert seine Waffe sofort.
  • Sie dürfen die Antworten studieren.
  • Es ist Ihnen ausdrücklich gestattet , das Verhalten des Gegners in jedem Turnier aufzuzeichnen.
  • In jeder Runde spielen Sie 100 Duelle gegen jeden Gegner. Die Reihenfolge der 100 Duelle ist jedoch zufällig - es ist nicht garantiert, dass Sie gegen denselben Gegner 100 Duelle hintereinander kämpfen.

Zusätzliche Ressourcen

@flawr hat die bereitgestellte C ++ - Quelle als Referenz in Java übersetzt, wenn Sie C ++ - Einträge senden möchten.

Schnittstelle für Nicht-C ++ - Einreichungen

Derzeit akzeptiert: Python 3, Java.

Bitte befolgen Sie eine der folgenden Spezifikationen:

Schnittstellenspezifikation 1: Beendigungscode

Ihre Einreichung wird einmal pro Spielzug ausgeführt.

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.

Sie können Ihre Einreichung in PythonPlayer\und JavaPlayer\Verzeichnissen testen .

Schnittstellenspezifikation 2: stdin / stdout

(Dank an H Walters)

Ihre Einreichung wird einmal pro Turnier ausgeführt.

Es gibt eine feste Anforderung für alle Eingaben zur I / O-Ausführung, da sowohl stdin als auch stdout mit dem Turniertreiber verbunden sind. Ein Verstoß kann zu einem Deadlock führen. Alle Einträge MÜSSEN diesem EXAKTEN Algorithmus (in Pseudocode) folgen :

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

Hier ist F eine der 0, 1, 2, -, oder =für load / bullet / plasma / metal / thermal. PROZESS bedeutet, optional auf das zu antworten, was Ihr Gegner getan hat (einschließlich der Verfolgung der Munition Ihres Gegners, wenn Sie dies tun). Beachten Sie, dass die Aktion des Gegners auch eine von '0', '1', '2', '-' oder '=' ist und sich im zweiten Zeichen befindet.

Abschließender Anzeiger

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 |

Das Turnier dauert bis zum 1. Februar 2017, sofern nicht anders angegeben.

Raserei Li
quelle
15
Beeindruckende erste Herausforderung übrigens!
Martin Ender
3
Wenn Sie bereit sind, andere Sprachen auszuführen, können Sie eine PlayerImplementierung zulassen, die einen anderen Prozess aufruft, um die aktuelle Runde zu berechnen. Auf diese Weise können Benutzer an jeder Sprache teilnehmen, die Sie gerne auf Ihrem Computer ausführen.
Martin Ender
5
Zufall erlaubt? (Nicht völlig zufällige
Runden
2
Technischer Punkt; "Sie müssen erben Player::fight" / "Sie können erben Player::perceive" ... in beiden Fällen wird der Begriff außer Kraft gesetzt und nicht geerbt .
H Walters
3
Ich denke, Sie haben einen Fehler in GunDuel.hppbeiden validAund validBverwendenactionA
AlexRacer

Antworten:

9

MontePlayer

Dieser Player verwendet den UCT-Suchalgorithmus für entkoppelte Monte-Carlo-Bäume, um zu entscheiden, welche Auswahl getroffen werden soll. Es verfolgt, was der Feind tut, um seine Aktionen vorherzusagen. Es simuliert den Feind als sich selbst, wenn ihm Daten fehlen.

Dieser Bot schlägt sich wirklich gut gegen jeden anderen Bot außer cβ. In einem 10000 Duell gegen cβ gewann Monte 5246 Duelle. Mit ein bisschen Mathe bedeutet das, dass Monte in 53,74% der Fälle ein Duell gegen cβ 51,17% gewinnt (99% Selbstvertrauen).

#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__
Die Nummer eins
quelle
25

Das BlackHatPlayer

Der BlackHat-Spieler weiß, dass Kugeln und Schilde der Vergangenheit angehören. Die eigentlichen Kriege werden von denen gewonnen, die die Programme des Gegners hacken können.

Also setzt er einen festen Metallschild auf und macht sein Ding.

Das erste Mal, wenn er dazu aufgefordert wird fight, versucht er, seinen Feind im Gedächtnis zu lokalisieren. In Anbetracht der Struktur der Kampfarena ist es fast sicher, dass der Compiler seine Adresse (in eine Umhüllung unique_ptr) und die des Gegners nebeneinander schreibt.

Der BlackHat geht also vorsichtig über den Stapel, wobei er einige einfache Heuristiken verwendet, um sicherzustellen, dass er ihn nicht unterschreitet, bis er einen Zeiger auf sich selbst findet. dann prüft er, ob die werte in den angrenzenden positionen plausibel sein gegner sind - ähnliche adresse, ähnliche adresse der vtabelle, plausibel typeid.

Wenn es ihm gelingt, ihn zu finden, saugt er sein Gehirn heraus und ersetzt es durch das eines Hitzkopf-Idioten. In der Praxis wird dazu der Zeiger des Gegners auf die V-Tabelle durch die Adresse der IdiotV-Tabelle ersetzt - ein dummer Spieler, der immer schießt.

Wenn dies alles gelingt (und in meinen Tests - gcc 6 unter Linux 64 Bit, MinGW 4.8 unter Wine 32 Bit - funktioniert dies ziemlich zuverlässig), ist der Krieg gewonnen. Was auch immer der Gegner in der ersten Runde getan hat, ist nicht wichtig - im schlimmsten Fall hat er uns erschossen, und wir hatten den Metallschild an.

Von nun an haben wir einen Idioten, der gerade schießt. Wir haben immer unseren Schild an, damit wir beschützt sind und er wird in 1 bis 3 Runden in die Luft jagen (je nachdem, was der ursprüngliche Bot bei seinem ersten fightAufruf getan hat ).


Nun: Ich bin mir fast sicher, dass dies sofort disqualifiziert werden sollte, aber es ist lustig, dass ich nicht explizit gegen eine der oben genannten Regeln verstoße:

Was du NICHT tun darfst

  • Sie dürfen KEINE andere direkte Methode anwenden, um Ihren Gegner als die angegebene Gegner-ID zu erkennen, die zu Beginn jedes Turniers vollständig zufällig ausgewählt wird. Sie dürfen nur raten, wer ein Spieler innerhalb eines Turniers ist.

BlackHat versucht nicht, den Gegner zu erkennen - eigentlich ist es völlig irrelevant, wer der Gegner ist, da sein Gehirn sofort ersetzt wird.

  • Sie dürfen KEINE Methoden in der Player-Klasse überschreiben, die nicht als virtuell deklariert sind.
  • Sie dürfen KEINE Elemente im globalen Bereich deklarieren oder initialisieren.

Alles passiert lokal mit der fightvirtuellen Funktion.


// 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__
Matteo Italia
quelle
6
@TheNumberOne: auch, wie im ersten (und am besten bewerteten) Kommentar zum Lückenthread zu lesen: "Lücken sind Teil dessen, was das Spiel interessant macht. Sogar gewöhnliche können witzig oder clever sein, je nach Kontext." IMO ist dies originell (zumindest habe ich hier nie etwas Ähnliches gesehen) und in technischer Hinsicht anständig interessant; Deshalb habe ich es hier geteilt.
Matteo Italia
3
#ifdef __BLACKHAT_PLAYER_HPP__#error "Dependency issue; to compile, please include this file before BlackHatPlayer.hpp"#else#define __BLACKHAT_PLAYER_HPP__#endif
H Walters
1
@ MatteoItalia Black Hat immer unser Wissen über Standard-Schlupflöcher zu erhöhen :-)
Frenzy Li
2
@HWalters: Ich denke , ich werde zu wechseln haben #pragma once;-)
Matteo Italia
3
Es scheint einfach genug, jeden Spieler in einem separaten Prozess zu führen und über Steckdosen mit dem Schiedsrichter zu kommunizieren.
Jasen
19

Als nächstes, das am meisten gefürchtete aller Kreaturen, war es in der Hölle und zurück und hat mit buchstäblich 900000 anderen Bots gekämpft , es ist ...

Das BotRobot

BotRobot wurde nach einem sehr einfachen genetischen Algorithmus benannt, trainiert und automatisch erstellt.

Es wurden zwei 9er-Teams gegeneinander aufgestellt, in jeder Generation wird jeder Roboter von Team 1 gegen jeden Roboter von Team 2 aufgestellt. Die Roboter mit mehr Siegen als Verlusten behielten ihr Gedächtnis, der andere kehrte zum letzten Schritt zurück und hatte die Chance, etwas zu vergessen, hoffentlich schlecht. Die Bots selbst sind verherrlichte Nachschlagetabellen. Wenn sie etwas fanden, das sie vorher nicht gesehen hatten, wählten sie einfach eine zufällig gültige Option und speicherten sie im Speicher. Die C ++ - Version macht das nicht, sie hätte es lernen sollen . Wie bereits erwähnt, behalten Gewinn-Bots diesen neu gefundenen Speicher bei, da er eindeutig funktioniert hat. Bots verlieren nicht und behalten, womit sie angefangen haben.

Am Ende waren die Bot-Kämpfe ziemlich eng und blieben selten aus. Der Gewinner wurde aus einem Pool der beiden Teams nach der Evolution ausgewählt, der 100.000 Generationen umfasste.

BotRobot, mit seinem zufällig generierten und SCHÖNEN Namen, war der Glückliche.

Generator

bot.lua

Revision: Obwohl der Roboter ziemlich schlau gegen sich selbst und andere ähnlich erzeugte Roboter war, erwies er sich in tatsächlichen Schlachten als ziemlich nutzlos. Also habe ich sein Gehirn gegen einige der bereits erstellten Bots regeneriert.

Das Ergebnis ist, wie man leicht sehen kann, ein viel komplexeres Gehirn, bei dem der gegnerische Spieler über 12 Munition verfügt.

Ich bin nicht sicher, wogegen er gekämpft hat, aber irgendetwas hat es getan.

Und natürlich das fertige Produkt ...

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

Ich hasse C ++ jetzt ...

Ein Taco
quelle
@FrenzyLi Ich bin mir nicht sicher, wie ich das nicht bemerkt habe, und behebe es jetzt.
ATaco
Nun, nach diesem Update scheint der Bot eine feste Öffnung von zu haben 00.
Raserei Li
Ich verstehe, warum jetzt ... "1: 1" "0" ergibt.
Raserei Li
1
Mehrere Spieler hier haben ihr gesamtes Spiel auf der Grundlage von Runden repariert. Ich denke nicht, dass eine feste Eröffnung ein Problem sein sollte
eis
10

CBetaPlayer (cβ)

Ungefähres Nash-Gleichgewicht.

Dieser Bot ist nur eine ausgefallene Mathematik mit einem Code-Wrapper.

Wir können dies als spieltheoretisches Problem umformulieren. Bezeichne einen Gewinn mit +1 und einen Verlust mit -1. Nun sei B (x, y) der Wert des Spiels, in dem wir x Munition haben und unser Gegner y Munition hat. Es ist zu beachten, dass B (a, b) = -B (b, a) und damit B (a, a) = 0. Um B-Werte in Bezug auf andere B-Werte zu finden, können wir den Wert der Auszahlungsmatrix berechnen. Zum Beispiel haben wir, dass B (1, 0) durch den Wert des folgenden Teilspiels gegeben ist:

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

(Ich habe die "schlechten" Optionen entfernt, auch bekannt als diejenigen, die von den vorhandenen Lösungen streng dominiert werden. Zum Beispiel würden wir nicht versuchen, Plasma zu schießen, da wir nur 1 Munition haben. Ebenso würde unser Gegner niemals einen thermischen Deflektor verwenden.) da werden wir nie plasma schießen.)

Die Spieltheorie lässt uns wissen, wie wir den Wert dieser Auszahlungsmatrix unter bestimmten technischen Bedingungen ermitteln können. Wir erhalten, dass der Wert der obigen Matrix ist:

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

Wenn wir für alle möglichen Spiele fortfahren und B (x, y) -> 1 als x -> unendlich mit festem y angeben, können wir alle B-Werte finden, wodurch wir wiederum die perfekten Züge berechnen können!

Natürlich stimmt die Theorie selten mit der Realität überein. Das Lösen der Gleichung für selbst kleine Werte von x und y wird schnell zu kompliziert. Um damit umzugehen, habe ich die sogenannte cβ-Approximation eingeführt. Es gibt 7 Parameter für diese Annäherung: c0, β0, c1, β1, c, β und k. Ich nahm an, dass die B-Werte die folgende Form hatten (spezifischste Form zuerst):

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)

Einige grobe Überlegungen, warum ich diese Parameter gewählt habe. Zuerst wusste ich, dass ich definitiv 0, 1 und 2 oder mehr Munition separat haben wollte, da jede andere Optionen öffnet. Außerdem dachte ich, eine geometrische Überlebensfunktion wäre am besten geeignet, da der Defensivspieler im Wesentlichen errät, was zu tun ist. Ich dachte, dass 2 oder mehr Munition im Grunde das gleiche ist, also habe ich mich stattdessen auf den Unterschied konzentriert. Ich wollte B (1, 0) auch als einen Super-Sonderfall behandeln, weil ich dachte, dass es viel auftauchen würde. Die Verwendung dieser Näherungsformen vereinfachte die Berechnung der B-Werte erheblich.

Ich habe die resultierenden Gleichungen ungefähr gelöst, um jeden B-Wert zu erhalten, den ich dann wieder in die Matrix einfügte, um die Auszahlungsmatrizen zu erhalten. Dann fand ich mit einem linearen Programmierlöser die optimalen Wahrscheinlichkeiten für jede Bewegung und schob sie in das Programm.

Das Programm ist eine verherrlichte Nachschlagetabelle. Wenn beide Spieler zwischen 0 und 4 Munition haben, verwendet es die Wahrscheinlichkeitsmatrix, um zufällig zu bestimmen, welche Bewegung es machen soll. Andernfalls wird versucht, basierend auf der Tabelle zu extrapolieren.

Es hat Probleme mit dummen deterministischen Bots, aber es ist ziemlich gut gegen rationale Bots. Aufgrund der Annäherung geht dies gelegentlich an StudiousPlayer verloren, wenn dies eigentlich nicht der Fall sein sollte.

Wenn ich das noch einmal machen würde, würde ich wahrscheinlich versuchen, mehr unabhängige Parameter oder vielleicht eine bessere Ansatzform hinzuzufügen und eine genauere Lösung zu finden. Auch ich habe das Turn-Limit (absichtlich) ignoriert, weil es die Sache schwieriger machte. Eine schnelle Modifikation könnte gemacht werden, um immer Plasma zu schießen, wenn wir genug Munition haben und nicht mehr genug Runden übrig sind.

// 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__
George V. Williams
quelle
Da Sie keinen Parameter übergeben GetRandomDouble, können Sie das Argument max entfernen.
Raserei Li
@FrenzyLi, whoops, danke!
George V. Williams
Würde es Ihnen etwas ausmachen, ein wenig mehr Informationen über Ihren Spieler hinzuzufügen, wie zum Beispiel, wie Sie zu der Wahrscheinlichkeit gekommen sind ... Tensor?
Raserei Li
2
Ich liebe diesen Bot. Ich denke, SP hat den Vorteil bisher nur aufgrund des Determinismus der anderen Einträge; Je mehr (nicht optimal gewichtete) Zufallsbots hinzugefügt werden, desto besser sind die CBP-Tarife. Dies wird durch Tests gesichert. In meinen internen Tests mit den üblichen Verdächtigen gewinnt SP immer mit CBP an zweiter Stelle. In einem Mini-Wettbewerb, an dem CBP, SP und FP beteiligt sind, liegt CBP jedoch zu 55% vorne, wobei SP und FP ungefähr gleich weit vorne liegen.
H Walters
1
Dies ist übrigens eine beeindruckend genaue Annäherung an das Nash-Gleichgewicht. Monte versucht nicht, die Gleichgewichtsstrategie zu finden, sondern den besten Zug gegen einen bestimmten Gegner. Die Tatsache, dass es nur 52% der Zweikämpfe zwischen ihm und cβ gewinnt, bedeutet, dass cβ dem Nash-Gleichgewicht ziemlich nahe kommt.
TheNumberOne
8

Mir fehlt überall der Kommentar, deshalb kann ich meine Fragen noch nicht stellen. Das ist also ein sehr einfacher Spieler, um gegen den ersten Bot zu gewinnen.

[Bearbeiten] Danke, jetzt stimmt der vorherige Status nicht mehr, aber ich denke, es ist besser, ihn beizubehalten, damit wir den Kontext dieses Bots verstehen können.

Das Opportunist

Der Opportunist besucht den gleichen Waffenclub wie die GunClubPlayers. Er hat jedoch auf einen Neuling gewettet, dass er alle GunClubPlayers schlagen kann. Also nutzt er die Angewohnheit aus, die er schon lange bemerkt hat, und zwingt sich, nicht zu schießen, sondern nur ein wenig zu warten, um zu gewinnen.

#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__
ColdK
quelle
7

Das BarricadePlayer

Der Barricade-Spieler lädt in der ersten Runde eine Kugel und hält dann einen geeigneten Schild (immer noch etwas zufällig). Er lädt auch jede 5. Runde einen weiteren Schuss. In jeder Runde besteht eine Chance von 15%, den Algorithmus zu ignorieren (mit Ausnahme des Nachladens in der ersten Runde) und eine Kugel abzuschießen. Wenn der Feind keine Munition hat, lädt er. Wenn irgendwie alles schief geht, Junge, schießt er einfach.

Neueste Änderungen:

Verbesserte Zufallszahlen (danke 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__
devRicher
quelle
1
Möchten Sie zumindest überprüfen, ob Munition vorhanden ist, bevor Sie schießen?
Pavel
8
Nein, ich lebe das gefährliche Leben. @Pavel
devRicher
1
Ist es nicht sinnlos, den thermischen Deflektor in der zweiten Kurve zu benutzen? Sie können nicht zwei Kugeln in der ersten Runde laden. Ich denke, auch wenn Sie wollen, dass es zufällig ist, sollten Sie es vermeiden, den Hitzeschild zu verwenden, wenn die Kugeln des Gegners 1 (oder weniger) sind.
Southpaw Hare
1
Vielen Dank für alle Vorschläge, ich habe die Klasse viel bearbeitet. @SouthpawHare
devRicher
2
Ist es getAmmoOpponentnicht getOpponentAmmo. Sie verpassen auch#endif // !__BARRICADE_PLAYER_HPP__
Blue
7

Das StudiousPlayer

Der Studious Player untersucht seine Beute und modelliert jeden Gegner, dem er begegnet. Dieser Spieler beginnt mit einer Grundstrategie, die an bestimmten Stellen zufällig gesteuert wird, und entwickelt einfache Anpassungsstrategien, die auf häufig auftretenden Maßnahmen der Reaktion des Gegners basieren. Es wird ein einfaches Modell von Gegnern verwendet, das darauf basiert, wie sie auf Munitionskombinationen reagieren.

#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

Beachten Sie, dass dies Informationen über Gegner gemäß den Regeln der Herausforderung verfolgt. Siehe die "Meyers style singleton" -Methode "savedLs ()" am unteren Rand. (Einige Leute fragten sich, wie das geht. Jetzt wissen Sie es!)

H Walters
quelle
1
Ich hatte keine Ahnung, dass es der Meyers-Stil Singleton genannt wurde, bis ich das sah!
Raserei Li
1
Nimm den Begriff nicht zu ernst - es ist eine Art Missbrauch von Begriffen, da der "Singleton" eher eine Template-Instanz als eine deklarierte Struktur ist, aber es ist die gleiche Technik.
H Walters
6

Das GunClubPlayer

Aus der ursprünglichen Frage ausschneiden. Dies dient als Beispiel für eine minimalistische Implementierung eines abgeleiteten Players. Dieser Spieler nimmt am Turnier teil.

Sie GunClubPlayergehen gerne in den Waffenclub. Während jedes Duell würde laden sie zum ersten Mal Munition, dann eine Kugel abzufeuern, und wiederholen Sie diesen Vorgang , bis zum Ende der Welt Duell. Es ist ihnen eigentlich egal, ob sie gewinnen oder nicht, und sie konzentrieren sich ausschließlich darauf, selbst eine angenehme Erfahrung zu machen.

// 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__
Raserei Li
quelle
1
Sie brauchen das else nach einer return-Anweisung nicht mehr, oder? Ich weiß, es ist kein Code-Golf, aber es fühlt sich falsch an.
Pavel
2
@Pavel Nun, OK, also ... es ist jetzt ... eine Art Golf.
Raserei Li
5

Das PlasmaPlayer

Der Plasma-Spieler schießt gern seine Plasmabolzen ab. Er wird versuchen, so viel wie möglich zu laden und zu feuern. Solange der Gegner Plasmamunition hat, benutzt er seinen Wärmeschild (Kugeln sind für die Schwachen).

#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__
Brian J
quelle
@FrenzyLi danke für den Konstruktor! Mein C ++ ist ein bisschen verrostet und ich habe keinen Compiler auf diesem Computer.
Brian J
Bitte! Ich füge dem Projekt noch mehr Code hinzu (Anzeigetafel drucken, externes Skript lesen usw.) und es ist sehr glücklich, dass noch keine der Einsendungen fehlerhaft ist.
Raserei Li
Dies funktioniert gut für jeden Gegner außer GunClub. Ja, es wird den SadisticShooter (den besten) töten. @BrianJ
devRicher
5

Genau dieses SadisticShooter

Er würde dich lieber leiden sehen als dich töten. Er ist nicht dumm und wird sich nach Bedarf absichern.

Wenn Sie absolut langweilig und vorhersehbar sind, bringt er Sie sofort um.

// 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__
Blau
quelle
Ich sehe, du hast es repariert.
DevRicher
4

Das TurtlePlayer

TurtlePlayerist ein Feigling. Er verbringt die meiste Zeit damit, sich hinter seinen Schilden zu verstecken - daher der Name. Manchmal kann er aus seiner Muschel kommen (kein Wortspiel beabsichtigt) und einen Schuss haben, aber normalerweise liegt er tief, während der Feind Munition hat.


Dieser Bot ist nicht besonders gut - jedoch benötigt jeder KOTH einige erste Einträge, um ihn zum Laufen zu bringen :)

Lokale Tests festgestellt , dass diese Siege gegen beide GunClubPlayerund Opportunist100% der Zeit. Ein Kampf gegen BotRobotPlayerschien immer mit einem Unentschieden zu enden, da sich beide hinter ihren Schilden verstecken.

#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();
    }
};
FlipTack
quelle
4

Das DeceptivePlayer

Der Deceptive Player versucht zwei Kugeln zu laden und feuert dann eine ab.

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

Ich codiere nicht in c ++, daher sind Verbesserungen des Codes willkommen.

Sxntk
quelle
Meine Bearbeitung betrifft die Modulo- und Makrodefinition. Sie sind sich nicht sicher, ob es Ihnen gefällt, aber vielleicht DeceptivePlayerist es ein besserer Name?
Raserei Li
@FrenzyLi Ja, ich mag es, ich werde den Namen ändern
Sxntk
1
@Sxntk Ich mag die Ironie, bei der dieser Spieler erwartet, dass Leute mit 2 Munition Plasma abschießen, aber er selbst wird zwei Munitionen halten und eine Kugel abschießen.
Brian J
@Sxntk Sie haben derzeit keine Möglichkeit, nichts zurückzugeben. Ein Spieler darf mehr als zwei Munition haben. Wenn dein Gegner also 3+ Munition hat, ergreifst du nichts. Sie könnten irgendwo mit einer explodierten Waffe enden. (Natürlich könnte das sowieso Ihr Masterplan sein :))
Brian J
@BrianJ Danke, ich werde darüber nachdenken, in der Zwischenzeit werde ich den Deceptive verrückt werden lassen und entscheiden, was zu tun ist, wenn oponnent 3+ Munition hat
Sxntk
2

HanSoloPlayer

Schießt zuerst! Ich arbeite immer noch daran, es zu überarbeiten, aber das ist ziemlich gut.

// 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__
Conor O'Brien
quelle
2

Das CamtoPlayer

CamtoPlayer HATES zeichnet und löst Schleifen, egal was nötig ist . (außer Selbstmord)

Es ist mein erstes C ++ - Programm, das irgendetwas tut. Beurteilen Sie es also nicht zu sehr.

Ich weiß, es könnte besser sein, aber bitte nicht bearbeiten.
Wenn Sie den Code ändern möchten, kommentieren Sie einfach einen Vorschlag.

#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__
Benjamin Philippe
quelle
Du vergisst ein#endif // ! __CAMTO_HPP__
Blue
@muddyfish Danke, dass du mir gesagt hast, ich habe verschiedene weniger als Symbole, die das Rendern des Codes verhindert haben! XD
Benjamin Philippe
Immer noch nicht auftauchend. Ich würde empfehlen, die HTML-Tags ganz zu streichen und nur Markdown zu verwenden (die Schaltfläche "Codebeispiel", auf der "{}" steht). Manuelles Zitieren <>&ist ein Schmerz.
H Walters
@HWalters Danke für den Tipp!
Benjamin Philippe
Danke für Ihre Teilnahme. Und eins: bitte entfernen, using namespace stdda es das Turnier stört. Wenn Sie debuggen möchten, könnten Sie verwenden, std::coutetc.
Frenzy Li
1

Das SurvivorPlayer

Der Survivor Player verhält sich ähnlich wie der Turtle and Barricade Player. Er wird niemals etwas unternehmen, das zu seinem Tod führen könnte, und lieber ein Unentschieden erzwingen, als den Kampf zu verlieren.

// 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__
Turamarth
quelle
1

Das FatedPlayer

Gemacht von Clotho, erzielt von Lachesis und getötet von Atropos ; Die einzige Strategie dieses Spielers besteht darin, mit dem, was er über Munition weiß, zu bestimmen, welche Aktionen angemessen sind.

Es ist jedoch nicht möglich, die Aktion auszuwählen . Dieser Teil ist den Göttern überlassen.

#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

... weil ich sehen möchte, wie ein zufälliger Spieler rangiert.

H Walters
quelle
1

SpecificPlayer

SpecificPlayer folgt einem einfachen Plan, bei dem zufällige (gültige) Aktionen ausgewählt werden. Das Hauptmerkmal ist jedoch, dass es nach bestimmten Situationen Ausschau hält, indem es die Munitionszahlen und den vorherigen Zug des Gegners analysiert.

Dies ist das erste Mal, dass ich etwas in C ++ schreibe und das erste Mal, dass ich versuche, eine Art von konkurrenzfähigem Bot-Schreiben durchzuführen. Ich hoffe also, dass mein dürftiger Versuch zumindest etwas Interessantes bringt. :)

// 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__
Dysnomian
quelle
1

NotSoPatientPlayer

Die Geschichte seiner Entstehung wird später kommen.

// 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__
AlexRacer
quelle
"Die Geschichte seiner Entstehung wird später kommen" Es ist über 3 Monate her :)
HyperNeutrino