Le duel d'armes futuriste


73

Le futur de fond

En 2017, vous et votre adversaire s'affronterez dans une bataille d'armes à feu futuriste où un seul d'entre eux pourrait survivre. Êtes- vous assez expérimenté pour vaincre votre adversaire? Il est maintenant temps de perfectionner vos compétences en armes à feu dans votre langage de programmation préféré et de vous battre contre toute attente!

Résultats du tournoi

Ce tournoi terminé à l'UTC matin du Feburary 2 e 2017. Merci à nos concurrents, nous avons eu un tournoi futuriste passionnant!

MontePlayer est le vainqueur final après une bataille serrée avec CBetaPlayer et StudiousPlayer. Les trois meilleurs duels de guen ont pris une photo commémorative:

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

Félicitations aux gagnants! Classement détaillé est vu vers la fin de ce post.

Orientation générale

  • Visitez le dépôt officiel pour le code source utilisé dans ce tournoi.
  • Entrées C ++: veuillez hériter de la Playerclasse.
  • Entrées non C ++: sélectionnez une interface dans la section Interface pour les soumissions non C ++ .
  • Langages actuellement non autorisés: Python 3, Java.

Le duel

  • Chaque joueur commence avec un pistolet non chargé pouvant charger une quantité infinie de munitions.
  • À chaque tour, les joueurs choisiront simultanément l'une des actions suivantes:
    • 0 - Charge 1 munition dans le pistolet.
    • 1- tirer une balle sur l'adversaire; coûte 1 munitions chargées.
    • 2- tirer un faisceau de plasma sur l'adversaire; coûte 2 munitions chargées.
    • - - Défendre la balle en utilisant un bouclier métallique.
    • = - Défendre le faisceau de plasma entrant à l'aide d'un déflecteur thermique.
  • Si les deux joueurs survivent après le 100 e tour, ils s'épuisent à la mort, ce qui entraîne un match nul .

Un joueur perd le duel lorsqu’il

  • N'a PAS utilisé le bouclier métallique pour défendre une balle entrante.
  • N'a PAS utilisé le déflecteur thermique pour défendre un plasma entrant.
  • Tirez avec une arme à feu sans charger assez de munitions, dans laquelle leur arme explose et tue le propriétaire.

Mises en garde

Selon le Manuel du propriétaire d'armes futuriste :

  • Un bouclier métallique NE PEUT PAS se défendre du faisceau de plasma entrant. De même, un déflecteur thermique NE PEUT PAS se défendre contre les balles entrantes.
  • Le faisceau de plasma domine la balle (car la première nécessite des munitions plus chargées). Par conséquent, si un joueur tire un faisceau de plasma sur l'adversaire qui tire une balle dans le même tour, l'adversaire est tué.
  • Si les deux joueurs se tirent une balle dans le même tour, les balles s'annulent et les deux joueurs survivent. De même, si les deux joueurs se lancent un faisceau de plasma au cours du même tour, ils survivent.

Il convient également de noter que:

  • Vous ne connaîtrez PAS l'action de votre adversaire dans un tour jusqu'à la fin.
  • La déviation des faisceaux de plasma et des balles de protection ne nuira pas à votre adversaire.

Par conséquent, il y a un total de 25 combinaisons d'actions valides à chaque tour:

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

Exemple de duel

Voici un duel que j'ai eu une fois avec un ami. À l'époque, nous ne savions pas grand chose à propos de la programmation, nous avons donc utilisé des gestes de la main et signalé à la vitesse de deux tours par seconde. De gauche à droite, nos actions ont été à leur tour:

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

Selon les règles ci-dessus, j'ai perdu. Tu vois pourquoi? C'est parce que j'ai tiré le dernier faisceau de plasma alors que je n'avais qu'une cartouche chargée, ce qui a fait exploser mon arme.


Le lecteur C ++

En tant que programmeur futuriste civilisé, vous ne manipulerez pas directement les armes. Au lieu de cela, vous codez un Playerqui se bat contre les autres. En héritant publiquement de la classe du projet GitHub, vous pouvez commencer à écrire votre légende urbaine.

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

