Duel Senjata Futuristik


73

The Latar Belakang Masa Depan

Di tahun 2017, Anda dan lawan Anda akan berhadapan dalam pertempuran senjata futuristik di mana hanya satu yang bisa bertahan. Apakah Anda cukup berpengalaman untuk mengalahkan lawan Anda? Sekarang adalah waktunya untuk memoles keterampilan meriam Anda dalam bahasa pemrograman favorit Anda dan berjuang melawan segala rintangan!

Hasil Turnamen

Turnamen ini berakhir pada UTC pagi Feburary 2 nd , 2017. Berkat kontestan kami, kami telah memiliki turnamen futuristik menarik!

MontePlayer adalah pemenang akhir setelah pertarungan dekat dengan CBetaPlayer dan StudiousPlayer. Tiga duel guen top telah mengambil foto peringatan:

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

Selamat untuk para pemenang! Papan peringkat terperinci terlihat di dekat akhir tulisan ini.

Panduan Umum

  • Kunjungi repositori resmi untuk kode sumber yang digunakan dalam turnamen ini.
  • Entri C ++: silakan mewarisi Playerkelas.
  • Entri Non C ++: pilih satu antarmuka di bagian Antarmuka untuk pengiriman Non-C ++ .
  • Saat ini diizinkan bahasa yang bukan C ++: Python 3, Java.

Pertarungan

  • Setiap pemain mulai dengan pistol tanpa muatan yang dapat memuat amunisi dalam jumlah tak terbatas.
  • Setiap belokan, pemain akan secara bersamaan memilih dari salah satu tindakan berikut:
    • 0 - Muat 1 amunisi ke dalam pistol.
    • 1- Menembakkan peluru ke arah lawan; biaya 1 amunisi dimuat.
    • 2- Tembakan sinar plasma pada lawan; biaya 2 amunisi dimuat.
    • - - Pertahankan peluru yang masuk menggunakan pelindung logam.
    • = - Pertahankan sinar plasma yang masuk menggunakan deflektor termal.
  • Jika kedua pemain bertahan hidup setelah giliran ke- 100 , mereka berdua kelelahan sampai mati, yang menghasilkan hasil seri .

Seorang pemain kehilangan duel senjata jika mereka

  • Apakah tidak menggunakan perisai logam untuk mempertahankan sebuah peluru yang masuk.
  • Apakah tidak menggunakan deflektor termal mempertahankan sebuah plasma yang masuk.
  • Tembakan senjata tanpa memuat amunisi yang cukup, di mana senjata mereka akan meledak sendiri dan membunuh pemiliknya.

Peringatan

Menurut Manual untuk Pemilik Gun Futuristik :

  • Perisai logam TIDAK BISA bertahan dari sinar plasma yang masuk. Demikian juga, deflektor termal TIDAK BISA bertahan dari peluru yang masuk.
  • Sinar plasma mengalahkan peluru (karena peluru membutuhkan lebih banyak amunisi). Oleh karena itu, jika seorang pemain menembakkan sinar plasma pada lawan yang menembakkan peluru pada belokan yang sama, lawan terbunuh.
  • Jika kedua pemain menembakkan peluru satu sama lain dalam belokan yang sama, peluru membatalkan dan kedua pemain bertahan. Demikian juga, jika kedua pemain menembakkan sinar plasma satu sama lain dalam belokan yang sama, kedua pemain bertahan.

Perlu dicatat bahwa:

  • Anda TIDAK akan mengetahui tindakan lawan Anda secara bergantian sampai berakhir.
  • Membengkokkan sinar plasma dan melindungi peluru TIDAK akan membahayakan lawan Anda.

Oleh karena itu, ada total 25 kombinasi tindakan yang valid setiap belokan:

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

Contoh Duel

Ini duel yang pernah kumiliki dengan seorang teman. Saat itu, kami tidak tahu banyak tentang pemrograman, jadi kami menggunakan gerakan tangan dan memberi sinyal dengan kecepatan dua putaran per detik. Dari kiri ke kanan, tindakan kami pada gilirannya:

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

Sesuai aturan di atas, saya kalah. Apakah kamu melihat mengapa? Itu karena aku menembakkan sinar plasma terakhir ketika aku hanya punya 1 amunisi, menyebabkan senjataku meledak.


C ++ Player

Anda , sebagai programmer futuristik yang beradab, tidak akan langsung menangani senjata. Alih-alih, Anda membuat kode Playeryang bertempur melawan yang lain. Dengan mewarisi kelas secara publik di proyek GitHub, Anda dapat mulai menulis legenda urban Anda.

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

