El duelo arma futurista

73

El futuro de fondo

En el año 2017, usted y su oponente se enfrentarán en un tiroteo futurista donde solo uno puede sobrevivir. ¿ Tienes suficiente experiencia para derrotar a tu oponente? ¡Ahora es el momento de pulir tus habilidades con las armas en tu lenguaje de programación favorito y luchar contra todo pronóstico!

Resultados del torneo

Este torneo terminó en la mañana del UTC de Feburary 2 nd , 2017. Gracias a nuestros concursantes, hemos tenido un emocionante torneo futurista!

MontePlayer es el ganador final después de una estrecha batalla con CBetaPlayer y StudiousPlayer. Los tres mejores duendes de guen han tomado una fotografía conmemorativa:

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

¡Felicitaciones a los ganadores! La tabla de clasificación detallada se ve cerca del final de esta publicación.

Orientación general

  • Visita el repositorio oficial del código fuente utilizado en este torneo.
  • Entradas de C ++: herede la Playerclase.
  • Entradas que no son de C ++: seleccione una interfaz en la sección Interfaz para envíos que no sean de C ++ .
  • Lenguajes actualmente no permitidos en C ++: Python 3, Java.

El duelo

  • Cada jugador comienza con un arma descargada que puede cargar una cantidad infinita de munición.
  • Cada turno, los jugadores elegirán simultáneamente una de las siguientes acciones:
    • 0 - Cargue 1 munición en la pistola.
    • 1- Dispara una bala al oponente; cuesta 1 munición cargada.
    • 2- Dispara un rayo de plasma al oponente; cuesta 2 municiones cargadas.
    • - - Defiende la bala entrante con un escudo de metal.
    • = - Defienda el haz de plasma entrante con un deflector térmico.
  • Si ambos jugadores sobreviven después de la 100 ª vez, ambos de escape a la muerte, lo que resulta en un empate .

Un jugador pierde el duelo armado si

  • Did NO usar el escudo de metal que defender una bala entrante.
  • Did NO utilizar el deflector térmico para defender un plasma entrante.
  • Dispara un arma sin cargar suficiente munición, en la cual su arma explotará y matará al propietario.

Advertencias

De acuerdo con el Manual para propietarios de armas futuristas :

  • Un escudo de metal NO PUEDE defenderse del haz de plasma entrante. Del mismo modo, un deflector térmico NO PUEDE defenderse de la bala entrante.
  • El rayo de plasma domina la bala (porque el primero requiere más munición cargada). Por lo tanto, si un jugador dispara un rayo de plasma al oponente que dispara una bala en el mismo turno, el oponente es asesinado.
  • Si ambos jugadores se disparan una a la otra en el mismo turno, las balas se cancelan y ambos jugadores sobreviven. Del mismo modo, si ambos jugadores se disparan un rayo de plasma el uno al otro en el mismo turno, ambos jugadores sobreviven.

También es digno de mención que:

  • Usted no sabe la acción de su oponente en una vuelta hasta que se termina.
  • Desviar los rayos de plasma y las balas de protección NO dañará a tu oponente.

Por lo tanto, hay un total de 25 combinaciones de acción válidas cada turno:

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

Duelo de ejemplo

Aquí hay un duelo que tuve una vez con un amigo. En aquel entonces, no sabíamos mucho sobre programación, así que usamos gestos con las manos y señalábamos a la velocidad de dos vueltas por segundo. De izquierda a derecha, nuestras acciones fueron a su vez:

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

Según las reglas anteriores, perdí. ¿Ves por qué? Es porque disparé el rayo de plasma final cuando solo tenía 1 munición cargada, haciendo que mi arma explotara.


El reproductor de C ++

Usted , como programador futurista civilizado, no manejará directamente las armas. En cambio, codificas una Playerque lucha contra los demás. Al heredar públicamente la clase en el proyecto GitHub, puede comenzar a escribir su leyenda urbana.

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

Lo que debes o puedes hacer

  • Debe heredar la Playerclase a través de la herencia pública y declarar su clase final.
  • Debe anular Player::fight, lo que devuelve un valor válido Player::Actioncada vez que se llama.
  • Opcionalmente, anula Player::perceivey vigila Player::declaredlas acciones de tu oponente y realiza un seguimiento de tus victorias.
  • Opcionalmente, use miembros y métodos estáticos privados en su clase derivada para realizar cálculos más complejos.
  • Opcionalmente, use otras bibliotecas estándar de C ++.

