미래의 총 결투


73

배경 미래

2017 년에, 당신과 상대는 오직 하나만 생존 할 수있는 미래형 총기 전투에서 맞서게됩니다. 있습니까 당신은 상대를 물리 칠 수있을만큼 경험? 이제 좋아하는 프로그래밍 언어로 총기 기술 을 연마 하고 모든 확률에 맞서 싸울 때입니다!

토너먼트 결과

2 월 2의 UTC 아침에 끝난이 대회 ND는 , 우리의 참가자에 2017 덕분에, 우리는 흥미로운 미래 토너먼트 있었다!

MontePlayer는 CBetaPlayer 및 StudiousPlayer와의 긴밀한 전투 후 최종 승자입니다. 3 명의 최고 guen 결투가 기념 사진을 찍었습니다.

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

우승자 축하합니다! 자세한 게시물은이 게시물의 끝 부분에서 볼 수 있습니다.

일반 안내

  • 이 토너먼트에 사용 된 소스 코드 는 공식 저장소 를 방문하십시오 .
  • C ++ 항목 : Player클래스 를 상속하십시오 .
  • 비 C ++ 항목 : 비 C ++ 제출을위한 인터페이스 섹션에서 하나의 인터페이스를 선택하십시오 .
  • 현재 허용되는 비 C ++ 언어 : Python 3, Java.

결투

  • 각 플레이어는 무한대의 탄약을 적재 할 수있는 언로드 건으로 시작합니다.
  • 매 턴마다 플레이어는 동시에 다음 행동 중 하나를 선택합니다.
    • 0 -총에 탄약 1 개를 넣습니다.
    • 1-상대방에게 총알을 발사; 탄약 1 개가 들어갑니다.
    • 2-상대방에게 플라즈마 빔을 발사; 탄약 2 개가 들어갑니다.
    • - -금속 실드를 사용하여 들어오는 총알을 방어하십시오.
    • = -열 편향기를 사용하여 들어오는 플라즈마 빔을 방어하십시오.
  • 100 번째 턴 후에도 두 선수가 모두 생존하면 모두 죽 습니다 .

플레이어 총 결투를 잃으면

  • 나요 NOT 들어오는 총알을 방어하기 위해 금속 실드를 사용합니다.
  • 나요 NOT 들어오는 플라즈마를 방어하기 위해 열 디플렉터를 사용합니다.
  • 탄약을 충분히 넣지 않은 상태에서 총을 발사하면 총이 스스로 폭발하여 주인을 죽일 수 있습니다.

경고

미래의 총기 소유자 매뉴얼에 따르면 :

  • 금속 실드 는 들어오는 플라즈마 빔으로부터 방어 할 수 없습니다 . 마찬가지로, 열 편향 기는 들어오는 총알을 막을 수 없습니다 .
  • 플라즈마 빔은 탄환을 압도합니다 (전포에는 탄약이 더 많이 필요하기 때문에). 따라서 플레이어가 같은 차례에 총알을 발사하는 상대방에게 플라즈마 빔을 발사하면 상대방이 죽습니다.
  • 두 플레이어가 같은 차례에 총알을 발사하면 총알이 취소되고 두 플레이어 모두 생존합니다. 마찬가지로 두 선수가 같은 차례에 서로 플라즈마 빔을 발사하면 두 선수 모두 생존합니다.

또한 주목할만한 점은 다음과 같습니다.

  • 당신은 상대의 행동이 끝날 때까지 한 번에 알 수 없습니다 .
  • 플라즈마 빔을 차단하고 총알을 막아도 상대에게 피해를주지 않습니다 .

따라서 턴마다 총 25 개의 유효한 액션 조합이 있습니다.

+-------------+---------------------------------------------+
|   Outcome   |               P L A Y E R   B               |
|    Table    +--------+-----------------+------------------+
| for Players | Load   | Bullet   Plasma | Metal    Thermal |
+---+---------+--------+--------+--------+--------+---------+
| P | Load    |        | B wins | B wins |        |         |
| L +---------+--------+--------+--------+--------+---------+
| A | Bullet  | A wins |        | B wins |        | A wins  |
| Y |         +--------+--------+--------+--------+---------+
| E | Plasma  | A wins | A wins |        | A wins |         |
| R +---------+--------+--------+--------+--------+---------+
|   | Metal   |        |        | B wins |        |         |
|   |         +--------+--------+--------+--------+---------+
| A | Thermal |        | B wins |        |        |         |
+---+---------+--------+--------+---------------------------+

Note: Blank cells indicate that both players survive to the next turn.

결투 예

여기 친구와 함께한 결투가 있습니다. 당시에는 프로그래밍에 대해 잘 몰랐기 때문에 손 동작을 사용하여 초당 2 회전의 속도로 신호를 보냈습니다. 왼쪽에서 오른쪽으로 우리의 행동은 차례로 이루어졌습니다.

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

위의 규칙에 따라, 나는졌다. 왜 그런지 알아? 탄약이 1 개만있을 때 최종 플라즈마 빔을 발사하여 총이 폭발하기 때문입니다.


C ++ 플레이어

문명화 된 미래의 프로그래머 인 당신 은 총을 직접 다루지 않을 것입니다. 대신, 당신 Player은 다른 사람들과 싸우는 a를 코딩합니다 . GitHub 프로젝트에서 클래스 를 공개적으로 상속함으로써 도시의 전설을 작성할 수 있습니다.

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

당신이 해야 하거나 할 수있는

  • 당신은 상속 할 필요가 Player공공 상속을 통해 클래스 및 클래스의 마지막을 선언합니다.
  • 를 호출 할 때마다 Player::fight유효한 값을 반환하는 override를 무시해야합니다 Player::Action.
  • 선택적으로, 상대방의 행동을 무시 Player::perceive하고 Player::declared감시하고 승리를 추적하십시오.
  • 선택적으로 파생 클래스에서 전용 정적 멤버 및 메서드를 사용하여보다 복잡한 계산을 수행 할 수 있습니다.
  • 선택적으로 다른 C ++ 표준 라이브러리를 사용하십시오.