Ce que vous devez ou pouvez faire

  • Vous devez hériter d'une Playerclasse par héritage public et déclarer votre classe finale.
  • Vous devez remplacer Player::fight, ce qui retourne une valeur valide à Player::Actionchaque appel.
  • Vous pouvez éventuellement passer outre Player::perceive, Player::declaredsurveiller les actions de votre adversaire et garder trace de vos victoires.
  • Utilisez éventuellement des membres et des méthodes statiques privés dans votre classe dérivée pour effectuer des calculs plus complexes.
  • Utilisez éventuellement d'autres bibliothèques standard C ++.

Ce que vous ne devez PAS faire

  • Vous ne devez PAS utiliser une méthode directe pour reconnaître votre adversaire autre que l'identifiant donné de l'adversaire, qui est mélangé au début de chaque tournoi. Vous êtes seulement autorisé à deviner qui est un joueur grâce à son jeu dans un tournoi.
  • Vous ne devez PAS écraser les méthodes de la Playerclasse qui ne sont pas déclarées virtuelles.
  • Vous devez pas déclarer ou initialiser quoi que ce soit dans la portée globale.
  • Depuis le début de (maintenant disqualifié) BlackHatPlayer, les joueurs ne sont PAS autorisés à jeter un coup d'œil ou à modifier l'état de votre adversaire.

Un exemple de duel

Le processus d'un duel d'armes à feu est effectué en utilisant la GunDuelclasse. Pour un exemple de combat, voir la Source.cppsection Initiation d’un duel .

Nous présentons GunClubPlayer, HumanPlayeret la GunDuelclasse, qui peuvent être trouvés dans le Tournament\répertoire du référentiel.

Dans chaque duel, GunClubPlayerchargera une balle; tirez-le; rincer et répéter. À chaque tour, HumanPlayervous demandera une action à jouer contre votre adversaire. Vos commandes du clavier sont les personnages 0, 1, 2, -et =. Sous Windows, vous pouvez utiliser HumanPlayerpour déboguer votre soumission.

Initier un duel

Voici comment vous pouvez déboguer votre lecteur via la console.

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

Exemple de jeux

Le nombre de tours minimum que vous devez vaincre GunClubPlayerest de 3. Voici la répétition de jouer 0-1contre GunClubPlayer. Le nombre entre parenthèses est le nombre de munitions chargées pour chaque joueur à la fin du tour.

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

Le moyen le plus rapide pour être vaincu GunClubPlayersans faire de mouvements invalides est la séquence 0=, car la balle tire à travers le déflecteur thermique. La rediffusion est

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

Le tournoi

Le tournoi suit le format "Dernier joueur debout". Dans un tournoi, toutes les soumissions valables (y compris les GunClubPlayer) sont placées dans un pool. Chaque soumission se voit attribuer un identifiant aléatoire mais unique qui restera le même pendant tout le tournoi. À chaque tour:

  • Chaque candidature commence par 0 point et disputera 100 duels contre toutes les autres candidatures.
  • Chaque duel victorieux accordera 1 point; dessiner et perdre donne 0 points.
  • À la fin du tour, les soumissions avec le minimum de points quittent le tournoi. En cas d'égalité, le joueur avec le moins de points gagnés depuis le début du tournoi partira.
  • S'il reste plus d'un joueur, le tour suivant commence.
  • Les points NE sont PAS reportés au tour suivant.

Soumission

Vous allez soumettre un joueur par réponse. Vous pouvez soumettre plusieurs fichiers pour un joueur, à condition qu'ils n'interfèrent PAS avec d'autres soumissions. Pour que les choses continuent de couler, s'il vous plaît:

  • Nommez votre fichier d'en-tête principal comme suit <Custom>Player.hpp:
  • Nommez vos autres fichiers comme <Custom>Player*.*, par exemple, MyLittlePlayer.txtsi votre nom de classe est MyLittlePlayerou EmoPlayerHates.cppsi votre nom de classe est EmoPlayer.
  • Si votre nom contient Shooterdes mots similaires qui correspondent au contexte de ce tournoi, vous n’aurez pas besoin d’ajouter Playerà la fin. Si vous êtes fermement convaincu que votre nom de soumission fonctionne mieux sans suffixe Player, vous n'avez pas besoin d'ajouter Player.
  • Assurez-vous que votre code peut être compilé et lié sous Windows.

Vous pouvez commenter pour demander des éclaircissements ou pour repérer des échappatoires. J'espère que vous apprécierez ce duel d'armes futuriste et vous souhaite une bonne année!