Lo que NO debes hacer

  • NO debes usar ningún método directo para reconocer a tu oponente que no sea el identificador de oponente dado, que se baraja al comienzo de cada torneo. Solo puedes adivinar quién es un jugador a través de su juego dentro de un torneo.
  • NO debe anular ningún método en la Playerclase que no se declare virtual.
  • NO debe declarar ni inicializar nada en el ámbito global.
  • Desde el debut de (ahora descalificado) BlackHatPlayer, los jugadores NO pueden mirar o modificar el estado de tu oponente.

Un ejemplo de duelo

El proceso de un duelo de armas se realiza utilizando la GunDuelclase. Para ver un ejemplo de pelea, mira Source.cppen la sección Iniciando un duelo .

Mostramos GunClubPlayer, HumanPlayery la GunDuelclase, que se puede encontrar en el Tournament\directorio del repositorio.

En cada duelo, GunClubPlayercargará una bala; dispararlo; enjuague y repita. Durante cada turno, HumanPlayerte pedirá una acción para jugar contra tu oponente. Sus controles de teclado son los personajes 0, 1, 2, -y =. En Windows, puede usar HumanPlayerpara depurar su envío.

Iniciando un duelo

Así es como puedes depurar tu reproductor a través de la consola.

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

Juegos de ejemplo

La menor cantidad de turnos que necesitas para derrotar GunClubPlayeres 3. Aquí está la repetición de jugar 0-1contra GunClubPlayer. El número en la parántesis es el número de munición cargada para cada jugador cuando finaliza el turno.

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

La forma más rápida de ser derrotado GunClubPlayersin hacer movimientos inválidos es la secuencia 0=, porque la bala dispara a través del deflector térmico. La repetición es

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

El torneo

El torneo sigue el formato "Último jugador en pie". En un torneo, todos los envíos válidos (incluido el GunClubPlayer) se colocan en un grupo. A cada envío se le asigna un identificador aleatorio pero único que permanecerá igual durante todo el torneo. Durante cada ronda:

  • Cada envío comienza con 0 puntos y jugará 100 duelos contra cualquier otro envío.
  • Cada duelo victorioso otorgará 1 punto; dibujar y perder dan 0 puntos.
  • Al final de la ronda, los envíos con los puntos mínimos abandonan el torneo. En caso de empate, el jugador con la menor cantidad de puntos ganados desde el comienzo del torneo se irá.
  • Si queda más de un jugador, comenzará la siguiente ronda.
  • Los puntos NO se transfieren a la siguiente ronda.

Sumisión

Enviarás un jugador por respuesta. Puede enviar varios archivos para un jugador, siempre que NO interfieran con otras presentaciones. Para mantener las cosas fluyendo, por favor:

  • Nombre su archivo de encabezado principal como <Custom>Player.hpp,
  • Nombre sus otros archivos como <Custom>Player*.*, por ejemplo, MyLittlePlayer.txtsi el nombre de su clase es MyLittlePlayer, o EmoPlayerHates.cppsi su nombre de clase es EmoPlayer.
  • Si su nombre contiene Shootero palabras similares que se ajustan al contexto de este torneo, no necesita agregar Playeral final. Si cree firmemente que su nombre de envío funciona mejor sin el sufijo Player, tampoco necesita agregarlo Player.
  • Asegúrese de que su código se pueda compilar y vincular en Windows.

Puede comentar para pedir una aclaración o para detectar lagunas. ¡Espero que disfrutes de este duelo de armas futurista y te deseo un feliz año nuevo!

Aclaración

  • Se le permite tener un comportamiento aleatorio.
  • Se permiten acciones no válidas (disparar cuando la munición cargada no es suficiente).
  • Si un jugador realiza una entrada no válida, su arma explotará inmediatamente.
  • Puedes estudiar las respuestas.
  • Se le permite explícitamente registrar el comportamiento del oponente dentro de cada torneo.
  • Cada ronda, jugarás 100 duelos contra cada oponente; Sin embargo, el orden de los 100 duelos es aleatorio: no se garantiza que pelees con el mismo oponente 100 duelos seguidos.