Apa yang harus atau dapat Anda lakukan

  • Anda harus mewarisi Playerkelas melalui warisan publik dan menyatakan final kelas Anda.
  • Anda harus mengganti Player::fight, yang mengembalikan yang valid Player::Actionsetiap kali dipanggil.
  • Secara opsional, menimpa Player::perceivedan Player::declareduntuk mengawasi tindakan lawan Anda dan melacak kemenangan Anda.
  • Secara opsional, gunakan anggota statis pribadi dan metode di kelas turunan Anda untuk melakukan perhitungan yang lebih kompleks.
  • Secara opsional, gunakan pustaka standar C ++ lainnya.

Apa yang tidak boleh Anda lakukan

  • Anda TIDAK boleh menggunakan metode langsung apa pun untuk mengenali lawan Anda selain pengenal lawan yang diberikan, yang dikocok di awal setiap turnamen. Anda hanya bisa menebak siapa pemain melalui permainan-permainan mereka dalam suatu turnamen.
  • Anda TIDAK boleh mengganti metode apa pun di Playerkelas yang tidak dinyatakan virtual.
  • Anda TIDAK boleh mendeklarasikan atau menginisialisasi apa pun dalam lingkup global.
  • Sejak debut (sekarang didiskualifikasi) BlackHatPlayer, pemain TIDAK diizinkan untuk mengintip atau memodifikasi kondisi lawan Anda.

Contoh duel

Proses duel senjata dilakukan menggunakan GunDuelkelas. Untuk contoh pertarungan, lihat Source.cppdi bagian Memulai duel .

Kami memamerkan GunClubPlayer, HumanPlayerdan GunDuelkelas, yang dapat ditemukan di Tournament\direktori repositori.

Dalam setiap duel, GunClubPlayerakan memuat peluru; tembak itu; bilas dan ulangi. Selama setiap belokan, HumanPlayerakan meminta Anda untuk bermain melawan lawan. Kontrol keyboard Anda adalah karakter 0, 1, 2, -dan =. Di Windows, Anda dapat menggunakan HumanPlayeruntuk men-debug kiriman Anda.

Memulai duel

Ini adalah bagaimana Anda dapat men-debug pemain Anda melalui konsol.

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

Contoh Game

Jumlah putaran paling tidak yang Anda butuhkan untuk mengalahkan GunClubPlayeradalah 3. Inilah ulangan dari bermain 0-1melawan GunClubPlayer. Angka dalam paranthesis adalah jumlah amunisi yang dimuat untuk setiap pemain saat belokan berakhir.

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

Cara tercepat untuk dikalahkan dengan GunClubPlayertanpa membuat gerakan yang tidak valid adalah urutannya 0=, karena peluru menembak menembus deflektor termal. Replaynya adalah

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

Turnamen

Turnamen ini mengikuti format "Last Player Standing". Dalam sebuah turnamen, semua pengiriman yang valid (termasuk GunClubPlayer) ditempatkan di kumpulan. Setiap pengiriman diberikan pengidentifikasi acak namun unik yang akan tetap sama selama seluruh turnamen. Selama setiap putaran:

  • Setiap pengiriman dimulai dengan 0 poin dan akan bermain 100 duel melawan setiap pengiriman lainnya.
  • Setiap duel yang menang akan memberikan 1 poin; menggambar dan kalah memberi 0 poin.
  • Di akhir babak, pengajuan dengan poin minimum meninggalkan turnamen. Dalam hal seri, pemain dengan jumlah poin paling sedikit yang diperoleh sejak awal turnamen akan pergi.
  • Jika lebih dari satu pemain tersisa, babak selanjutnya akan dimulai.
  • Poin TIDAK terbawa ke babak selanjutnya.

pengajuan

Anda akan mengirimkan satu pemain per jawaban. Anda dapat mengirim beberapa file untuk pemain, selama mereka TIDAK mengganggu pengiriman lainnya. Agar hal-hal terus mengalir, silakan:

  • Beri nama file header utama Anda sebagai <Custom>Player.hpp,
  • Beri nama file Anda yang lain <Custom>Player*.*, misalnya MyLittlePlayer.txtjika nama kelas Anda MyLittlePlayer, atau EmoPlayerHates.cppjika nama kelas Anda EmoPlayer.
  • Jika nama Anda mengandung Shooteratau kata-kata serupa yang sesuai dengan konteks turnamen ini, Anda tidak perlu menambahkan Playerdi akhir. Jika Anda merasa yakin bahwa nama kiriman Anda berfungsi lebih baik tanpa akhiran Player, Anda juga tidak perlu menambahkan Player.
  • Pastikan kode Anda dapat dikompilasi dan ditautkan di bawah Windows.

Anda dapat berkomentar untuk meminta klarifikasi atau untuk menemukan celah. Semoga Anda menikmati Duel Senjata Futuristik ini dan mengucapkan Selamat Tahun Baru!