하지 말아야 할 것

  • 당신은 있어야 하지 각 대회의 시작 부분에 셔플 주어진 상대 식별자가 아닌 다른 상대를 인식하는 직접적인 방법을 사용합니다. 토너먼트 내에서 누가 게임 플레이를하는지 추측 할 수 있습니다.
  • 당신은 있어야 하지 어떤 방법 오버라이드 (override) Player가상 선언되지 않은 클래스를.
  • 전역 범위에서 어떤 것도 선언하거나 초기화 해서는 안됩니다 .
  • (현재 실격) 데뷔 한 이래로 BlackHatPlayer플레이어는 상대방의 상태를 들여다 보거나 수정할 수 없습니다 .

결투의 예

총 결투 과정은 GunDuel클래스를 사용하여 수행됩니다 . 싸움의 예 는 결투 시작Source.cpp 섹션을 참조하십시오 .

우리는 전시 GunClubPlayer, HumanPlayer그리고 GunDuel클래스는에서 찾을 수있는 Tournament\저장소의 디렉토리.

각 결투에서 GunClubPlayer총알을로드합니다. 해고; 헹구고 반복하십시오. 매 턴마다 HumanPlayer상대와의 대전을 요구합니다. 키보드 컨트롤은 문자입니다 0, 1, 2, -=. Windows에서는 HumanPlayer제출을 디버그 하는 데 사용할 수 있습니다 .

결투 시작

콘솔을 통해 플레이어를 디버깅하는 방법입니다.

// Source.cpp
// An example duel between a HumanPlayer and GunClubPlayer.

#include "HumanPlayer.hpp"
#include "GunClubPlayer.hpp"
#include "GunDuel.hpp"

int main()
{
    // Total number of turns per duel.
    size_t duelLength = 100;

    // Player identifier 1: HumanPlayer.
    HumanPlayer human(2);
    // Player identifier 2: GunClubPlayer.
    GunClubPlayer gunClub(1);

    // Prepares a duel.
    GunDuel duel(human, gunClub, duelLength);
    // Start a duel.
    duel.fight();
}

게임 예

당신이 패배 할 필요가 회전의 최소 금액은 GunClubPlayer여기에 3입니다 재생에서 재생의 0-1에 대해 GunClubPlayer. 마비의 숫자는 턴이 끝났을 때 각 플레이어의 탄약 수입니다.

 :: Turn 0
    You [0/12/-=] >> [0] load ammo (1 ammo)
    Opponent selects [0] load ammo (1 ammo)
 :: Turn 1
    You [0/12/-=] >> [-] defend using metal shield (1 ammo)
    Opponent selects [1] fire a bullet (0 ammo)
 :: Turn 2
    You [0/12/-=] >> [1] fire a bullet (0 ammo)
    Opponent selects [0] load ammo (1 ammo)
 :: You won after 3 turns!
 :: Replay
    YOU 0-1
    FOE 010
Press any key to continue . . .

총알이 열 편향기를 통해 바로 쏘기 때문에 GunClubPlayer무효 한 움직임없이 패배하는 가장 빠른 방법 은 순서 0=입니다. 재생은

 :: Turn 0
    You [0/12/-=] >> [0] load ammo (1 ammo)
    Opponent selects [0] load ammo (1 ammo)
 :: Turn 1
    You [0/12/-=] >> [=] defend using thermal deflector (1 ammo)
    Opponent selects [1] fire a bullet (0 ammo)
 :: You lost after 2 turns!
 :: Replay
    YOU 0=
    FOE 01
Press any key to continue . . .

토너먼트

토너먼트는 "최종 선수 순위"형식을 따릅니다. 토너먼트에서 모든 유효한 제출물 (을 포함하여 GunClubPlayer)은 풀에 배치됩니다. 각 제출물에는 전체 토너먼트 동안 동일하게 유지되는 무작위이지만 고유 한 식별자가 할당됩니다. 각 라운드 동안 :

  • 각 제출은 0 점으로 시작하며 다른 모든 제출에 대해 100 개의 결투를합니다.
  • 각 승리 한 결투는 1 점을 부여합니다. 그리기와 잃는 것은 0 점을줍니다.
  • 라운드가 끝나면 최소 점수로 제출하면 토너먼트가 종료됩니다. 동점 인 경우, 토너먼트 시작 후 획득 한 포인트가 가장 적은 플레이어가 떠나게됩니다.
  • 한 명 이상의 선수가 남으면 다음 라운드가 시작됩니다.
  • 포인트는 다음 라운드로 넘어 가지 않습니다 .

제출

답변 당 한 명의 플레이어를 제출합니다. 다른 제출을 방해 하지 않는 한 플레이어에 여러 파일을 제출할 수 있습니다 . 흐름을 유지하려면 다음을 수행하십시오.

  • 기본 헤더 파일 이름을로 지정하십시오 <Custom>Player.hpp.
  • 로 다른 파일 이름 <Custom>Player*.*예, MyLittlePlayer.txt당신의 클래스 이름 인 경우 MyLittlePlayer, 또는 EmoPlayerHates.cpp클래스 이름 인 경우 EmoPlayer.
  • 귀하의 이름 Shooter에이 토너먼트의 상황에 맞는 단어가 포함 된 경우 Player끝에 추가 할 필요가 없습니다 . 제출 이름이 접미사없이 더 잘 작동한다고 강하게 느끼면 Player추가 할 필요가 없습니다 Player.
  • Windows에서 코드를 컴파일하고 링크 할 수 있는지 확인하십시오.

설명을 요청하거나 허점을 발견하기 위해 주석을 달 수 있습니다. 이 미래형 건 결투를 즐기시고 새해 복 많이 받으시기 바랍니다.

설명

  • 무작위 행동을 할 수 있습니다.
  • 유효하지 않은 행동 (탄약이 충분하지 않은 경우 발사)이 허용됩니다.
  • 플레이어가 유효하지 않은 입력을하면 총이 즉시 폭발합니다.
  • 당신은 답을 연구 할 수 있습니다.
  • 각 토너먼트 내에서 상대방의 행동을 명시 적 으로 기록 수 있습니다 .
  • 각 라운드마다 각 상대에 대해 100 개의 결투를합니다. 그러나 100 개의 결투의 순서는 무작위입니다. 같은 상대에게 100 개의 결투를 연속해서 싸울 수는 없습니다.

추가 자료

C ++ 항목을 제출하려면 @flawr이 제공된 C ++ 소스를 참조 로 Java변환했습니다 .

비 C ++ 제출을위한 인터페이스

현재 허용 : Python 3, Java.

아래 사양 중 하나를 따르십시오.