Recursos adicionales

@flawr ha traducido la fuente de C ++ proporcionada a Java como referencia si desea enviar entradas de C ++.

Interfaz para envíos no C ++

Actualmente aceptado: Python 3, Java.

Siga una de las especificaciones a continuación:

Especificación de interfaz 1: código de salida

Su envío se ejecutará una vez por turno.

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.

Puede probar su presentación en PythonPlayer\y JavaPlayer\directorios.

Especificación de interfaz 2: stdin / stdout

(Crédito a H Walters)

Tu envío se ejecutará una vez por torneo.

Hay un requisito fijo para todas las entradas sobre cómo hacer E / S, ya que tanto stdin como stdout están conectados al controlador del torneo. Violar esto podría llevar a un punto muerto. Todas las entradas DEBEN seguir este algoritmo EXACTO (en pseudocódigo):

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

Aquí, M es uno de 0, 1, 2, -, o =para load / bullet / plasma / metal / thermal. PROCESO significa responder opcionalmente a lo que hizo tu oponente (incluido el seguimiento de la munición de tu oponente si estás haciendo esto). Tenga en cuenta que la acción del oponente también es una de '0', '1', '2', '-' o '=', y está en el segundo carácter.

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

El torneo durará hasta el 1 de febrero de 2017 a menos que se indique lo contrario.

Frenesí Li
fuente
15
Impresionante primer desafío, por cierto!
Martin Ender
3
Si está dispuesto a ejecutar otros idiomas, puede permitir una Playerimplementación que invoque otro proceso para calcular el turno actual. Eso permitiría a las personas participar en cualquier idioma que esté dispuesto a ejecutar en su máquina.
Martin Ender
55
Aleatoriedad permitida? (No turnos completamente al azar, solo una elección de acción 50/50 en una determinada situación)
FlipTack
2
Punto técnico; "Debe heredar Player::fight" / "puede heredar Player::perceive" ... en ambos casos, el término es anular , no heredar .
H Walters
3
Creo que tiene un error GunDuel.hpp, ambos validAy validBusoactionA
AlexRacer

Respuestas:

9

MontePlayer

Este jugador utiliza el algoritmo de búsqueda de árbol de Monte Carlo desacoplado UCT para decidir qué elecciones debe hacer. Realiza un seguimiento de lo que hace el enemigo para predecir sus acciones. Simula al enemigo como sí mismo si carece de datos.

Este bot funciona muy bien contra cualquier otro bot excepto cβ. En un duelo de 10000 contra cβ, Monte ganó 5246 duelos. Con un poco de matemática, eso significa que Monte ganará un duelo contra cβ 51.17% al 53.74% del tiempo (99% de confianza).

#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__
El numero uno
fuente
25

los BlackHatPlayer

El BlackHat Player sabe que las balas y los escudos son cosa del pasado; las guerras reales las ganan aquellos que pueden hackear los programas del oponente.

Entonces, se pone un escudo de metal fijo y comienza a hacer lo suyo.

La primera vez que se le pide fight, intenta localizar a su enemigo en la memoria. Dada la estructura de la arena de combate, es casi seguro que el compilador terminará poniendo su dirección (envuelta en una unique_ptr) y la del oponente solo una al lado de la otra.

Entonces, el BlackHat camina cuidadosamente por la pila, usando algunas heurísticas simples para asegurarse de que no se desborde, hasta que encuentre un puntero para sí mismo; luego verifica si los valores en las posiciones adyacentes son plausiblemente su oponente - dirección similar, dirección similar de la tabla, plausible typeid.

Si logra encontrarlo, se chupa los sesos y los reemplaza con los de un idiota excéntrico. En la práctica, esto se hace reemplazando el puntero del oponente a la vtable con la dirección de la Idiotvtable, un jugador tonto que siempre dispara.

Si todo esto tiene éxito (y en mis pruebas - gcc 6 en Linux 64 bit, MinGW 4.8 en wine 32 bit - esto funciona de manera bastante confiable), la guerra está ganada. Lo que hizo el oponente en la primera ronda no es importante; en el peor de los casos, nos disparó, y teníamos el escudo de metal puesto.