Klarifikasi

  • Anda diizinkan memiliki perilaku acak.
  • Tindakan tidak valid (menembak ketika amunisi yang dimuat tidak cukup) diizinkan.
  • Jika seorang pemain membuat input yang tidak valid, pistol mereka akan segera meledak.
  • Anda diizinkan mempelajari jawabannya.
  • Anda secara eksplisit diizinkan untuk merekam perilaku lawan dalam setiap turnamen.
  • Setiap putaran, Anda akan memainkan 100 duel melawan setiap lawan; urutan 100 duel, bagaimanapun, adalah acak - Anda tidak dijamin untuk melawan 100 duel lawan yang sama berturut-turut.

Sumber daya tambahan

@ flawr telah menerjemahkan sumber C ++ yang disediakan ke Jawa sebagai referensi jika Anda ingin mengirimkan entri C ++.

Antarmuka untuk Pengajuan Non-C ++

Saat ini diterima: Python 3, Java.

Silakan ikuti salah satu spesifikasi di bawah ini:

Spesifikasi antarmuka 1: kode keluar

Kiriman Anda akan berjalan sekali per giliran.

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.

Anda dapat menguji kiriman PythonPlayer\dan JavaPlayer\direktori Anda.

Spesifikasi antarmuka 2: stdin / stdout

(Kredit ke H Walters)

Kiriman Anda akan berjalan sekali per turnamen.

Ada persyaratan tetap untuk semua entri tentang cara melakukan I / O, karena stdin dan stdout terhubung ke driver turnamen. Melanggar ini bisa menyebabkan kebuntuan. Semua entri HARUS mengikuti algoritma EXACT ini (dalam pseudo-code):

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

Di sini, F adalah salah satu dari 0, 1, 2, -, atau =untuk load / bullet / plasma / metal / thermal. PROSES berarti secara opsional menanggapi apa yang dilakukan lawan Anda (termasuk melacak amunisi lawan Anda jika Anda melakukan ini). Perhatikan bahwa tindakan lawan juga merupakan salah satu dari '0', '1', '2', '-', atau '=', dan berada dalam karakter kedua.

Papan Skor Akhir

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 |

Turnamen ini akan berlangsung hingga 1 Februari 2017 kecuali dinyatakan lain.


15
Omong-omong, tantangan pertama yang mengesankan!
Martin Ender

3
Jika Anda ingin menjalankan beberapa bahasa lain, Anda bisa mengizinkan Playerimplementasi yang memanggil proses lain untuk menghitung giliran saat ini. Itu akan memungkinkan orang untuk berpartisipasi dalam bahasa apa pun yang Anda senang jalankan di mesin Anda.
Martin Ender

5
Keacakan diizinkan? (Ternyata tidak sepenuhnya acak, hanya pilihan aksi 50/50 dalam situasi tertentu)
FlipTack

2
Titik teknis; "Anda harus mewarisi Player::fight" / "Anda bisa mewarisi Player::perceive" ... dalam kedua kasus, istilah tersebut menimpa , bukan mewarisi .
H Walters

3
Saya pikir Anda memiliki bug GunDuel.hpp, baik validAdan validBgunakanactionA
AlexRacer

Jawaban:


9

MontePlayer

Pemain ini menggunakan algoritma UCT Monte Carlo Tree Search Decoupled untuk memutuskan pilihan apa yang harus dibuat. Ini melacak apa yang dilakukan musuh untuk memprediksi tindakannya. Ini mensimulasikan musuh sebagai dirinya sendiri jika tidak memiliki data.

Bot ini bekerja sangat baik terhadap setiap bot lain kecuali cβ. Dalam pertandingan duel 10.000 melawan cβ, Monte memenangkan 5246 duel. Dengan sedikit matematika, itu berarti bahwa Monte akan memenangkan duel melawan cβ 51.17% menjadi 53.74% dari waktu (kepercayaan 99%).

#ifndef __Monte_PLAYER_HPP__
#define __Monte_PLAYER_HPP__

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


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

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

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


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

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

    struct Stat
    {
        int wins;
        int attempts;

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

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

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

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

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

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


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

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

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

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

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

public:

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

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

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

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

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

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

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

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

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

        return (Action)move;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return game;
    }

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

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

        //Choose random opponent action.
        int opponentAction;

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

        validMoves = 0;

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

        return doTurn(game, myAction, opponentAction);
    }

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

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

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

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

        return best;

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

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

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

#endif // !__Monte_PLAYER_HPP__

25

Itu BlackHatPlayer

The BlackHat Player tahu bahwa peluru dan perisai adalah sesuatu dari masa lalu; perang yang sebenarnya dimenangkan oleh mereka yang bisa meretas program lawan.

Jadi, dia memakai perisai logam tetap dan mulai melakukan pekerjaannya.