인터페이스 사양 1 : 종료 코드

제출은 한 차례에 한 번 실행됩니다.

Expected Command Line Argument Format:
    <opponent-id> <turn> <status> <ammo> <ammo-opponent> <history> <history-opponent>

Expected Return Code: The ASCII value of a valid action character.
    '0' = 48, '1' = 49, '2' = 50, '-' = 45, '=' = 61

<opponent-id> is an integer in [0, N), where N is size of tournament.
<turn> is 0-based.
If duel is in progress, <status> is 3.
If duel is draw / won / lost, <status> is 0 / 1 / 2.
<history> and <history-opponent> are strings of actions, e.g. 002 0-=
If turn is 0, <history> and <history-opponent> are not provided.
You can ignore arguments you don't particularly need.

제출물 PythonPlayer\JavaPlayer\디렉토리를 테스트 할 수 있습니다 .

인터페이스 사양 2 : stdin / stdout

(H 월터스 크레딧)

귀하의 제출물은 토너먼트마다 한 번씩 실행됩니다.

stdin과 stdout이 토너먼트 드라이버에 연결되어 있기 때문에 I / O 수행 방법에 대한 모든 항목에 대해 고정 된 요구 사항이 있습니다. 이를 위반하면 교착 상태가 발생할 수 있습니다. 모든 항목 정확한 알고리즘 (의사 코드)을 따라야합니다 .

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

여기서, F 중 하나입니다 0, 1, 2, -, 또는 =를 위해 load / bullet / plasma / metal / thermal. PROCESS는 상대방의 탄약 추적을 포함하여 상대방의 행동에 선택적으로 반응하는 것을 의미합니다. 상대방의 행동도 '0', '1', '2', '-'또는 '='중 하나이며 두 번째 문자입니다.

최종 스코어 보드

08:02 AM Tuesday, February 2, 2017 Coordinated Universal Time (UTC)
| Player             | Language   | Points |     1 |     2 |     3 |     4 |     5 |     6 |     7 |     8 |     9 |    10 |    11 |    12 |    13 |    14 |    15 |    16 |
|:------------------ |:---------- | ------:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:| -----:|
| MontePlayer        | C++        |  11413 |  1415 |  1326 |  1247 |  1106 |  1049 |   942 |   845 |   754 |   685 |   555 |   482 |   381 |   287 |   163 |   115 |    61 |
| CBetaPlayer        | C++        |   7014 |   855 |   755 |   706 |   683 |   611 |   593 |   513 |   470 |   414 |   371 |   309 |   251 |   192 |   143 |   109 |    39 |
| StudiousPlayer     | C++        |  10014 |  1324 |  1233 |  1125 |  1015 |   907 |   843 |   763 |   635 |   555 |   478 |   403 |   300 |   201 |   156 |    76 |
| FatedPlayer        | C++        |   6222 |   745 |   683 |   621 |   655 |   605 |   508 |   494 |   456 |   395 |   317 |   241 |   197 |   167 |   138 |
| HanSoloPlayer      | C++        |   5524 |   748 |   668 |   584 |   523 |   490 |   477 |   455 |   403 |   335 |   293 |   209 |   186 |   153 |
| SurvivorPlayer     | C++        |   5384 |   769 |   790 |   667 |   574 |   465 |   402 |   354 |   338 |   294 |   290 |   256 |   185 |
| SpecificPlayer     | C++        |   5316 |   845 |   752 |   669 |   559 |   488 |   427 |   387 |   386 |   340 |   263 |   200 |
| DeceptivePlayer    | C++        |   4187 |   559 |   445 |   464 |   474 |   462 |   442 |   438 |   369 |   301 |   233 |
| NotSoPatientPlayer | C++        |   5105 |   931 |   832 |   742 |   626 |   515 |   469 |   352 |   357 |   281 |
| BarricadePlayer    | C++        |   4171 |   661 |   677 |   614 |   567 |   527 |   415 |   378 |   332 |
| BotRobotPlayer     | C++        |   3381 |   607 |   510 |   523 |   499 |   496 |   425 |   321 |
| SadisticShooter    | C++        |   3826 |   905 |   780 |   686 |   590 |   475 |   390 |
| TurtlePlayer       | C++        |   3047 |   754 |   722 |   608 |   539 |   424 |
| CamtoPlayer        | C++        |   2308 |   725 |   641 |   537 |   405 |
| OpportunistPlayer  | C++        |   1173 |   426 |   420 |   327 |
| GunClubPlayer      | C++        |    888 |   500 |   388 |
| PlasmaPlayer       | C++        |    399 |   399 |

달리 명시되지 않는 한 토너먼트는 2017 년 2 월 1 일 까지 지속됩니다 .


15
그런데 인상적인 첫 번째 도전!
Martin Ender

3
다른 언어를 기꺼이 실행하려는 경우 Player다른 프로세스를 호출하여 현재 회전을 계산 하는 구현을 허용 할 수 있습니다. 그것은 사람들이 당신이 당신의 머신에서 실행하기에 좋은 언어로 참여할 수있게 해줄 것입니다.
Martin Ender

5
무작위 허용? (완전히 임의의 회전이 아니라 특정 상황에서 50/50의 행동 선택)
FlipTack

2
기술적 요점; " 두 가지 경우 모두 Player::fight"/ 상속을 상속해야합니다. " Player::perceive라는 용어는 상속이 아니라 재정의 입니다.
H Walters

3
나는 당신이 GunDuel.hpp둘 다에 버그가 validA있고 validB사용 한다고 생각합니다actionA
AlexRacer

답변:


9

MontePlayer

이 플레이어는 분리 된 UCT Monte Carlo Tree Search 알고리즘을 사용하여 어떤 선택을해야하는지 결정합니다. 적의 행동을 예측하기 위해 무엇을하는지 추적합니다. 데이터가 부족한 경우 적 자체를 시뮬레이션합니다.

이 봇은 cβ를 제외한 다른 모든 봇에 대해 실제로 잘 작동합니다. cβ와 10000 개의 결투 경기에서 Monte는 5246 개의 결투에서 승리했습니다. 약간의 수학으로, Monte는 cβ 51.17 %에서 53.74 % (99 %의 신뢰도)에 대한 결투에서 승리 할 것입니다.