De ahora en adelante, tenemos un idiota simplemente disparando; siempre tenemos nuestro escudo encendido, así que estamos protegidos, y explotará en 1 a 3 rondas (dependiendo de lo que hizo el bot original en su primera fightllamada).


Ahora: estoy casi seguro de que esto debería ser descalificado de inmediato, pero es curioso que no estoy violando explícitamente ninguna de las reglas mencionadas anteriormente:

Lo que NO debes hacer

  • NO debes usar ningún método directo para reconocer a tu oponente que no sea el identificador de oponente dado, que es completamente aleatorio al comienzo de cada torneo. Solo se te permite adivinar quién es un jugador a través de su juego dentro de un torneo.

BlackHat no trata de reconocer al oponente; en realidad, es completamente irrelevante quién es el oponente, dado que su cerebro es reemplazado de inmediato.

  • NO debe anular ningún método en la clase Player que no se declare virtual.
  • NO debe declarar ni inicializar nada en el ámbito global.

Todo sucede localmente a la fightfunción virtual.


// BlackHatPlayer.hpp

#ifndef __BLACKHAT_PLAYER_HPP__
#define __BLACKHAT_PLAYER_HPP__

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

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

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

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

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

        // Our adversary
        Player *c = nullptr;

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

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

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

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

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

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

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

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

#endif // !__BLACKHAT_PLAYER_HPP__
Matteo Italia
fuente
66
@TheNumberOne: también, según el primer comentario (y el más votado) al hilo de lagunas: "Las lagunas son parte de lo que hace que el juego sea interesante. Incluso las más comunes pueden ser divertidas o inteligentes, según el contexto". En mi opinión, esto es original (al menos, nunca vi algo similar aquí) y decentemente interesante, en cuanto a ingeniería; Por eso lo compartí aquí.
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 siempre aumenta nuestro conocimiento de las lagunas estándar :-)
Frenzy Li
2
@HWalters: Creo que tendré que cambiarme a #pragma once;-)
Matteo Italia
3
Parece bastante simple ejecutar a cada jugador en un proceso separado y usar enchufes para comunicarse con el árbitro.
Jasen
19

Luego, la más temida de todas las criaturas, ha estado en el infierno y de regreso y ha luchado con literalmente 900000 otros robots , es ...

los BotRobot

BotRobot fue nombrado, entrenado y construido automáticamente por un algoritmo genético muy básico.

Dos equipos de 9 se formaron uno contra el otro, en cada generación, cada robot del equipo 1 se enfrenta a cada robot del equipo 2. Los robots con más victorias que pérdidas, mantuvieron su memoria, el otro, volvieron al último paso , y tuve la oportunidad de olvidar algo, espero que sea malo. Los bots mismos son tablas de búsqueda glorificadas, donde si encuentran algo que no habían visto antes, simplemente elegirían una opción válida aleatoria y la guardarían en la memoria. La versión C ++ no hace esto, debería haberlo aprendido . Como se dijo anteriormente, los bots ganadores mantienen esta nueva memoria encontrada, ya que claramente funcionó. Los bots que pierden no lo hacen, y mantienen lo que comenzaron.

Al final, las peleas de bots fueron bastante cercanas, rara vez estancadas. El ganador fue elegido de un grupo de los dos equipos después de la evolución, que fue de 100000 generaciones.

BotRobot, con su nombre HERMOSO y generado aleatoriamente , fue el afortunado.

Generador

bot.lua

Revisión: Aunque el robot era bastante inteligente contra sí mismo y otros robots generados de manera similar, demostró ser bastante inútil en las batallas reales. Entonces, regenere su cerebro contra algunos de los bots ya creados.

El resultado, como se puede ver fácilmente, es un cerebro mucho más complejo, con opciones para que el jugador enemigo tenga 12 municiones.

No estoy seguro de contra qué estaba luchando, eso consiguió hasta 12 municiones, pero algo lo hizo.

Y, por supuesto, el producto terminado ...

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

Me gusta C ++ ahora ...