Clarification

  • Vous êtes autorisé à avoir un comportement aléatoire.
  • Les actions non valides (tirer lorsque des munitions chargées ne suffisent pas) sont autorisées.
  • Si un joueur fait une entrée invalide, son arme explose immédiatement.
  • Vous êtes autorisé à étudier les réponses.
  • Vous êtes explicitement autorisé à enregistrer le comportement de votre adversaire dans chaque tournoi.
  • Chaque tour, vous jouerez 100 duels contre chaque adversaire; Cependant, l'ordre des 100 duels est aléatoire. Vous n'êtes pas assuré de combattre le même adversaire 100 duels d'affilée.

Ressources additionnelles

@flawr a traduit la source C ++ fournie en Java en tant que référence si vous souhaitez soumettre des entrées C ++.

Interface pour les soumissions non C ++

Actuellement accepté: Python 3, Java.

Veuillez suivre l'une des spécifications ci-dessous:

Spécification d'interface 1: code de sortie

Votre soumission sera exécutée une fois par tour.

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.

Vous pouvez tester votre soumission dans PythonPlayer\et des JavaPlayer\répertoires.

Spécification d'interface 2: stdin / stdout

(Crédit à H Walters)

Votre soumission sera exécutée une fois par tournoi.

Il existe une exigence fixe pour toutes les entrées sur la manière de faire des E / S, puisque stdin et stdout sont tous les deux connectés au pilote du tournoi. Violer cela pourrait mener à une impasse. Toutes les entrées DOIVENT suivre cet algorithme EXACT (en pseudo-code):

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

Ici, F est l' un 0, 1, 2, -ou =pour load / bullet / plasma / metal / thermal. PROCESSUS signifie éventuellement répondre à ce que votre adversaire a fait (y compris le suivi des munitions de votre adversaire si vous le faites). Notez que l'action de l'adversaire est également l'une des actions "0", "1", "2", "-" ou "=", et se trouve dans le deuxième caractère.

Tableau de bord final

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 |

Le tournoi durera jusqu'au 1er février 2017, sauf indication contraire.


15
Soit dit en passant, premier défi!
Martin Ender

3
Si vous êtes prêt à utiliser d'autres langages, vous pouvez autoriser une Playerimplémentation qui appelle un autre processus pour calculer le tournant actuel. Cela permettrait aux gens de participer à n’importe quelle langue que vous êtes heureux d’utiliser sur votre machine.
Martin Ender

5
Aléatoire autorisé? (Pas de tours complètement aléatoires, juste un choix d'action 50/50 dans une certaine situation)
FlipTack

2
Point technique; "Vous devez hériter Player::fight" / "vous pouvez hériter Player::perceive" ... dans les deux cas, le terme est redéfini , pas hérité .
H Walters

3
Je pense que vous avez un problème avec les GunDuel.hppdeux validAet que vous les validButilisezactionA
AlexRacer

Réponses:


9

MontePlayer

Ce joueur utilise l'algorithme de recherche d'arborescence découplée UCT Monte Carlo pour décider des choix à effectuer. Il surveille ce que l'ennemi fait pour prédire ses actions. Il simule l'ennemi comme s'il manquait de données.

Ce bot fait vraiment bien contre tous les autres, sauf cβ. Dans un match de 10000 duels contre cβ, Monte a remporté 5246 duels. Avec un peu de maths, cela signifie que Monte remportera un duel contre 51,17% à 53,74% des fois (confiance de 99%).

#ifndef __Monte_PLAYER_HPP__
#define __Monte_PLAYER_HPP__

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


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

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

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


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

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

    struct Stat
    {
        int wins;
        int attempts;

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

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

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

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

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

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


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

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

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

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

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

public:

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

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

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

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

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

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

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

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

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

        return (Action)move;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return game;
    }

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

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

        //Choose random opponent action.
        int opponentAction;

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

        validMoves = 0;

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

        return doTurn(game, myAction, opponentAction);
    }

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

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

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

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

        return best;

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

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

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

#endif // !__Monte_PLAYER_HPP__

25

le BlackHatPlayer

Le joueur BlackHat sait que les balles et les boucliers appartiennent au passé. les guerres sont gagnées par ceux qui peuvent pirater les programmes de l'adversaire.

Alors, il met un bouclier métallique fixe et commence à faire son truc.