#ifndef __Monte_PLAYER_HPP__
#define __Monte_PLAYER_HPP__

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


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

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

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


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

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

    struct Stat
    {
        int wins;
        int attempts;

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

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

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

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

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

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


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

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

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

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

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

public:

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

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

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

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

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

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

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

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

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

        return (Action)move;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return game;
    }

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

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

        //Choose random opponent action.
        int opponentAction;

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

        validMoves = 0;

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

        return doTurn(game, myAction, opponentAction);
    }

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

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

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

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

        return best;

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

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

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

#endif // !__Monte_PLAYER_HPP__

25

그만큼 BlackHatPlayer

블랙 햇 플레이어는 총알과 방패가 과거의 일이라는 것을 알고 있습니다. 실제 전쟁은 상대방의 프로그램을 해킹 할 수있는 사람들이 이깁니다.

그래서 그는 고정 금속 방패를 착용하고 자신의 일을 시작합니다.

그는 처음으로 요청을 받았을 때 fight, 적을 기억에 국한 시키려고한다. 싸우는 경기장의 구조를 감안할 때 컴파일러는 자신의 주소 (에 싸여 있음 unique_ptr)와 상대방 중 하나를 서로 옆에 두게 될 것입니다.

따라서 BlackHat은 스택을 조심스럽게 다니면서 간단한 휴리스틱을 사용하여 자신에게 포인터를 찾을 때까지 넘치지 않도록합니다. 그런 다음 인접한 위치의 값이 그의 상대 주소와 비슷한 지, vtable의 주소가 비슷한 지 여부를 확인합니다 typeid.

그것이 그를 찾으면, 그는 그의 두뇌를 빨아 들여 그들을 핫 헤드 바보의 것으로 대체합니다. 실제로, 이것은 상대를 가리키는 vtable을 vtable의 주소로 대체함으로써 이루어집니다 Idiot-항상 멍청한 플레이어입니다.

이 모든 것이 성공하면 (그리고 내 테스트에서-Linux 64 비트의 gcc 6, 와인 32 비트의 MinGW 4.8-이것은 상당히 안정적으로 작동합니다) 전쟁이 승리합니다. 상대방이 첫 라운드에서 무엇을했는지는 중요하지 않습니다. 최악의 상황에서 그는 우리를 쏴서 금속 방패를 착용했습니다.

지금부터, 우리는 바보가 방금 총을 쏘았습니다. 우리는 항상 방패를 착용하고 보호를받으며 1 ~ 3 라운드로 날아갈 것입니다 (원래 봇이 첫 번째 fight콜 에서 한 일에 따라 다름 ).


지금 : 이것이 즉시 실격 처리되어야한다고 확신하지만 위에서 언급 한 규칙을 명시 적으로 위반하지 않는 것은 재미 있습니다.

하지 말아야 할 것

  • 각 토너먼트 시작시 무작위로 지정된 상대 식별자 이외의 상대를 인식하기 위해 직접적인 방법을 사용해서는 안됩니다. 토너먼트 내에서 플레이어가 게임 플레이를하는 사람을 추측 할 수 있습니다.

BlackHat은 상대방을 인식하려고 시도하지 않습니다. 실제로 그의 두뇌가 즉시 교체된다는 점에서 상대방이 누구인지는 전혀 관련이 없습니다.

  • 플레이어 클래스에서 가상으로 선언되지 않은 메서드를 재정의해서는 안됩니다.
  • 전역 범위에서 어떤 것도 선언하거나 초기화해서는 안됩니다.

모든 것은 fight가상 함수에 로컬로 발생 합니다.


// BlackHatPlayer.hpp

#ifndef __BLACKHAT_PLAYER_HPP__
#define __BLACKHAT_PLAYER_HPP__

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

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

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

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

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

        // Our adversary
        Player *c = nullptr;

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

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

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

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

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

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

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

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

#endif // !__BLACKHAT_PLAYER_HPP__

6
@TheNumberOne : 또한 허점 스레드에 대한 첫 번째 (가장 많이 찬성 한) 의견에 따르면 : "루프 홀은 게임을 재미있게 만드는 요소 중 하나입니다. 일반적인 컨텍스트조차 상황에 따라 재미 있거나 영리 할 수 ​​있습니다". IMO 이것은 독창적이며 (적어도 여기서 비슷한 것을 본 적이 없습니다) 공학적으로 현명합니다. 그래서 내가 여기서 공유 한 것입니다.
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은 항상 표준 허점에 대한 지식을 높입니다 :-)
Frenzy Li

2
@HWalters : #pragma once;-) 로 전환해야한다고 생각합니다
Matteo Italia

3
각 플레이어를 별도의 프로세스로 충분히 실행하고 소켓을 사용하여 심판과 통신하는 것만 큼 간단합니다.
Jasen

19

다음으로, 모든 생물 중에서 가장 두려워하는 것은 지옥과 뒤죽박죽 이었고 문자 그대로 900000 개의 다른 봇 과 싸웠습니다 .

그만큼 BotRobot

BotRobot은 매우 기본적인 유전자 알고리즘에 의해 자동으로 명명, 교육 및 구축되었습니다.

두 세대의 팀 9 개가 서로 대립하여 각 세대에 팀 1의 각 로봇이 팀 2의 각 로봇에 배치되었습니다. , 희망적으로 나쁜 것을 잊을 기회가있었습니다. 봇 자체는 영광스러운 조회 테이블이며, 이전에 보지 못한 것을 발견하면 임의의 유효한 옵션을 선택하여 메모리에 저장합니다. 이 작업을 수행하지 않는 C ++ 버전, 그것은 배운한다 . 앞서 언급했듯이, 승리 한 봇은이 새로운 발견 된 메모리를 분명히 작동 한 것처럼 유지합니다. 잃어버린 봇은 그렇지 않고 시작한 것을 유지합니다.

결국, 봇 싸움은 상당히 가까웠으며 거의 ​​찌르지 않았습니다. 승자는 진화 후 두 팀 의 에서 뽑혔습니다 .

BotRobot는 그 무작위로 생성과 함께 아름다운 이름, 행운이었다.

발전기

bot.lua

개정 : 로봇은 자신과 유사하게 생성 된 다른 로봇에 대해 상당히 영리하지만 실제 전투에서 상당히 쓸모없는 것으로 판명되었습니다. 그래서, 나는 이미 생성 된 봇들에 대해 그의 두뇌를 재생성했습니다.