Un taco
fuente
@FrenzyLi No estoy seguro de cómo no me di cuenta de eso, arreglándolo ahora.
ATaco
Bueno, después de esta actualización, el bot parece tener una apertura fija de 00.
Frenesí Li
Ahora veo por qué ... "1: 1" da "0".
Frenesí Li
1
varios jugadores aquí han fijado toda su juego basado en turnos, así que no creo que una apertura fija debería ser un problema
EIS
10

CBetaPlayer (cβ)

Equilibrio aproximado de Nash.

Este bot es matemática elegante con un contenedor de código.

Podemos replantear esto como un problema de teoría de juegos. Denota una victoria por +1 y una pérdida por -1. Ahora deje que B (x, y) sea el valor del juego donde tenemos x munición y nuestro oponente tiene y munición. Tenga en cuenta que B (a, b) = -B (b, a) y, por lo tanto, B (a, a) = 0. Para encontrar los valores de B en términos de otros valores de B, podemos calcular el valor de la matriz de pagos. Por ejemplo, tenemos que B (1, 0) viene dado por el valor del siguiente subjuego:

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

(He eliminado las opciones "malas", es decir, las que están estrictamente dominadas por las soluciones existentes. Por ejemplo, no intentaríamos disparar plasma ya que solo tenemos 1 munición. Del mismo modo, nuestro oponente nunca usaría un deflector térmico, ya que nunca dispararemos plasma.)

La teoría de juegos nos permite saber cómo encontrar el valor de esta matriz de pagos, asumiendo ciertas condiciones técnicas. Obtenemos que el valor de la matriz anterior es:

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

Continuando con todos los juegos posibles y observando que B (x, y) -> 1 como x -> infinito con y fijo, podemos encontrar todos los valores de B, lo que a su vez nos permite calcular los movimientos perfectos.

Por supuesto, la teoría rara vez se alinea con la realidad. Resolver la ecuación incluso para valores pequeños de x e y rápidamente se vuelve demasiado complicado. Para lidiar con esto, introduje lo que llamo la aproximación cβ. Hay 7 parámetros para esta aproximación: c0, β0, c1, β1, c, β y k. Supuse que los valores B tomaron la siguiente forma (primero las formas más específicas):

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)

Algún razonamiento aproximado sobre por qué elegí estos parámetros. Primero supe que definitivamente quería lidiar con tener 0, 1 y 2 o más municiones por separado, ya que cada una abre diferentes opciones. También pensé que una función de supervivencia geométrica sería la más apropiada, porque el jugador defensivo esencialmente está adivinando qué movimiento hacer. Pensé que tener 2 o más municiones era básicamente lo mismo, así que me concentré en la diferencia. También quería tratar a B (1, 0) como un caso súper especial porque pensé que aparecería mucho. El uso de estas formas aproximadas simplificó enormemente los cálculos de los valores B.

Aproximadamente resolví las ecuaciones resultantes para obtener cada valor B, que luego puse de nuevo en la matriz para obtener las matrices de pago. Luego, usando un solucionador de programación lineal, encontré las probabilidades óptimas para hacer cada movimiento y las metí en el programa.

El programa es una tabla de búsqueda glorificada. Si ambos jugadores tienen entre 0 y 4 municiones, usa la matriz de probabilidad para determinar aleatoriamente qué movimiento debe hacer. De lo contrario, intenta extrapolar en función de su tabla.

Tiene problemas contra los estúpidos bots deterministas, pero le va bastante bien contra los bots racionales. Debido a toda la aproximación, esto ocasionalmente perderá para StudiousPlayer cuando realmente no debería.

Por supuesto, si volviera a hacer esto, probablemente intentaría agregar parámetros más independientes o tal vez una mejor forma de respuesta y llegar a una solución más exacta. También (intencionalmente) ignoré el límite de giro, porque hacía las cosas más difíciles. Se podría hacer una modificación rápida para disparar siempre plasma si tenemos suficiente munición y no quedan suficientes giros.

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

#ifndef __CBETA_PLAYER_HPP__
#define __CBETA_PLAYER_HPP__

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

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

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

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

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

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

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

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

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

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


};