La première fois qu'on le lui demande fight, il tente de localiser son ennemi en mémoire. Compte tenu de la structure de l'arène de combat, il est presque certain que le compilateur finira par mettre son adresse (entourée d'un unique_ptr) et celle de l'adversaire l'une à côté de l'autre.

Ainsi, le BlackHat parcourt la pile avec précaution, en utilisant quelques heuristiques simples pour s’assurer de ne pas la submerger, jusqu’à ce qu’il trouve un pointeur sur lui-même; puis vérifie si les valeurs dans les positions adjacentes sont plausiblement son adversaire - adresse similaire, adresse similaire de la table, plausible typeid.

S'il parvient à le trouver, il se fait sucer la tête et les remplace par ceux d'un idiot à la tête brûlée. En pratique, cela se fait en remplaçant le pointeur de l'adversaire sur la table virtuelle par l'adresse de la Idiottable virtuelle - un joueur stupide qui tire toujours.

Si tout cela réussit (et dans mes tests - gcc 6 sur Linux 64 bits, MinGW 4.8 sur Wine 32 bits - cela fonctionne de manière assez fiable), la guerre est gagnée. Peu importe ce que l’adversaire a fait au premier tour, ce n’est pas important - au pire, il nous a tiré dessus et nous avions le bouclier métallique.

À partir de maintenant, nous avons un idiot en train de tirer; nous avons toujours notre bouclier, nous sommes donc protégés et il explose en 1 à 3 rounds (en fonction de ce que le bot d'origine a fait lors de son premier fightappel).


Maintenant: je suis presque sûr que cela devrait être immédiatement disqualifié, mais il est amusant de ne pas enfreindre explicitement les règles énoncées ci-dessus:

Ce que vous ne devez PAS faire

  • Vous ne devez PAS utiliser une méthode directe pour reconnaître votre adversaire autre que l'identifiant donné de l'adversaire, lequel est complètement randomisé au début de chaque tournoi. Vous êtes seulement autorisé à deviner qui est un joueur grâce à son gameplay dans un tournoi.

BlackHat n'essaie pas de reconnaître l'adversaire - en réalité, l'identité de l'adversaire est totalement indifférente étant donné que son cerveau est immédiatement remplacé.

  • Vous ne devez PAS écraser les méthodes de la classe Player qui ne sont pas déclarées virtuelles.
  • Vous ne devez rien déclarer ni initialiser dans la portée globale.

Tout se passe localement à la fightfonction virtuelle.


// BlackHatPlayer.hpp

#ifndef __BLACKHAT_PLAYER_HPP__
#define __BLACKHAT_PLAYER_HPP__

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

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

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

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

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

        // Our adversary
        Player *c = nullptr;

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

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

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

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

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

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

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

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

#endif // !__BLACKHAT_PLAYER_HPP__

6
@TheNumberOne: aussi, comme dans le premier commentaire (et le plus voté) sur le fil des échappatoires: "Les échappatoires font partie de ce qui rend le jeu intéressant. Même les plus courantes peuvent être amusantes ou intelligentes, selon le contexte". IMO, c’est original (du moins, je n’ai jamais rien vu de tel ici) et décemment intéressant, du point de vue technique; c'est pourquoi je l'ai partagé ici.
Matteo Italia

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

1
@MatteoItalia BlackHat toujours augmenter notre connaissance des échappatoires standard :-)
Frenzy Li

2
@HWalters: Je suppose que je vais devoir passer à #pragma once;-)
Matteo Italia

3
semble assez simple, lancer chaque joueur dans un processus séparé et utiliser des sockets pour communiquer avec l'arbitre.
Jasen

19

Ensuite, la plus redoutée de toutes les créatures, elle a été en enfer et à l'arrière et s'est battue avec 900 000 autres bots , sa ...

le BotRobot

BotRobot a été nommé, formé et construit automatiquement par un algorithme génétique très basique.

Deux équipes de 9 ont été mises en place, chaque génération, chaque robot de l’équipe 1 est confronté à chaque robot de l’équipe 2. Les robots avec plus de victoires que de défaites ont gardé leur mémoire, l’autre est revenu à la dernière étape. , et eu la chance d’oublier quelque chose, heureusement mauvais. Les robots eux-mêmes sont des tables de consultation enrichies. Dans le cas où ils trouveraient quelque chose qu'ils n'avaient jamais vu auparavant, ils choisiraient simplement une option valide aléatoire et l'enregistreraient en mémoire. La version C ++ ne fait pas cela, elle aurait dû apprendre . Comme indiqué précédemment, les bots gagnants conservent cette nouvelle mémoire, de toute évidence, ils ont fonctionné. Perdre des robots ne le fait pas, et conserve ce avec quoi ils ont commencé.

À la fin, les combats de bots étaient assez serrés, rarement dans l’impasse. Le gagnant a été choisi parmi un groupe de deux équipes post évolution, soit 100 000 générations.

BotRobot, avec son nom généré BEAU et de manière aléatoire , a été le plus chanceux.

Générateur

bot.lua

Révision: Bien que le robot fût assez intelligent contre lui-même et contre d’autres robots générés de la même manière, il s’avéra relativement peu utile dans les combats réels. Alors, j'ai régénéré son cerveau contre certains des bots déjà créés.

Comme on peut facilement le constater, le résultat est un cerveau beaucoup plus complexe, avec des options allant jusqu’au joueur ennemi disposant de 12 munitions.

Je ne suis pas sûr de savoir ce qu'il combattait contre 12 munitions, mais quelque chose l'a fait.

Et bien sûr, le produit fini ...

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

Je déteste le C ++ maintenant ...


@FrenzyLi Pas sûr de savoir comment je ne l'ai pas remarqué, en le réglant maintenant.
ATaco

Après cette mise à jour, le bot semble avoir une ouverture fixe de 00.
Frenzy Li

Je vois pourquoi maintenant ... "1: 1" donne "0".
Frenzy Li

1
plusieurs joueurs ici ont réglé leur jeu en entier en fonction des tours, donc je ne pense pas qu'une ouverture fixe soit un problème
eis

10

CBetaPlayer (cβ)

Équilibre de Nash approximatif.

Ce bot est juste mathématique de fantaisie avec un wrapper de code.

Nous pouvons recadrer cela comme un problème de théorie des jeux. Indique une victoire de +1 et une défaite de -1. Soit maintenant B (x, y) la valeur du jeu où nous avons x munitions et où notre adversaire a y munitions. Notez que B (a, b) = -B (b, a) et donc B (a, a) = 0. Pour trouver les valeurs B en termes d'autres valeurs B, nous pouvons calculer la valeur de la matrice de gains. Par exemple, nous avons que B (1, 0) est donné par la valeur du sous-jeu suivant:

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

(J'ai supprimé les "mauvaises" options, c'est-à-dire celles qui sont strictement dominées par les solutions existantes. Par exemple, nous n'essaierions pas de tirer au plasma car nous ne disposons que d'une munition. De même, notre adversaire n'utilisera jamais de déflecteur thermique, puisque nous ne tirerons jamais de plasma.)

La théorie des jeux nous permet de déterminer la valeur de cette matrice de gains, dans certaines conditions techniques. Nous obtenons que la valeur de la matrice ci-dessus est:

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

En procédant pour tous les jeux possibles et en notant que B (x, y) -> 1 comme x -> infini avec y fixe, nous pouvons trouver toutes les valeurs B, ce qui nous permet de calculer les mouvements parfaits!

Bien sûr, la théorie s'aligne rarement avec la réalité. Résoudre l'équation même pour de petites valeurs de x et y devient rapidement trop compliqué. Afin de régler ce problème, j'ai introduit ce que j'appelle l'approximation cβ. Il y a 7 paramètres à cette approximation: c0, β0, c1, β1, c, β et k. J'ai supposé que les valeurs B prenaient la forme suivante (les formes les plus spécifiques en premier):

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)

Un raisonnement approximatif sur la raison pour laquelle j'ai choisi ces paramètres. Tout d'abord, je savais que je voulais absolument avoir séparément 0, 1 et 2 munitions ou plus, car chacune ouvre des options différentes. De plus, je pensais qu'une fonction de survie géométrique serait la plus appropriée, car le joueur défensif est essentiellement en train de deviner quel mouvement il veut faire. J'ai pensé qu'avoir 2 munitions ou plus était fondamentalement la même chose, alors je me suis concentré sur la différence. Je voulais aussi traiter B (1, 0) comme un cas super spécial parce que je pensais que cela se manifesterait beaucoup. L'utilisation de ces formes approximatives a grandement simplifié les calculs des valeurs B.

J'ai approximativement résolu les équations résultantes pour obtenir chaque valeur B, que j'ai ensuite réinsérée dans la matrice afin d'obtenir les matrices de gains. Puis, en utilisant un solveur de programmation linéaire, j’ai trouvé les probabilités optimales pour chaque déplacement et les ai insérées dans le programme.

Le programme est une table de recherche enrichie. Si les deux joueurs ont entre 0 et 4 cartouches, il utilise la matrice de probabilité pour déterminer au hasard le coup qu'il doit effectuer. Sinon, il essaie d'extrapoler en fonction de sa table.

Il a des problèmes contre des robots déterministes stupides, mais réussit plutôt bien contre des robots rationnels. En raison de toutes les approximations, StudiousPlayer perdra de temps en temps ce qui ne devrait pas être le cas.

Bien sûr, si je devais refaire cela, j'essaierais probablement d'ajouter des paramètres plus indépendants ou peut-être une meilleure forme ansatz et de proposer une solution plus exacte. De plus, j'ai (volontairement) ignoré la limite de virage, car cela rendait les choses plus difficiles. Une modification rapide pourrait être faite pour toujours utiliser le plasma si nous avons assez de munitions et s'il ne reste pas assez de tours.

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

Comme vous ne transmettez pas de paramètre GetRandomDouble, vous pouvez supprimer l’argument max.
Frenzy Li

@FrenzyLi, oups, merci!
George V. Williams

Souhaitez-vous ajouter un peu plus d’informations sur votre joueur, par exemple comment vous en êtes arrivé à la probabilité ... tenseur?
Frenzy Li

2
J'adore ce bot. Je pense que SP n’a jusqu’à présent l'avantage que grâce au déterminisme des autres entrées; plus on ajoute de bots aléatoires (non pondérés de manière optimale), meilleurs sont les tarifs CBP. Ceci est soutenu par des tests; Dans mes tests internes avec les suspects habituels, SP gagne toujours avec CBP à la seconde place ... Cependant, dans un mini-concours associant CBP, SP et FP, CBP avance 55% du temps, SP et FP obtenant à peu près la même chose.
H Walters

1
Soit dit en passant, il s’agit d’une approximation extrêmement précise de l’équilibre initial. Monte n'essaie pas de trouver la stratégie d'équilibre, mais le meilleur coup contre un adversaire donné. Le fait qu'il ne gagne que 52% des duels entre lui et cβ signifie que cβ est assez proche de l'équilibre initial.
TheNumberOne

8

Le commentaire me manque partout, donc je ne peux pas encore poser mes questions. C'est donc un joueur très basique à gagner contre le premier bot.

[Edit] Merci, le statut précédent n'est plus vrai, mais je pense qu'il est préférable de le conserver pour que nous puissions comprendre le contexte de ce bot.

le Opportunist

L'opportuniste fréquente le même club de tir que les GunClubPlayers, mais il a parié sur un nouveau venu qu'il pourrait battre tous les GunClubPlayers. Il exploite donc l’habitude qu’il a longtemps remarquée et s’oblige à ne pas tirer mais à attendre un peu pour gagner.

#ifndef __OPPORTUNIST_PLAYER_HPP__
#define __OPPORTUNIST_PLAYER_HPP__

#include <string>
#include <vector>

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

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

7

le BarricadePlayer

Le joueur de la barricade charge une balle au premier tour, puis garde un bouclier approprié (encore un peu aléatoire). Il charge également un autre coup tous les 5 tours. À chaque tour, il y a 15% de chances d'ignorer l'algorithme (sauf pour le rechargement du premier tour) et de tirer une balle. Quand l'ennemi n'a pas de munitions, il se charge. Si d’une manière ou d’une autre tout va mal, oh mon Dieu, il tire juste.

Nouveaux changements:

Amélioration des nombres aléatoires (merci Frenzy Li).

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

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

#ifndef __BARRICADE_PLAYER_HPP__
#define __BARRICADE_PLAYER_HPP__

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

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

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

        }
        return bullet();
    }
};