Pertama kali dia diminta fight, dia mencoba melokalisasi musuhnya di memori. Mengingat struktur arena pertempuran, hampir pasti bahwa penyusun akhirnya akan menempatkan alamatnya (dibungkus dengan unique_ptr) dan salah satu lawan hanya bersebelahan.

Jadi, BlackHat berjalan dengan hati-hati tumpukan, menggunakan beberapa heuristik sederhana untuk memastikan tidak melemahkannya, sampai dia menemukan pointer ke dirinya sendiri; kemudian memeriksa apakah nilai-nilai di posisi yang berdekatan masuk akal lawannya - alamat yang sama, alamat yang sama dari vtable, masuk akal typeid.

Jika berhasil menemukannya, dia menghisap otaknya dan menggantikannya dengan orang-orang idiot. Dalam prakteknya, ini dilakukan dengan mengganti pointer lawan ke vtable dengan alamat Idiotvtable - pemain bodoh yang selalu menembak.

Jika ini semua berhasil (dan dalam pengujian saya - gcc 6 di Linux 64 bit, MinGW 4,8 pada wine 32 bit - ini bekerja dengan cukup andal), perang dimenangkan. Apa pun yang dilakukan lawan di babak pertama tidak penting - paling buruk dia menembak kami, dan kami memiliki perisai logam.

Mulai sekarang, kami punya orang bodoh yang hanya menembak; kami selalu menggunakan perisai kami, jadi kami terlindungi, dan dia akan meledak dalam 1 hingga 3 putaran (tergantung pada apa yang dilakukan bot asli pada fightpanggilan pertamanya ).


Sekarang: Saya hampir yakin bahwa ini harus segera didiskualifikasi, tetapi lucu bahwa saya tidak secara eksplisit melanggar aturan yang disebutkan di atas:

Apa yang tidak boleh Anda lakukan

  • Anda TIDAK boleh menggunakan metode langsung apa pun untuk mengenali lawan Anda selain pengenal lawan yang diberikan, yang sepenuhnya diacak pada awal setiap turnamen. Anda hanya bisa menebak siapa pemain melalui gameplay mereka dalam suatu turnamen.

BlackHat tidak mencoba mengenali lawan - sebenarnya, sama sekali tidak relevan siapa lawannya, mengingat otaknya segera diganti.

  • Anda TIDAK boleh mengganti metode apa pun di kelas Player yang tidak dinyatakan virtual.
  • Anda TIDAK boleh mendeklarasikan atau menginisialisasi apa pun dalam lingkup global.

Semuanya terjadi secara lokal ke fightfungsi 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__

6
@TheNumberOne: juga, sesuai komentar pertama (dan yang paling banyak dipilih) untuk utas celah: "Celah adalah bagian dari apa yang membuat permainan ini menarik. Bahkan yang umum pun bisa lucu atau pintar, tergantung pada konteksnya". IMO ini asli (setidaknya, saya tidak pernah melihat yang serupa di sini) dan cukup menarik, teknik-bijaksana; itu sebabnya saya membagikannya di sini.
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 selalu meningkatkan pengetahuan kami tentang celah standar :-)
Frenzy Li

2
@HWalters: Saya kira saya harus beralih ke #pragma once;-)
Matteo Italia

3
tampaknya cukup sederhana menjalankan setiap pemain dalam proses terpisah dan menggunakan soket untuk berkomunikasi dengan wasit.
Jasen

19

Selanjutnya, yang paling ditakuti dari semua makhluk, sudah ke neraka dan kembali dan bertarung dengan 900.000 bot lainnya , itu ...

Itu BotRobot

BotRobot dinamai, dilatih dan dibangun secara otomatis oleh algoritma Genetika yang sangat dasar.

Dua tim dari 9 didirikan melawan satu sama lain, dalam setiap generasi, masing-masing robot dari tim 1 ditempatkan melawan masing-masing robot tim 2. Robot dengan lebih banyak kemenangan daripada kerugian, menyimpan ingatannya, yang lain, dikembalikan kembali ke langkah terakhir , dan punya kesempatan untuk melupakan sesuatu, semoga buruk. Bot itu sendiri adalah tabel pencarian yang dimuliakan, di mana jika mereka menemukan sesuatu yang belum pernah mereka lihat sebelumnya, mereka hanya akan memilih opsi acak yang valid dan menyimpannya ke memori. Versi C ++ tidak melakukan ini, itu seharusnya sudah dipelajari . Seperti yang dinyatakan sebelumnya, bot yang menang menyimpan memori yang baru ditemukan ini, karena jelas itu berfungsi. Kehilangan bot tidak, dan simpan apa yang mereka mulai.

Pada akhirnya, pertarungan bot cukup dekat, jarang terjadi kebuntuan. Pemenangnya dipilih dari kumpulan dua tim pasca evolusi, yaitu 100.000 generasi.