#endif // !__CBETA_PLAYER_HPP__
George V. Williams
fuente
Como no pasa un parámetro GetRandomDouble, puede eliminar el argumento max.
Frenesí Li
@FrenzyLi, ¡gracias!
George V. Williams
¿Te importaría agregar un poco más de información sobre tu reproductor, como cómo llegaste a la probabilidad ... tensor?
Frenesí Li
2
Me encanta este robot. Creo que SP tiene la ventaja hasta ahora solo debido al determinismo de las otras entradas; cuantos más bots aleatorios (sin ponderación óptima) se agreguen, mejores serán las tarifas de CBP. Esto está respaldado por pruebas; en mis pruebas internas con los sospechosos habituales, SP siempre gana con CBP segundo ... sin embargo, en un mini concurso que involucra CBP, SP y FP, CBP se adelanta el 55% del tiempo, con SP y FP funcionando de manera uniforme.
H Walters
1
Por cierto, esta es una aproximación impresionantemente precisa del equilibrio nash. Monte no trata de encontrar la estrategia de equilbrio, sino el mejor movimiento contra cualquier oponente. El hecho de que gane solo el 52% por ciento de los duelos entre él y cβ significa que cβ está bastante cerca del equilibrio nash.
TheNumberOne
8

Me falta el comentario en todas partes, así que todavía no puedo hacer mis preguntas. Así que este es un jugador muy básico para ganar contra el primer bot.

[Editar] Gracias, ahora el estado anterior ya no es cierto, pero creo que es mejor mantenerlo para que podamos entender el contexto de este bot.

los Opportunist

El oportunista frecuenta el mismo club de armas que los GunClubPlayers, sin embargo, apostó a un recién llegado que podía vencer a todos los GunClubPlayers. Así que explota el hábito que ha notado por mucho tiempo y se obliga a no disparar, sino que espera un poco para ganar.

#ifndef __OPPORTUNIST_PLAYER_HPP__
#define __OPPORTUNIST_PLAYER_HPP__

#include <string>
#include <vector>

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

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

los BarricadePlayer

El jugador de la barricada carga una bala en la primera ronda, luego mantiene un escudo apropiado (todavía un poco aleatorio). También carga otro tiro cada 5ta ronda. Cada ronda, hay un 15% de posibilidades de ignorar el algoritmo (excepto para recargar el primer turno) y disparar una bala. Cuando el enemigo no tiene munición, se carga. Si de alguna manera todo sale mal, oh chico, solo dispara.

Cambios más recientes:

Números aleatorios mejorados (gracias Frenzy Li).

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

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

#ifndef __BARRICADE_PLAYER_HPP__
#define __BARRICADE_PLAYER_HPP__

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

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

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

        }
        return bullet();
    }
};

#endif // !__BARRICADE_PLAYER_HPP__
devRicher
fuente
1
¿Quieres al menos verificar si hay munición antes de disparar?
Pavel
8
No. Vivo la vida peligrosa. @Pavel
devRicher
1
¿No tiene sentido usar el deflector térmico en el segundo turno? No puedes cargar dos balas en el primer turno. Creo que incluso si quieres que sea aleatorio, debes evitar usar el escudo térmico si las balas del oponente son 1 (o menos).
Southpaw Hare
1
Gracias por todas las sugerencias, edité mucho la clase. @SouthpawHare
devRicher
2
Es getAmmoOpponentno getOpponentAmmo. También te estás perdiendo#endif // !__BARRICADE_PLAYER_HPP__
Azul
7

los StudiousPlayer

El jugador estudioso estudia a su presa, modelando a cada oponente que encuentra. Este jugador comienza con una estrategia básica, conducida aleatoriamente en lugares, y progresa a estrategias adaptativas simples basadas en medidas frecuentes de respuesta del oponente. Utiliza un modelo simple de oponentes basado en cómo reaccionan a las combinaciones de munición.

#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

Tenga en cuenta que esto rastrea la información sobre los oponentes de acuerdo con las reglas del desafío; vea el método "singleton de estilo Meyers" "scopedLs ()" en la parte inferior. (Algunas personas se preguntaban cómo hacer esto; ¡ahora lo sabes!)

H Walters
fuente
1
¡No tenía idea de que se llamaba el singleton de estilo Meyers hasta que vi esto!
Frenesí Li
1
No tome el término demasiado en serio, es una especie de abuso de términos, ya que el "singleton" es una instancia de plantilla en lugar de una estructura declarada, pero es la misma técnica.
H Walters
6