쉽게 볼 수 있듯이 결과는 훨씬 더 복잡한 두뇌이며, 탄약 이 12 개인 적 플레이어까지 선택할 수 있습니다 .

나는 그가 12 발의 탄약에 맞서 싸우는 것이 확실하지 않지만 무언가를했다.

물론 완제품은 ...

// BotRobot
// ONE HUNDRED THOUSAND GENERATIONS TO MAKE THE ULTIMATE LIFEFORM!

#ifndef __BOT_ROBOT_PLAYER_HPP__
#define __BOT_ROBOT_PLAYER_HPP__

#include "Player.hpp"

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

public:
    virtual Action fight()
    {
        std::string action = "";
        action += std::to_string(getAmmo());
        action += ":";
        action += std::to_string(getAmmoOpponent());

        int toDo = 3;

        for (int i = 0; i < int(sizeof(options)/sizeof(*options)); i++) {
            if (options[i].compare(action)==0) {
                toDo = outputs[i];
                break;
            }
        }

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

private:
    std::string options[29] =
    {
        "0:9",
        "1:12",
        "1:10",
        "0:10",
        "1:11",
        "0:11",
        "0:6",
        "2:2",
        "0:2",
        "2:6",
        "3:6",
        "0:7",
        "1:3",
        "2:3",
        "0:3",
        "2:0",
        "1:0",
        "0:4",
        "1:4",
        "2:4",
        "0:0",
        "3:0",
        "1:1",
        "2:1",
        "2:9",
        "0:5",
        "0:8",
        "3:1",
        "0:1"
    };

    int outputs[29] =
    {
        0,
        1,
        1,
        4,
        1,
        0,
        0,
        4,
        4,
        0,
        0,
        3,
        0,
        1,
        3,
        0,
        1,
        4,
        0,
        1,
        0,
        1,
        0,
        3,
        4,
        3,
        0,
        1,
        0
    };
};

#endif // !__BOT_ROBOT_PLAYER_HPP__

나는 지금 C ++를 싫어한다 ...


@FrenzyLi 어떻게 알지 못했는지 모르겠지만 지금 수정하십시오.
ATaco

글쎄,이 업데이트 후, 봇은 고정 된 오프닝을 갖는 것으로 보입니다 00.
Frenzy Li

"1 : 1"이 "0"을 제공하는 이유를 알 수 있습니다.
Frenzy Li

1
여기에있는 몇몇 플레이어는 턴을 기준으로 전체 게임을 수정했습니다. 그래서 나는 고정 된 오프닝이 문제가되지 않을 것이라고 생각하지 않습니다
eis

10

CBetaPlayer (cβ)

대략적인 내쉬 평형.

이 봇은 코드 래퍼를 사용한 멋진 수학입니다.

이것을 게임 이론 문제로 재구성 할 수 있습니다. +1로 이기고 -1로 패를 나타냅니다. 이제 B (x, y)는 x 탄약이 있고 상대는 y 탄약이있는 게임의 가치가되게하십시오. B (a, b) = -B (b, a) 및 B (a, a) = 0입니다. 다른 B 값을 기준으로 B 값을 찾으려면 지불 행렬의 값을 계산할 수 있습니다. 예를 들어, 우리는 B (1, 0)이 다음 서브 게임의 값으로 주어진다는 것을 알고 있습니다 :

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

(기존의 솔루션으로 엄격하게 지배되는 옵션 인 "나쁜"옵션을 제거했습니다. 예를 들어, 탄약이 1 개 밖에 없기 때문에 플라즈마를 쏘지 않으려 고합니다. 우리는 절대로 플라즈마를 쏘지 않기 때문입니다.)

게임 이론은 특정 기술 조건을 가정하여이 지불 행렬의 가치를 찾는 방법을 알려줍니다. 위 행렬의 값은 다음과 같습니다.

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

가능한 모든 게임을 진행하고 B (x, y)-> 1 as x-> 무한대로 y를 고정하면 모든 B 값을 찾을 수 있습니다. 이로 인해 완벽한 움직임을 계산할 수 있습니다!

물론 이론은 현실과 거의 일치하지 않습니다. 작은 값의 x와 y에 대한 방정식을 풀면 너무 복잡해집니다. 이 문제를 해결하기 위해 cβ- 근사라고 부르는 것을 소개했습니다. 이 근사값에는 c0, β0, c1, β1, c, β 및 k의 7 가지 매개 변수가 있습니다. 나는 B 값이 다음과 같은 형식을 취했다고 가정했다.

B(1, 0) = k
B(x, 0) = 1 - c0 β0^x
B(x, 1) = 1 - c1 β1^x
B(x, y) = 1 - c β^(x - y)   (if x > y)

이 매개 변수를 선택한 이유에 대한 대략적인 추론. 처음에는 각각 다른 옵션을 열기 때문에 0, 1 및 2 이상의 탄약을 별도로 처리하고 싶었습니다. 또한 수비 선수가 본질적으로 무엇을 움직 일지 추측하기 때문에 기하학적 생존 함수가 가장 적절하다고 생각했습니다. 탄약이 2 개 이상인 것은 기본적으로 같으므로 그 차이에 초점을 맞췄습니다. 나는 또한 B (1, 0)을 매우 특별한 것으로 취급하고 싶었습니다. 왜냐하면 그것이 많이 나타날 것이라고 생각했기 때문입니다. 이러한 근사 형태를 사용하면 B 값의 계산이 크게 단순화되었습니다.

결과 방정식을 해결하여 각 B 값을 얻은 다음 지불 행렬을 얻기 위해 행렬에 다시 넣었습니다. 그런 다음 선형 프로그래밍 솔버를 사용하여 각 움직임을 만들어 프로그램에 넣을 수있는 최적의 확률을 발견했습니다.

이 프로그램은 영광스러운 조회 테이블입니다. 두 플레이어 모두 0과 4 사이의 탄약을 가지고 있다면 확률 매트릭스를 사용하여 어떤 움직임을해야하는지 무작위로 결정합니다. 그렇지 않으면 테이블을 기반으로 외삽을 시도합니다.

어리석은 결정 론적 봇에는 문제가 있지만 합리적인 봇에 대해서는 꽤 잘합니다. 모든 근사치 때문에 StudiousPlayer가 실제로는 안되면 때때로 손실됩니다.