BotRobot, dengan nama yang dihasilkan secara acak dan INDAH , adalah yang beruntung.

Generator

bot.lua

Revisi: Meskipun robot itu cukup pintar terhadap dirinya sendiri dan robot lain yang dihasilkan serupa, ia terbukti cukup berguna dalam pertempuran yang sebenarnya. Jadi, saya meregenerasi otaknya melawan beberapa bot yang sudah dibuat.

Hasilnya, seperti dapat dengan mudah dilihat, adalah otak yang jauh lebih kompleks, dengan opsi hingga pemain musuh memiliki 12 amunisi.

Saya tidak yakin apa yang dia lawan hingga mencapai 12 amunisi, tetapi sesuatu berhasil.

Dan tentu saja, produk jadi ...

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

Saya benci C ++ sekarang ...


@FrenzyLi Tidak yakin bagaimana saya tidak menyadarinya, memperbaikinya sekarang.
ATaco

Nah, setelah pembaruan ini, bot tampaknya memiliki pembukaan tetap 00.
Frenzy Li

Saya mengerti mengapa sekarang ... "1: 1" memberikan "0".
Frenzy Li

1
beberapa pemain di sini telah memperbaiki seluruh permainan mereka berdasarkan belokan, jadi saya tidak berpikir pembukaan tetap harus menjadi masalah
eis

10

CBetaPlayer (cβ)

Perkiraan Ekuilibrium Nash.

Bot ini hanya matematika mewah dengan pembungkus kode.

Kita bisa membingkai ulang ini sebagai masalah teori permainan. Nyatakan kemenangan dengan +1 dan kerugian -1. Sekarang mari B (x, y) menjadi nilai permainan di mana kita memiliki x amunisi dan lawan kita memiliki amunisi y. Perhatikan bahwa B (a, b) = -B (b, a) dan B (a, a) = 0. Untuk menemukan nilai B dalam hal nilai B lainnya, kita dapat menghitung nilai matriks hasil. Misalnya, kami memiliki B (1, 0) yang diberikan oleh nilai dari subgame berikut:

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

(Saya telah menghapus opsi "buruk", alias yang sangat didominasi oleh solusi yang ada. Misalnya, kami tidak akan mencoba menembak plasma karena kami hanya memiliki 1 amunisi. Demikian juga lawan kami tidak akan pernah menggunakan deflektor termal, karena kita tidak akan pernah menembak plasma.)

Teori permainan memberi tahu kami cara menemukan nilai matriks hasil ini, dengan asumsi kondisi teknis tertentu. Kami mendapatkan bahwa nilai dari matriks di atas adalah:

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

Melanjutkan semua permainan yang mungkin dan mencatat bahwa B (x, y) -> 1 sebagai x -> tanpa batas dengan y diperbaiki, kita dapat menemukan semua nilai B, yang pada gilirannya memungkinkan kita menghitung pergerakan sempurna!

Tentu saja, teori jarang sejalan dengan kenyataan. Memecahkan persamaan untuk nilai x dan y yang kecil dengan cepat menjadi terlalu rumit. Untuk mengatasi ini, saya memperkenalkan apa yang saya sebut pendekatan-c. Ada 7 parameter untuk perkiraan ini: c0, β0, c1, β1, c, β dan k. Saya berasumsi bahwa nilai B mengambil bentuk berikut (bentuk paling spesifik terlebih dahulu):

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)

Beberapa alasan kasar mengapa saya memilih parameter ini. Pertama saya tahu bahwa saya pasti ingin berurusan dengan memiliki amunisi 0, 1 dan 2 atau lebih secara terpisah, karena masing-masing membuka opsi yang berbeda. Juga saya pikir fungsi bertahan geometris akan menjadi yang paling tepat, karena pemain defensif pada dasarnya menebak langkah apa yang harus dilakukan. Saya pikir memiliki 2 atau lebih amunisi pada dasarnya sama, jadi saya fokus pada perbedaannya. Saya juga ingin memperlakukan B (1, 0) sebagai kasus yang sangat spesial karena saya pikir itu akan banyak muncul. Dengan menggunakan formulir perkiraan ini, perhitungan perhitungan nilai B sangat disederhanakan.

Saya kira-kira memecahkan persamaan yang dihasilkan untuk mendapatkan setiap nilai B, yang kemudian saya masukkan kembali ke dalam matriks untuk mendapatkan matriks hasil. Kemudian menggunakan pemecah pemrograman linier, saya menemukan probabilitas optimal untuk membuat setiap gerakan dan memasukkannya ke dalam program.