los GunClubPlayer

Cortado de la pregunta original. Esto sirve como un ejemplo de una implementación minimalista de un jugador derivado. Este jugador participará en el torneo.

A los GunClubPlayers les gusta ir al club de armas. Durante cada duelo, primero cargarían munición, luego dispararían una bala y repetirían este proceso hasta el final del duelo mundial . En realidad no les importa si ganan o no, y se centran exclusivamente en tener una experiencia agradable ellos mismos.

// 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__
Frenesí Li
fuente
1
No necesita el otro después de una declaración de devolución, ¿verdad? Sé que no es un código de golf, pero se siente mal.
Pavel
2
@Pavel Bueno, está bien, entonces ... es ... una especie de golf ahora.
Frenesí Li
5

los PlasmaPlayer

Al jugador de plasma le gusta disparar sus rayos de plasma. Intentará cargar y disparar tanto como sea posible. Sin embargo, mientras el oponente tenga munición de plasma, usará su escudo térmico (las balas son para los débiles).

#ifndef __PLASMA_PLAYER_HPP__
#define __PLASMA_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

#endif // !__PLASMA_PLAYER_HPP__
Brian J
fuente
@FrenzyLi gracias por el constructor! Mi C ++ está un poco oxidado y no tengo un compilador en esta máquina.
Brian J
¡De nada! Todavía estoy agregando más código (imprimir marcador, leer script externo, etc.) al proyecto y es muy afortunado que ninguno de los envíos se haya roto todavía.
Frenesí Li
Esto funcionará bien para cualquier oponente aparte de GunClub. Sí, matará al SadisticShooter (el mejor). @BrianJ
devRicher
5

El muy SadisticShooter

Prefiere verte sufrir que matarte. No es estúpido y se cubrirá según sea necesario.

Si eres completamente aburrido y predecible, él te matará de inmediato.

// 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__
Azul
fuente
Veo que lo arreglaste.
devRicher
4

los TurtlePlayer

TurtlePlayeres un cobarde Pasa la mayor parte del tiempo escondiéndose detrás de sus escudos, de ahí su nombre. A veces, puede salir de su caparazón (sin juego de palabras) y recibir un disparo, pero normalmente permanece acostado mientras el enemigo tiene munición.


Este bot no es particularmente bueno, sin embargo, cada KOTH necesita algunas entradas iniciales para que funcione :)

Las pruebas locales encontraron que esto gana contra ambos GunClubPlayery el Opportunist100% del tiempo. Una batalla en contra BotRobotPlayersiempre parecía resultar en un empate ya que ambos se esconden detrás de sus escudos.

#include "Player.hpp"

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

class TurtlePlayer final : public Player {

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

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

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

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

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

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

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

los DeceptivePlayer

El jugador engañoso intenta cargar dos balas y luego dispara una.

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

No codifico en c ++, por lo que cualquier mejora en el código será bienvenida.

Sxntk
fuente
Mi edición está en la definición de módulo y macro. No estoy seguro de que te gustará, pero ¿quizás DeceptivePlayersea ​​un nombre mejor?
Frenesí Li
@FrenzyLi Sí, me gusta, cambiaré el nombre
Sxntk
1
@Sxntk Me gusta la ironía en la que este jugador espera que las personas con 2 municiones disparen plasma, pero él mismo tendrá dos municiones y disparará una bala.
Brian J
@Sxntk Actualmente no tiene la posibilidad de no devolver nada. Un jugador tiene permitido más de dos municiones. Entonces, si tu oponente tiene más de 3 municiones, no realizarás ninguna acción. Podrías terminar con un arma explotada en alguna parte. (por supuesto, ese podría ser su plan maestro de todos modos :))
Brian J
@BrianJ Gracias, lo pensaré, mientras tanto, dejaré que el engañoso se vuelva loco y decida qué hacer cuando oponnent tenga más de 3 municiones
Sxntk
2

HanSoloPlayer

¡Dispara primero! Todavía estoy trabajando en revisarlo, pero esto es bastante bueno.

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

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