물론이 작업을 다시 수행하려면 더 독립적 인 매개 변수 나 더 나은 ansatz 형식을 추가하고보다 정확한 솔루션을 찾게됩니다. 또한 턴제 한을 무시하기도했습니다. 탄약이 충분하고 남은 회전 수가 충분하지 않으면 항상 플라즈마를 발사하도록 빠른 수정을 할 수 있습니다.

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

에 매개 변수를 전달하지 않으므로 GetRandomDoublemax 인수를 제거 할 수 있습니다.
Frenzy Li

@FrenzyLi, 으악, 고마워!
George V. Williams

확률 ... 텐서에 도달 한 방법과 같이 플레이어에 대한 정보를 조금 더 추가 하시겠습니까?
Frenzy Li

2
나는 이 봇을 좋아 한다. SP는 다른 항목의 결정 성 때문에 지금까지만 이점이 있다고 생각합니다. (최적 가중치가 아닌) 랜덤 봇이 추가 될수록 CBP 요금이 더 좋습니다. 이것은 테스트에 의해 뒷받침됩니다. 평범한 용의자와의 내부 테스트에서 SP는 항상 CBP 초로 승리하지만 CBP, SP 및 FP와 관련된 미니 콘테스트에서 CBP는 55 %를 앞당기 고 SP와 FP는 균등하게 이동합니다.
H Walters

1
그건 그렇고, 이것은 내쉬 평형의 인상적인 정확한 근사치입니다. 몬테는 균형 전략을 찾으려고 시도하지는 않지만 주어진 상대에 대한 최선의 움직임을 보여줍니다. 그것이 그것과 cβ 사이의 결투의 52 % 퍼센트만을 이긴다는 사실은 cβ가 내쉬 평형에 매우 가깝다는 것을 의미합니다.
TheNumberOne

8

나는 모든 곳에서 주석이 부족하므로 아직 질문을 할 수 없습니다. 첫 번째 봇과의 대결에서이기는 매우 기본적인 선수입니다.

[편집] 감사합니다. 이제 이전 상태는 더 이상 사실이 아니지만이 봇의 상황을 이해할 수 있도록 유지하는 것이 좋습니다.

그만큼 Opportunist

기회주의자는 GunClubPlayers와 같은 총기 클럽을 자주 방문하지만, 새로운 이민자에게 모든 GunClubPlayers를 이길 수 있다는 것을 내기했습니다. 그래서 그는 오랫동안 알아 차린 습관을 이용하여 스스로를 쏘지 말고 조금만이기도록 기다립니다.

#ifndef __OPPORTUNIST_PLAYER_HPP__
#define __OPPORTUNIST_PLAYER_HPP__

#include <string>
#include <vector>

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

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

7

그만큼 BarricadePlayer

Barricade Player는 첫 번째 탄환을로드 한 다음 적절한 방패를 유지합니다 (여전히 조금 무작위 임). 또한 5 라운드마다 또 다른 샷을로드합니다. 매 라운드마다 15 % 확률로 알고리즘을 무시하고 (첫 번째 장전을 제외하고) 총알을 쏴야합니다. 적에게 탄약이 없으면 적을 탄다. 어쨌든 모든 것이 잘못되면 오 소년, 그냥 쏴.

최신 변경 사항 :

난수 개선 (Frenzy Li 덕분에).

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

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

#ifndef __BARRICADE_PLAYER_HPP__
#define __BARRICADE_PLAYER_HPP__

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

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

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

        }
        return bullet();
    }
};

#endif // !__BARRICADE_PLAYER_HPP__

1
발사하기 전에 적어도 탄약이 있는지 확인하고 싶습니까?
Pavel

8
아뇨. 나는 위험한 삶을 살고 있습니다. @Pavel
devRicher

1
두 번째 턴에서 열 편향기를 사용하는 것은 의미가 없습니까? 첫 번째 턴에는 총알 2 개를로드 할 수 없습니다. 무작위로 원하더라도 상대방의 총알이 1 (또는 그 이하)이면 열 차폐를 사용하지 않아야한다고 생각합니다.
Southpaw Hare

1
모든 제안에 감사드립니다. 나는 수업을 많이 편집했습니다. @SouthpawHare
devRicher

2
그것은이다 getAmmoOpponent없습니다 getOpponentAmmo. 또한 빠졌습니다#endif // !__BARRICADE_PLAYER_HPP__
Blue

7

그만큼 StudiousPlayer

Studious Player는 먹이를 연구하여 각 상대를 모델링합니다. 이 플레이어는 기본 전략으로 시작하여 무작위로 제자리에서 움직이며 상대방 반응의 빈번한 측정을 기반으로 간단한 적응 전략으로 진행합니다. 탄약 조합에 반응하는 방식에 따라 간단한 상대 모델을 사용합니다.

#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

이는 챌린지 규칙에 따라 상대방에 대한 정보를 추적합니다. 하단의 "Meyers 스타일 싱글 톤"범위 "storedLs ()"메소드를 참조하십시오. (어떤 사람들은 어떻게해야하는지 궁금해하고 있습니다. 이제는 알고 있습니다!)


1
나는 이것이 이것을 볼 때까지 Meyers 스타일 싱글 톤이라고 불렀다.
Frenzy Li

1
이 용어를 너무 진지하게 받아들이지 마십시오. "단일"은 선언 된 구조체가 아닌 템플릿 인스턴스화이기 때문에 일종의 용어 남용 일 수 있지만 동일한 기술입니다.
H Walters

6

그만큼 GunClubPlayer

원래 질문에서 잘라내십시오. 이것은 파생 플레이어의 최소한의 구현 예입니다. 이 플레이어는 토너먼트에 참여합니다.

GunClubPlayer들 총 클럽에 가고 싶어. 각 결투 중에는 먼저 탄약을 장전 한 후 총알을 발사하고 세계 결투 가 끝날 때까지이 과정을 반복합니다 . 그들은 실제로 그들이 이겼는지 아닌지 신경 쓰지 않고, 즐거운 경험을하는 데에만 집중합니다.

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

#ifndef __GUN_CLUB_PLAYER_HPP__
#define __GUN_CLUB_PLAYER_HPP__

#include "Player.hpp"

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

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

#endif // !__GUN_CLUB_PLAYER_HPP__

1
당신은 return 서술문 이후에 else가 필요하지 않습니까? 나는 그것이 코드 골프가 아니라는 것을 알고 있지만 잘못 느낍니다.
Pavel