Program ini adalah tabel pencarian yang dimuliakan. Jika kedua pemain memiliki antara 0 dan 4 amunisi, ia menggunakan matriks probabilitas untuk secara acak menentukan gerakan yang harus dilakukan. Kalau tidak, ia mencoba untuk mengekstrapolasi berdasarkan tabelnya.

Ini memiliki masalah terhadap bot deterministik bodoh, tetapi cukup baik terhadap bot rasional. Karena semua perkiraan, ini terkadang akan kalah dari StudiousPlayer padahal sebenarnya tidak seharusnya.

Tentu saja, jika saya melakukan ini lagi saya mungkin akan mencoba untuk menambahkan parameter yang lebih independen atau mungkin bentuk ansatz yang lebih baik dan menghasilkan solusi yang lebih tepat. Juga saya (sengaja) mengabaikan turn-limit, karena itu membuat segalanya lebih sulit. Modifikasi cepat dapat dilakukan untuk selalu menembak plasma jika kita memiliki cukup amunisi dan tidak ada cukup putaran tersisa.

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

Karena Anda tidak memberikan parameter GetRandomDouble, Anda dapat menghapus argumen maks.
Frenzy Li

@FrenzyLi, wah, terima kasih!
George V. Williams

Maukah Anda menambahkan sedikit info lebih lanjut tentang pemain Anda, seperti bagaimana Anda sampai pada probabilitas ... tensor?
Frenzy Li

2
Saya suka bot ini. Saya pikir SP memiliki keunggulan sejauh ini hanya karena determinisme dari entri lain; semakin banyak bot acak (non-optimal tertimbang) ditambahkan, semakin baik tarif CBP. Ini didukung oleh pengujian; dalam tes internal saya dengan SP yang biasa dicurigai selalu menang dengan CBP kedua ... Namun, dalam kontes mini yang melibatkan CBP, SP, dan FP, CBP memotong 55% dari waktu sebelumnya, dengan SP dan FP bernasib merata.
H Walters

1
Ngomong-ngomong, ini adalah perkiraan akurat dari keseimbangan nash. Monte tidak mencoba menemukan strategi keseimbangan, tetapi langkah terbaik melawan lawan mana pun. Fakta itu hanya memenangkan 52% persen dari duel antara itu dan cβ berarti bahwa cβ cukup dekat dengan kesetimbangan nash.
TheNumberOne

8

Saya kekurangan komentar di mana-mana, jadi saya belum bisa mengajukan pertanyaan. Jadi ini adalah pemain yang sangat mendasar untuk menang melawan bot pertama.

[Sunting] Terima kasih, sekarang status sebelumnya tidak benar lagi, tetapi saya pikir lebih baik menyimpannya agar kita dapat memahami konteks bot ini.

Itu Opportunist

Sang oportunis sering mengunjungi klub senjata yang sama dengan GunClubPlayers, namun, dia bertaruh kepada pendatang baru bahwa dia bisa mengalahkan setiap GunClubPlayers. Jadi dia mengeksploitasi kebiasaan yang sudah lama dia perhatikan dan memaksakan dirinya untuk tidak menembak tetapi hanya menunggu sedikit untuk menang.

#ifndef __OPPORTUNIST_PLAYER_HPP__
#define __OPPORTUNIST_PLAYER_HPP__

#include <string>
#include <vector>

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

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

7

Itu BarricadePlayer

Barricade Player memuat peluru di babak pertama, lalu menyimpan perisai yang tepat (masih agak acak). Dia juga memuat tembakan lain setiap putaran ke-5. Setiap putaran, ada peluang 15% untuk mengabaikan algoritme (kecuali untuk mengisi ulang giliran pertama) dan menembakkan peluru. Ketika musuh tidak memiliki amunisi, itu dimuat. Jika entah bagaimana semuanya salah, oh nak, dia hanya menembak.

Perubahan terbaru:

Angka acak yang ditingkatkan (terima kasih Frenzy Li).

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

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

#ifndef __BARRICADE_PLAYER_HPP__
#define __BARRICADE_PLAYER_HPP__

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

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

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

        }
        return bullet();
    }
};

#endif // !__BARRICADE_PLAYER_HPP__

1
Apakah Anda ingin setidaknya memeriksa apakah ada amunisi sebelum menembak?
Pavel

8
Tidak, saya menjalani kehidupan yang berbahaya. @Pavel
devRicher

1
Bukankah tidak ada gunanya menggunakan deflektor termal pada belokan kedua? Anda tidak dapat memuat dua peluru pada belokan pertama. Saya pikir bahkan jika Anda ingin itu acak, Anda harus menghindari menggunakan pelindung termal jika peluru lawan adalah 1 (atau kurang).
Southpaw Hare

1
Terima kasih untuk semua saran, saya mengedit banyak kelas. @SouthpawHare
devRicher