#endif // !__BARRICADE_PLAYER_HPP__

1
Voulez-vous au moins vérifier s'il y a des munitions avant de tirer?
Pavel

8
Non, je vis la vie dangereuse. @Pavel
devRicher

1
N’est-il pas inutile d’utiliser le déflecteur thermique au deuxième tour? Vous ne pouvez pas charger deux balles au premier tour. Je pense que même si vous voulez que ce soit aléatoire, vous devriez éviter d'utiliser le bouclier thermique si les balles de l'adversaire sont à 1 (ou moins).
Southpaw Hare

1
Merci pour toutes les suggestions, j'ai édité la classe beaucoup. @SouthpawHare
devRicher

2
Ce n'est getAmmoOpponentpas getOpponentAmmo. Vous manquez également#endif // !__BARRICADE_PLAYER_HPP__
Bleu

7

le StudiousPlayer

Le joueur studieux étudie sa proie et présente chaque adversaire qu’il rencontre. Ce joueur commence par une stratégie de base, conduite de manière aléatoire dans des lieux, puis passe à de simples stratégies adaptatives basées sur des mesures fréquentistes de la réponse de l'adversaire. Il utilise un modèle simple d'opposants basé sur la manière dont ils réagissent aux combinaisons de munitions.

#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

Notez que cela suit les informations sur les adversaires selon les règles du défi; voir le "singleton de style Meyers" scoped "stocked Ls () "méthode en bas. (Certaines personnes se demandaient comment faire cela; maintenant vous savez!)