#ifndef __HAN_SOLO_PLAYER_HPP__
#define __HAN_SOLO_PLAYER_HPP__

#include "Player.hpp"

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

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

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

#endif // !__HAN_SOLO_PLAYER_HPP__
Conor O'Brien
fuente
2

los CamtoPlayer

CamtoPlayer HATES dibuja y saldrá de los bucles sin importar lo que haga falta. (excepto suicidio)

Es mi primer programa C ++ que hace algo, así que no lo juzgues demasiado.

Sé que podría ser mejor, pero no lo edites.
Si desea modificar el código simplemente comente una sugerencia.

#ifndef __CAMTO_HPP__
#define __CAMTO_HPP__

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

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

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

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

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

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

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

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

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

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

    }
};

#endif // !__CAMTO_HPP__
Benjamin Philippe
fuente
Te estás olvidando de un#endif // ! __CAMTO_HPP__
Azul
@muddyfish Gracias por decirme, ¡tengo varios símbolos que detienen el procesamiento del código! XD
Benjamin Philippe
Aún no aparece. Recomendaría deshacerse de las etiquetas HTML por completo y simplemente usar markdown (el botón "Code Sample" que tiene "{}" en él). Citar manualmente <>&es un dolor.
H Walters
@HWalters ¡Gracias por la sugerencia!
Benjamin Philippe
Gracias por participar. Y una cosa: elimínalo using namespace stdporque interfiere con el torneo. Si desea depurar, puede usar std::coutetc.
Frenzy Li
1

los SurvivorPlayer

El jugador superviviente se comporta de manera similar al jugador tortuga y barricada. Nunca tomará una acción que pueda conducir a su muerte y preferiría imponer un empate que perder la pelea.

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

#ifndef __SURVIVOR_PLAYER_HPP__
#define __SURVIVOR_PLAYER_HPP__

#include "Player.hpp"

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

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

#endif // !__SURVIVOR_PLAYER_HPP__
Turamarth
fuente
1

los FatedPlayer

Hecho por Clotho, anotado por Lachesis y asesinado por Atropos ; La única estrategia de este jugador es usar lo que sabe sobre munición para determinar qué acciones son razonables.

Sin embargo, no puede elegir la acción; esa parte se deja a los dioses.

#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

... porque me gustaría ver cómo se clasifica un jugador aleatorio.

H Walters
fuente
1

Jugador específico

SpecificPlayer sigue un plan simple de elegir algunas acciones aleatorias (válidas). Sin embargo, su característica principal es que busca ciertas situaciones analizando los recuentos de munición y el movimiento anterior del oponente.

Esta es la primera vez que escribo algo en C ++, y la primera vez que intento hacer algún tipo de escritura competitiva de bot. Así que espero que mi exiguo intento al menos haga algo interesante. :)

// 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__
Disnomio
fuente
1

NotSoPatientPlayer

La historia de su creación vendrá más tarde.

// NotSoPatientPlayer.hpp

#ifndef __NOT_SO_PATIENT_PLAYER_HPP__
#define __NOT_SO_PATIENT_PLAYER_HPP__

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

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

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

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

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

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

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

private:
    static long opponentMoves[TOTAL_PLAYERS][TOTAL_ACTIONS];
    int opponent;
    //When it becomes true, the bot starts shooting.
    bool burstMode = false;
    //turnForBullet1 and turnForBullet2,
    //the 2 turns in which the bot will shoot bullets
    int turnForBullet1 = -1, turnForBullet2 = -1;
    //For debugging purposes
    //Reminder: enum Result { DRAW, WIN, LOSS };
    static int currentRoundResults[TOTAL_PLAYERS][3], totalResults[TOTAL_PLAYERS][3];
};
long NotSoPatientPlayer::opponentMoves[TOTAL_PLAYERS][TOTAL_ACTIONS] = { { 0 } };
int NotSoPatientPlayer::currentRoundResults[TOTAL_PLAYERS][3] = { { 0 } };
int NotSoPatientPlayer::totalResults[TOTAL_PLAYERS][3] = { { 0 } };
#endif // !__NOT_SO_PATIENT_PLAYER_HPP__
AlexRacer
fuente
"La historia de su creación vendrá después" han pasado más de 3 meses :)
HyperNeutrino