2
Ini getAmmoOpponenttidak getOpponentAmmo. Anda juga ketinggalan#endif // !__BARRICADE_PLAYER_HPP__
Biru

7

Itu StudiousPlayer

The Studious Player mempelajari mangsanya, memodelkan setiap lawan yang ditemuinya. Pemain ini mulai dengan strategi dasar, digerakkan secara acak di beberapa tempat, dan berlanjut ke strategi adaptif sederhana berdasarkan ukuran respons lawan yang sering. Ini menggunakan model sederhana lawan berdasarkan bagaimana mereka bereaksi terhadap kombinasi amunisi.

#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

Perhatikan bahwa ini melacak informasi tentang lawan sesuai dengan aturan tantangan; lihat metode "Meyers style singleton" scoped "SavedLs ()" di bagian bawah. (Beberapa orang bertanya-tanya bagaimana melakukan ini; sekarang Anda tahu!)


1
Saya tidak tahu itu disebut singleton gaya Meyers sampai saya melihat ini!
Frenzy Li

1
Jangan menganggap istilah itu terlalu serius - ini semacam penyalahgunaan istilah, karena "singleton" lebih merupakan contoh instasiasi daripada struct yang dideklarasikan, tetapi tekniknya sama.
H Walters

6

Itu GunClubPlayer

Potong dari pertanyaan awal. Ini berfungsi sebagai contoh implementasi minimalis pemain yang diturunkan. Pemain ini akan berpartisipasi dalam turnamen.

Ini GunClubPlayerseperti pergi ke klub senjata. Selama setiap duel, pertama-tama mereka akan memuat amunisi, lalu menembakkan peluru, dan mengulangi proses ini sampai akhir duel dunia . Mereka sebenarnya tidak peduli apakah mereka menang atau tidak, dan hanya berfokus pada pengalaman yang menyenangkan.

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

#ifndef __GUN_CLUB_PLAYER_HPP__
#define __GUN_CLUB_PLAYER_HPP__

#include "Player.hpp"

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

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

#endif // !__GUN_CLUB_PLAYER_HPP__

1
Anda tidak perlu yang lain setelah pernyataan kembali, kan? Saya tahu ini bukan kode golf tetapi rasanya salah.
Pavel

2
@Pavel Yah, oke, jadi ... ini ... semacam golf sekarang.
Frenzy Li

5

Itu PlasmaPlayer

Pemain Plasma suka menembakkan baut plasma-nya. Dia akan mencoba memuat dan menembakkan sebanyak mungkin. Namun, saat lawan memiliki amunisi plasma, ia akan menggunakan pelindung termal (peluru untuk yang lemah).

#ifndef __PLASMA_PLAYER_HPP__
#define __PLASMA_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

#endif // !__PLASMA_PLAYER_HPP__

@FrenzyLi terima kasih untuk konstruktornya! C ++ saya sedikit berkarat, dan saya tidak memiliki kompiler pada mesin ini.
Brian J

Sama-sama! Saya masih menambahkan lebih banyak kode (cetak papan skor, baca skrip eksternal, dll.) Ke proyek dan sangat beruntung bahwa belum ada kiriman yang rusak.
Frenzy Li

Ini akan bekerja dengan baik untuk setiap lawan selain dari GunClub. Ya, itu akan membunuh SadisticShooter (terbaik). @BrianJ
devRicher

5

Sangat SadisticShooter

Dia lebih suka melihatmu menderita daripada membunuhmu. Dia tidak bodoh dan akan menutupi dirinya sesuai kebutuhan.

Jika Anda benar-benar membosankan dan dapat diprediksi, ia akan langsung membunuh Anda.

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

Saya melihat Anda memperbaikinya.
devRicher

4

Itu TurtlePlayer

TurtlePlayeradalah seorang pengecut. Dia menghabiskan sebagian besar waktu bersembunyi di balik perisainya - karena itulah namanya. Kadang-kadang, ia keluar dari cangkangnya (tidak ada permainan kata-kata yang dimaksudkan) dan memiliki tembakan, tetapi ia biasanya ia berbaring rendah sementara musuh memiliki amunisi.


Bot ini tidak terlalu bagus - namun, setiap KOTH membutuhkan beberapa entri awal untuk membuatnya berjalan :)

Pengujian lokal menemukan bahwa ini menang melawan keduanya GunClubPlayerdan Opportunist100% dari waktu. Pertempuran melawan BotRobotPlayersepertinya selalu menghasilkan hasil imbang karena keduanya bersembunyi di balik perisai mereka.

#include "Player.hpp"

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