1
Je ne savais pas qu'il s'appelait le singleton style Meyers jusqu'à ce que je voie ça!
Frenzy Li

1
Ne prenez pas le terme trop au sérieux - c'est un peu un abus de termes, car le "singleton" est une instanciation de template plutôt qu'une struct déclarée, mais c'est la même technique.
H Walters

6

le GunClubPlayer

Découpé de la question initiale. Cela sert d'exemple d'une implémentation minimaliste d'un joueur dérivé. Ce joueur participera au tournoi.

Ils GunClubPlayeraiment aller au club de tir. Au cours de chaque duel, ils chargeaient d’abord des munitions, puis tiraient une balle et répètent ce processus jusqu’au duel de la fin du monde . Ils ne se soucient pas vraiment de savoir s'ils gagnent ou non, et se concentrent exclusivement sur une expérience agréable.

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

#ifndef __GUN_CLUB_PLAYER_HPP__
#define __GUN_CLUB_PLAYER_HPP__

#include "Player.hpp"

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

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

#endif // !__GUN_CLUB_PLAYER_HPP__

1
Vous n'avez pas besoin du reste après une déclaration de retour, non? Je sais que ce n'est pas du code golf, mais ça fait mal.
Pavel

2
@Pavel Eh bien, d'accord, alors ... c'est ... une sorte de golfé maintenant.
Frenzy Li