2
@Pavel 음, 알았어요 ... 지금은 ... 골프 종류입니다.
Frenzy Li

5

그만큼 PlasmaPlayer

플라즈마 플레이어는 자신의 플라즈마 볼트를 발사하는 것을 좋아합니다. 그는 가능한 한 많이 적재하고 발사하려고 노력할 것입니다. 그러나 상대방은 플라즈마 탄약을 가지고 있지만 열 차폐를 사용합니다 (총알은 약한 것입니다).

#ifndef __PLASMA_PLAYER_HPP__
#define __PLASMA_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

#endif // !__PLASMA_PLAYER_HPP__

@FrenzyLi 생성자에게 감사드립니다! 내 C ++은 약간 녹슬고이 머신에는 컴파일러가 없습니다.
Brian J

천만에요! 나는 여전히 프로젝트에 더 많은 코드 (인쇄 점수 판, 외부 스크립트 읽기 등)를 추가하고 있으며 제출물이 아직 깨지지 않은 것은 운이 좋다.
Frenzy Li

이것은 GunClub을 제외한 모든 상대에게 효과적입니다. 예, 그것은 SadisticShooter (가장 좋은 것)를 죽일 것입니다. @BrianJ
devRicher

5

바로 SadisticShooter

그는 오히려 당신이 당신을 죽이는 것보다 고통을보고 것입니다. 그는 바보가 아니며 필요에 따라 자신을 덮을 것입니다.

당신이 완전히 지루하고 예측 가능하다면, 그는 당신을 곧장 죽일 것입니다.

// SadisticShooter by muddyfish
// PPCG: http://codegolf.stackexchange.com/a/104947/11933

// SadisticShooter.hpp
// A very sad person. He likes to shoot people.

#ifndef __SAD_SHOOTER_PLAYER_HPP__
#define __SAD_SHOOTER_PLAYER_HPP__

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

class SadisticShooter final : public Player
{
public:
    SadisticShooter(size_t opponent = -1) : Player(opponent) {}
private:
    bool historySame(std::vector<Action> const &history, int elements) {
        if (history.size() < elements) return false;

        std::vector<Action> lastElements(history.end() - elements, history.end());

        for (Action const &action : lastElements)
            if (action != lastElements[0]) return false;
        return true;
    }
public:
    virtual Action fight()
    {
        int my_ammo = getAmmo();
        int opponent_ammo = getAmmoOpponent();
        int turn_number = getTurn();
        //std::cout << " :: Turn " << turn_number << " ammo: " << my_ammo << " oppo: " << opponent_ammo << std::endl;

        if (turn_number == 90) {
            // Getting impatient
            return load();
        }
        if (my_ammo == 0 && opponent_ammo == 0) {
            // It would be idiotic not to load here
            return load();
        }
        if (my_ammo >= 2 && historySame(getHistoryOpponent(), 3)) {
            if (getHistoryOpponent()[turn_number - 1] == THERMAL) return bullet();
            if (getHistoryOpponent()[turn_number - 1] == METAL) return thermal();
        }
        if (my_ammo < 2 && opponent_ammo == 1) {
            // I'd rather not die thank you very much
            return metal();
        }
        if (my_ammo == 1) {
            if (opponent_ammo == 0) {
                // You think I would just shoot you?
                return load();
            }
            if (turn_number == 2) {
                return thermal();
            }
            return bullet();
        }
        if (opponent_ammo >= 2) {
            // Your plasma weapon doesn't scare me
            return thermal();
        }
        if (my_ammo >= 2) {
            // 85% more bullet per bullet
            if (turn_number == 4) return bullet();
            return plasma();
        }
        // Just load the gun already
        return load();
    }
};

#endif // !__SAD_SHOOTER_PLAYER_HPP__

나는 당신이 그것을 고정 참조하십시오.
devRicher

4

그만큼 TurtlePlayer

TurtlePlayer겁쟁이입니다. 그는 대부분의 시간을 자신의 방패 뒤에 숨겨 지므로 이름입니다. 때때로, 그는 자신의 포탄에서 나오고 (공기의 의도가없는) 발사를 할 수 있지만, 적은 탄약을 가지고있는 동안 일반적으로 낮게 누워 있습니다.


이 봇은 그리 훌륭하지는 않지만 모든 KOTH는 실행하기 위해 초기 항목이 필요합니다. :)

현지 테스트 결과이 시간 GunClubPlayerOpportunist100 % 에 모두이기는 것으로 나타났습니다 . 에 대한 전투는 BotRobotPlayer항상 방패 뒤에 숨어서 무승부로 보였다.

#include "Player.hpp"

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

class TurtlePlayer final : public Player {

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

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

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

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

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

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

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

4

그만큼 DeceptivePlayer

속임수 플레이어는 총알 2 개를로드 한 다음 발사합니다.

// DeceiverPlayer.hpp
// If we have two shoots, better shoot one by one

#ifndef __DECEPTIVE_PLAYER_HPP__
#define __DECEPTIVE_PLAYER_HPP__

#include "Player.hpp"

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

public:
    virtual Action fight()
    {
        int ammo = getAmmo();
        int opponentAmmo = getAmmoOpponent();
        int turn = getTurn();

        // Without ammo, always load
        if (ammo == 0)
        {
            return load();
        }

        // Every 10 turns the Deceiver goes crazy
        if (turn % 10 || opponentAmmo >= 3)
        {
            // Generate random integer in [0, 5)
            int random = GetRandomInteger() % 5;
            switch (random)
            {
            case 0:
                return bullet();
            case 1:
                return metal();
            case 2:
                if (ammo == 1)
                {
                    return bullet();
                }

                return plasma();
            case 3:
                return thermal();
            case 4:
                return load();
            }
        }

        // The Deceiver shoots one bullet
        if (ammo == 2)
        {
            return bullet();
        }

        // Protect until we can get bullet 2
        if (opponentAmmo == 0)
        {
            return load();
        }

        if (opponentAmmo == 1)
        {
            return metal();
        }

        if (opponentAmmo == 2)
        {
            return thermal();
        }
    }
};

#endif // !__DECEPTIVE_PLAYER_HPP__

나는 C ++로 코딩하지 않으므로 코드 개선이 환영받을 것입니다.