class TurtlePlayer final : public Player {

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

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

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

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

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

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

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

4

Itu DeceptivePlayer

Pemain menipu mencoba untuk memuat dua peluru dan kemudian menembakkan satu.

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

Saya tidak kode dalam c ++ sehingga setiap perbaikan kode akan diterima.


Hasil edit saya ada di modulo dan definisi makro. Tidak yakin Anda akan menyukainya, tapi mungkin DeceptivePlayernama yang lebih baik?
Frenzy Li

@FrenzyLi Ya, saya suka, saya akan mengubah nama
Sxntk

1
@ Slntk Saya suka ironi di mana pemain ini mengharapkan orang-orang dengan 2 amunisi untuk menembak plasma, tetapi dirinya akan memegang dua amunisi dan menembakkan peluru.
Brian J

@ Sxntk Anda tidak memiliki kemungkinan untuk tidak mengembalikan apa pun saat ini. Seorang Pemain diizinkan lebih dari dua amunisi. Jadi, jika lawan Anda memiliki 3+ amunisi, Anda tidak mengambil tindakan. Anda mungkin berakhir dengan pistol yang meledak di suatu tempat. (tentu saja, itu bisa menjadi rencana induk Anda :))
Brian J

@BrianJ Terima kasih, saya akan memikirkannya, sementara itu saya akan membiarkan Deceptive menjadi gila dan memutuskan apa yang harus dilakukan ketika oponnent memiliki 3+ amunisi
Sxntk

2

HanSoloPlayer

Tembak dulu! Masih berusaha merevisinya, tapi ini cukup bagus.

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

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

#ifndef __HAN_SOLO_PLAYER_HPP__
#define __HAN_SOLO_PLAYER_HPP__

#include "Player.hpp"

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

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

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

#endif // !__HAN_SOLO_PLAYER_HPP__

2

Itu CamtoPlayer

CamtoPlayer membenci imbang dan akan keluar dari loop tidak peduli apa yang diperlukan. (kecuali untuk bunuh diri)

Ini adalah program C ++ pertama saya yang melakukan apa saja, jadi jangan menilai terlalu keras.

Saya tahu ini bisa lebih baik tapi tolong jangan mengeditnya.
Jika Anda ingin mengubah kode, cukup komentari saran.

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

Anda lupa a#endif // ! __CAMTO_HPP__
Biru

@muddyfish Terima kasih telah memberi tahu saya, saya memiliki berbagai simbol kurang dari yang menghentikan kode rendering! XD
Benjamin Philippe

Masih belum muncul. Saya akan merekomendasikan membuang tag HTML sama sekali dan hanya menggunakan penurunan harga (tombol "Contoh Kode" yang memiliki "{}" di atasnya). Mengutip secara manual itu menyebalkan <>&.
H Walters

@Halter Terima kasih atas tipnya!
Benjamin Philippe

Terima kasih telah berpartisipasi. Dan satu hal: tolong hapus using namespace stdkarena mengganggu turnamen. Jika Anda ingin men-debug, Anda bisa menggunakan std::coutdll.
Frenzy Li

1

Itu SurvivorPlayer

Survivor Player berperilaku serupa dengan Turtle and Barricade Player. Dia tidak akan pernah mengambil tindakan yang bisa menyebabkan kematiannya dan lebih suka memaksakan hasil seri daripada kalah.

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

#ifndef __SURVIVOR_PLAYER_HPP__
#define __SURVIVOR_PLAYER_HPP__

#include "Player.hpp"

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

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

#endif // !__SURVIVOR_PLAYER_HPP__

1

Itu FatedPlayer

Dibuat oleh Clotho, dicetak oleh Lachesis, dan dibunuh oleh Atropos ; satu-satunya strategi pemain ini adalah menggunakan apa yang ia ketahui tentang amunisi untuk menentukan tindakan mana yang masuk akal.

Namun, tidak bisa memilih tindakan; bagian itu diserahkan kepada para dewa.

#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

... karena saya ingin melihat bagaimana peringkat pemain secara acak.


1

Pemain Khusus

SpecificPlayer mengikuti rencana sederhana untuk memilih beberapa tindakan acak (valid). Namun fitur utamanya adalah melihat situasi tertentu dengan menganalisis jumlah amunisi dan gerakan lawan sebelumnya.

Ini adalah pertama kalinya saya menulis apa pun dalam C ++, dan pertama kali mencoba melakukan segala jenis penulisan bot yang kompetitif. Jadi saya berharap usaha saya yang sedikit setidaknya melakukan sesuatu yang menarik. :)

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

#ifndef __SPECIFIC_PLAYER_HPP__
#define __SPECIFIC_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

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

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

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

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

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

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

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

        return returnval;
    }

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

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

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

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

#endif // !__SPECIFIC_PLAYER_HPP__

1

NotSoPatientPlayer

Kisah penciptaannya akan datang nanti.

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

"Kisah penciptaannya akan datang kemudian" sudah lebih dari 3 bulan :)
HyperNeutrino
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.