5

le PlasmaPlayer

Le joueur de plasma aime tirer ses boulons de plasma. Il essaiera de charger et de tirer le plus possible. Cependant, tant que l'adversaire aura des munitions à plasma, il utilisera son bouclier thermique (les balles sont pour les faibles).

#ifndef __PLASMA_PLAYER_HPP__
#define __PLASMA_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

#endif // !__PLASMA_PLAYER_HPP__

@FrenzyLi merci pour le constructeur! Mon C ++ est un peu rouillé et je n'ai pas de compilateur sur cette machine.
Brian J

Vous êtes les bienvenus! J'ajoute encore du code (tableau d'affichage, lecture de script externe, etc.) au projet et c'est très chanceux qu'aucune des soumissions ne soit encore brisée.
Frenzy Li

Cela fonctionnera bien pour tout adversaire en dehors de GunClub. Oui, ça va tuer le SadisticShooter (le meilleur). @BrianJ
devRicher

5

La très SadisticShooter

Il préfèrerait te regarder souffrir que te tuer. Il n'est pas stupide et va se couvrir au besoin.

Si vous êtes totalement ennuyeux et prévisible, il vous tuera tout de suite.

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

Je vois que tu l'as réparé.
devRicher

4

le TurtlePlayer

TurtlePlayerest un lâche. Il passe la plupart du temps à se cacher derrière ses boucliers - d'où son nom. Parfois, il peut sortir de sa coquille (sans jeu de mots) et avoir un coup de feu, mais il est généralement allongé au sol, tandis que l'ennemi a des munitions.


Ce bot n'est pas particulièrement génial - cependant, chaque KOTH a besoin de quelques entrées initiales pour le faire fonctionner :)

Des tests locaux ont montré que cela l'emporte à la fois GunClubPlayeret dans Opportunist100% des cas. Une bataille contre BotRobotPlayersemblait avoir toujours pour résultat un match nul puisque les deux se cachent derrière leurs boucliers.

#include "Player.hpp"

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