내 편집은 모듈로 및 매크로 정의에 있습니다. 마음에 드실 지 모르지만 DeceptivePlayer더 나은 이름일까요?
Frenzy Li

@FrenzyLi 예, 마음에 듭니다. 이름을 변경하겠습니다
Sxntk

1
@Sxntk 나는이 플레이어가 탄약 2 개를 가진 사람들이 플라즈마를 쏘기를 기대하지만, 2 개의 탄약을 들고 총알을 쏘는 아이러니를 좋아한다.
Brian J

@Sxntk 현재 아무것도 반환하지 않을 가능성이 없습니다. 플레이어는 탄약이 두 개 이상 허용됩니다. 따라서 상대방이 3 개 이상의 탄약을 가지고 있다면 아무 행동도하지 않습니다. 당신은 어딘가에 폭발 총을 감을 수 있습니다. (물론, 그것은 당신의 마스터 플랜이 될 수 있습니다 :))
Brian J

@BrianJ 고마워, 나는 그것에 대해 생각할 것이다. 한편 난 속임수가 미치도록했고 oponnent가 3 개 이상의 탄약을 가지고있을 때해야 할 일을 결정할 것이다
Sxntk

2

한솔로 선수

먼저 쏴! 여전히 수정 작업 중이지만 꽤 좋습니다.

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

그만큼 CamtoPlayer

CamtoPlayer 미워은 무 걸립니다 상관없이 어떤 루프에서 벗어나 없습니다. (자살 제외)

그것은 아무것도하지 않는 첫 번째 C ++ 프로그램이므로 너무 열심히 판단하지 마십시오.

나는 그것이 더 나을 수 있다는 것을 알고 있지만 편집하지 마십시오.
코드를 수정하려면 제안을 주석 처리하십시오.

#ifndef __CAMTO_HPP__
#define __CAMTO_HPP__

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

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

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

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

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

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

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

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

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

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

    }
};

#endif // !__CAMTO_HPP__

당신은 잊고 있습니다#endif // ! __CAMTO_HPP__
Blue

@muddyfish 말해 주셔서 감사합니다. 코드 렌더링을 중지시킨 기호보다 다양한 기호가 있습니다! XD
Benjamin Philippe

여전히 나타나지 않습니다. HTML 태그를 모두 버리고 마크 다운 ( "{}"이있는 "코드 샘플"버튼)을 사용하는 것이 좋습니다. 의 인용을 수동으로 인용 <>&하는 것은 고통입니다.
H Walters

@HWalters 팁 주셔서 감사합니다!
Benjamin Philippe

참여해 주셔서 감사합니다. 그리고 한가지 : using namespace std토너먼트를 방해 하기 때문에 제거하십시오 . 당신이 디버그하고 싶다면, 당신은 std::cout등을 사용할 수 있습니다
Frenzy Li

1

그만큼 SurvivorPlayer

Survivor Player는 Turtle and Barricade Player와 유사한 방식으로 동작합니다. 그는 자신의 죽음으로 이어질 싸움을 잃는 것보다 무승부를 강제하는 행동을 취하지 않을 것입니다.

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

그만큼 FatedPlayer

Clotho 제작, Lachesis 득점, Atropos 사망 ; 이 플레이어의 유일한 전략은 탄약에 대해 알고있는 것을 사용하여 어떤 행동이 합리적인지를 결정하는 것입니다.

그러나 조치 를 선택할 수는 없습니다 . 그 부분은 신에게 맡겨져 있습니다.

#ifndef __FATEDPLAYER_H__
#define __FATEDPLAYER_H__

#include "Player.hpp"
#include <functional>
class FatedPlayer final : public Player
{
public:
   FatedPlayer(size_t o) : Player(o){}
   Action fight() {
      std::vector<std::function<Action()>>c{[&]{return load();}};
      switch(getAmmo()){
      default:c.push_back([&]{return plasma();});
      case 1 :c.push_back([&]{return bullet();});
      case 0 :;}
      switch(getAmmoOpponent()){
      default:c.push_back([&]{return thermal();});
      case 1 :c.push_back([&]{return metal();});
      case 0 :;}
      return c[GetRandomInteger(c.size()-1)]();
   }
};

#endif

무작위 플레이어의 순위를보고 싶습니다.


1

SpecificPlayer

SpecificPlayer 는 임의의 (유효한) 조치를 선택하는 간단한 계획을 따릅니다. 그러나 주요 특징은 탄약 수와 상대의 이전 움직임을 분석하여 특정 상황을 찾는 것입니다.

C ++로 무엇이든 쓰고 처음으로 경쟁 봇 작성을 시도한 것은 처음입니다. 그래서 나는 빈약 한 시도가 적어도 흥미로운 일을하기를 바랍니다. :)

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

#ifndef __SPECIFIC_PLAYER_HPP__
#define __SPECIFIC_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

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

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

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

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

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

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

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

        return returnval;
    }

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

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

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

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

#endif // !__SPECIFIC_PLAYER_HPP__

1

NotSoPatientPlayer

창조의 이야기는 나중에 올 것이다.

// NotSoPatientPlayer.hpp

#ifndef __NOT_SO_PATIENT_PLAYER_HPP__
#define __NOT_SO_PATIENT_PLAYER_HPP__

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

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

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

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

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

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

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

private:
    static long opponentMoves[TOTAL_PLAYERS][TOTAL_ACTIONS];
    int opponent;
    //When it becomes true, the bot starts shooting.
    bool burstMode = false;
    //turnForBullet1 and turnForBullet2,
    //the 2 turns in which the bot will shoot bullets
    int turnForBullet1 = -1, turnForBullet2 = -1;
    //For debugging purposes
    //Reminder: enum Result { DRAW, WIN, LOSS };
    static int currentRoundResults[TOTAL_PLAYERS][3], totalResults[TOTAL_PLAYERS][3];
};
long NotSoPatientPlayer::opponentMoves[TOTAL_PLAYERS][TOTAL_ACTIONS] = { { 0 } };
int NotSoPatientPlayer::currentRoundResults[TOTAL_PLAYERS][3] = { { 0 } };
int NotSoPatientPlayer::totalResults[TOTAL_PLAYERS][3] = { { 0 } };
#endif // !__NOT_SO_PATIENT_PLAYER_HPP__

"그것의 창조의 이야기가 올 것이다 나중에"이 3 개월 이상 :)이었다
HyperNeutrino
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.