class TurtlePlayer final : public Player {

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

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

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

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

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

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

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

4

le DeceptivePlayer

Le joueur trompeur essaie de charger deux balles puis en tire une.

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

Je ne code pas en c ++, toute amélioration du code sera la bienvenue.


Mon édition est sur la définition modulo et macro. Pas sûr que ça te plaise, mais peut DeceptivePlayer- être un meilleur nom?
Frenzy Li

@FrenzyLi Oui, j'aime bien, je changerai de nom
Sxntk

1
@Sxntk J'aime l'ironie où ce joueur s'attend à ce que les personnes disposant de 2 munitions tirent au plasma, mais lui-même détiendra deux munitions et tirera une balle.
Brian J

@Sxntk Vous n'avez pas la possibilité de ne rien retourner pour le moment. Un joueur a plus de deux munitions. Donc, si votre adversaire a plus de 3 munitions, vous ne faites rien. Vous pourriez vous retrouver avec une arme explosée quelque part. (Bien sûr, cela pourrait être votre plan directeur de toute façon :))
Brian J

@BrianJ Merci, j'y penserai, en attendant, je laisserai le Deceptive devenir fou et déciderai quoi faire quand l'oponnent aura plus de 3 munitions
Sxntk

2

HanSoloPlayer

Tire d'abord! Je travaille toujours sur la révision, mais c’est très bien.

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

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

#ifndef __HAN_SOLO_PLAYER_HPP__
#define __HAN_SOLO_PLAYER_HPP__

#include "Player.hpp"

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

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

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

#endif // !__HAN_SOLO_PLAYER_HPP__

2

le CamtoPlayer

CamtoPlayer HATES dessine et sortira des boucles, peu importe ce que cela prend. (sauf suicide)

C'est mon premier programme C ++ qui fait quelque chose, alors ne le jugez pas trop fort.

Je sais que cela pourrait être mieux, mais s'il vous plait, ne le modifiez pas.
Si vous souhaitez modifier le code, commentez une suggestion.

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

Vous oubliez un#endif // ! __CAMTO_HPP__
Bleu

@muddyfish Merci de me l'avoir dit, il y a beaucoup moins de symboles qui ont empêché le rendu du code! XD
Benjamin Philippe

Toujours pas arriver. Je recommanderais de supprimer les balises HTML et d'utiliser simplement markdown (le bouton "Exemple de code" sur lequel figure "{}"). Citer manuellement <>&est une douleur.
H Walters

@HWalters Merci pour le tuyau!
Benjamin Philippe

Merci d'avoir participé. Et une chose: s'il vous plaît supprimer, using namespace stdcar cela interfère avec le tournoi. Si vous voulez déboguer, vous pouvez utiliser std::coutetc.
Frenzy Li

1

le SurvivorPlayer

Le joueur survivant se comporte de la même manière que le joueur tortue et barricade. Il ne prendra jamais une mesure qui pourrait mener à sa mort et préférerait imposer un match nul que de perdre le combat.

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

#ifndef __SURVIVOR_PLAYER_HPP__
#define __SURVIVOR_PLAYER_HPP__

#include "Player.hpp"

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

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

#endif // !__SURVIVOR_PLAYER_HPP__

1

le FatedPlayer

Fabriqué par Clotho, marqué par Lachesis et tué par Atropos ; La seule stratégie de ce joueur est d'utiliser ses connaissances sur les munitions pour déterminer quelles actions sont raisonnables.

Cependant, il ne faut pas choisir l'action; cette partie est laissée aux dieux.

#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

... parce que j'aimerais voir comment un joueur au hasard se classe.


1

SpecificPlayer

SpecificPlayer suit un plan simple consistant à choisir des actions aléatoires (valides). Cependant, sa principale caractéristique est qu'il surveille certaines situations en analysant le nombre de munitions et le coup précédent de l'adversaire.

C’est la première fois que j’écris quelque chose en C ++ et j’essaie d’écrire des bots concurrents. J'espère donc que ma maigre tentative fera au moins quelque chose d'intéressant. :)

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

#ifndef __SPECIFIC_PLAYER_HPP__
#define __SPECIFIC_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

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

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

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

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

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

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

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

        return returnval;
    }

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

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

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

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

#endif // !__SPECIFIC_PLAYER_HPP__

1

NotSoPatientPlayer

L'histoire de sa création viendra plus tard.

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

"L'histoire de sa création viendra plus tard" ça fait plus de 3 mois :)
HyperNeutrino
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.