Cuộc đấu súng tương lai


73

Các nền tương lai

Trong năm 2017, bạn và đối thủ của bạn sẽ đối đầu trong một cuộc đấu súng tương lai, nơi chỉ có một người có thể sống sót. Là bạn đủ kinh nghiệm để đánh bại đối thủ của bạn? Bây giờ là lúc để đánh bóng kỹ năng súng của bạn bằng ngôn ngữ lập trình yêu thích của bạn và chiến đấu chống lại tất cả các tỷ lệ cược!

Kết quả giải đấu

Trận đấu này đã kết thúc vào sáng UTC của Feburary 2 nd , 2017. Nhờ các thí sinh của chúng tôi, chúng tôi đã có một giải đấu tương lai thú vị!

MontePlayer là người chiến thắng cuối cùng sau một trận chiến gần với CBetaPlayer và StudiousPlayer. Ba đấu sĩ guen hàng đầu đã chụp một bức ảnh kỷ niệm:

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

Xin chúc mừng những người chiến thắng! Bảng xếp hạng chi tiết được nhìn thấy gần cuối bài này.

Hướng dẫn chung

  • Truy cập kho lưu trữ chính thức cho mã nguồn được sử dụng trong giải đấu này.
  • Các mục C ++: vui lòng kế thừa Playerlớp.
  • Các mục không phải C ++: chọn một giao diện trong phần Giao diện cho các lần gửi Không C ++ .
  • Hiện tại cho phép các ngôn ngữ không C ++: Python 3, Java.

Các cuộc đấu tay đôi

  • Mỗi người chơi bắt đầu với một khẩu súng không nạp có thể nạp một lượng đạn vô hạn.
  • Mỗi lượt, người chơi sẽ đồng thời chọn một trong các hành động sau:
    • 0 - Nạp 1 đạn vào súng.
    • 1- Bắn một viên đạn vào đối thủ; Chi phí 1 đạn nạp.
    • 2- Bắn một tia plasma vào đối thủ; Chi phí 2 đạn nạp.
    • - - Bảo vệ viên đạn đến bằng lá chắn kim loại.
    • = - Bảo vệ chùm plasma tới bằng cách sử dụng bộ làm lệch nhiệt.
  • Nếu cả hai người chơi sống sót sau lượt thứ 100 , cả hai đều kiệt sức đến chết, kết quả là hòa .

Một người chơi thua cuộc đấu súng nếu họ

  • Đã không sử dụng lá chắn kim loại để bảo vệ một viên đạn đến.
  • Đã KHÔNG sử dụng làm lệch nhiệt để bảo vệ một plasma đến.
  • Bắn một khẩu súng mà không nạp đủ đạn, trong đó súng của họ sẽ tự nổ và giết chết chủ nhân.

Hãy cẩn thận

Theo Hướng dẫn dành cho chủ sở hữu súng tương lai :

  • Một lá chắn kim loại CANNOT bảo vệ khỏi chùm plasma đến. Tương tự như vậy, một bộ làm lệch nhiệt CANNOT bảo vệ khỏi viên đạn đến.
  • Tia plasma áp đảo viên đạn (vì trước đây đòi hỏi nhiều đạn hơn). Do đó, nếu người chơi bắn một tia plasma vào đối thủ bắn một viên đạn trong cùng một lượt, thì đối thủ sẽ bị giết.
  • Nếu cả hai người chơi bắn một viên đạn vào nhau trong cùng một lượt, viên đạn sẽ hủy và cả hai người chơi đều sống sót. Tương tự như vậy, nếu cả hai người chơi bắn một tia plasma vào nhau trong cùng một lượt, cả hai người chơi đều sống sót.

Điều đáng chú ý là:

  • Bạn sẽ KHÔNG biết hành động của đối thủ trong lượt của mình cho đến khi kết thúc.
  • Làm chệch hướng tia plasma và đạn che chắn sẽ KHÔNG gây hại cho đối thủ của bạn.

Do đó, có tổng cộng 25 kết hợp hành động hợp lệ mỗi lượt:

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

Ví dụ đấu tay đôi

Đây là một cuộc đấu tay đôi tôi từng có với một người bạn. Trước đó, chúng tôi không biết nhiều về lập trình, vì vậy chúng tôi đã sử dụng cử chỉ tay và báo hiệu ở tốc độ hai lượt mỗi giây. Từ trái sang phải, hành động của chúng tôi lần lượt:

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

Theo các quy tắc trên, tôi đã mất. Bạn có thấy tại sao không? Đó là bởi vì tôi đã bắn chùm tia plasma cuối cùng khi tôi chỉ có 1 viên đạn được nạp, khiến khẩu súng của tôi phát nổ.


Trình phát C ++

Bạn , với tư cách là một lập trình viên tương lai văn minh, sẽ không trực tiếp cầm súng. Thay vào đó, bạn viết mã Playerchiến đấu chống lại người khác. Bằng cách kế thừa công khai lớp trong dự án GitHub, bạn có thể bắt đầu viết huyền thoại đô thị của mình.

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

Những gì bạn phải hoặc có thể làm

  • Bạn phải kế thừa Playerlớp thông qua kế thừa công khai và tuyên bố lớp cuối cùng của bạn.
  • Bạn phải ghi đè Player::fight, trả về giá trị Player::Actionmỗi lần nó được gọi.
  • Tùy chọn, ghi đè Player::perceivePlayer::declaredđể theo dõi hành động của đối thủ và theo dõi các chiến thắng của bạn.
  • Tùy chọn, sử dụng các thành viên và phương thức tĩnh riêng trong lớp dẫn xuất của bạn để thực hiện các phép tính phức tạp hơn.
  • Tùy chọn, sử dụng các thư viện chuẩn C ++ khác.

Những gì bạn không được làm

  • Bạn KHÔNG được sử dụng bất kỳ phương pháp trực tiếp nào để nhận ra đối thủ của mình ngoài số nhận dạng đối thủ đã cho, được xáo trộn vào đầu mỗi giải đấu. Bạn chỉ được phép đoán ai là người chơi thông qua trò chơi của họ trong một giải đấu.
  • Bạn KHÔNG được ghi đè bất kỳ phương thức nào trong Playerlớp không được khai báo ảo.
  • Bạn KHÔNG được khai báo hoặc khởi tạo bất cứ điều gì trong phạm vi toàn cầu.
  • Kể từ khi ra mắt (hiện đã bị loại) BlackHatPlayer, người chơi KHÔNG được phép nhìn trộm hoặc sửa đổi trạng thái của đối thủ của bạn.

Một ví dụ đấu tay đôi

Quá trình đấu súng được thực hiện bằng cách sử dụng GunDuellớp. Để biết ví dụ về cuộc chiến, hãy xem Source.cppphần Bắt đầu một cuộc đấu tay đôi .

Chúng tôi giới thiệu GunClubPlayer, HumanPlayervà các GunDuellớp học, có thể được tìm thấy trong Tournament\thư mục của các kho lưu trữ.

Trong mỗi trận đấu, GunClubPlayersẽ nạp một viên đạn; bắn nó; rửa sạch và lặp lại. Trong mỗi lượt, HumanPlayersẽ nhắc bạn hành động để chống lại đối thủ của bạn. Điều khiển bàn phím của bạn là những nhân vật 0, 1, 2, -=. Trên Windows, bạn có thể sử dụng HumanPlayerđể gỡ lỗi trình của mình.

Bắt đầu một cuộc đấu tay đôi

Đây là cách bạn có thể gỡ lỗi trình phát của mình thông qua bảng điều khiển.

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

Trò chơi ví dụ

Số tiền tối thiểu của lượt bạn cần phải đánh bại GunClubPlayerlà 3. Đây là replay từ chơi 0-1chống lại GunClubPlayer. Số trong phép so sánh là số lượng đạn được nạp cho mỗi người chơi khi lượt chơi kết thúc.

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

Cách nhanh nhất để bị đánh bại GunClubPlayermà không thực hiện các động tác không hợp lệ là trình tự 0=, bởi vì viên đạn bắn xuyên qua bộ làm lệch nhiệt. Phát lại là

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

Giải đấu

Giải đấu tuân theo định dạng "Người chơi cuối cùng". Trong một giải đấu, tất cả các bài nộp hợp lệ (bao gồm cả GunClubPlayer) được đặt trong một nhóm. Mỗi bài dự thi được chỉ định một số nhận dạng ngẫu nhiên nhưng duy nhất sẽ giữ nguyên trong toàn bộ giải đấu. Trong mỗi vòng:

  • Mỗi bài nộp bắt đầu với 0 điểm và sẽ chơi 100 trận đấu với mọi bài khác.
  • Mỗi trận đấu tay đôi sẽ giành được 1 điểm; vẽ và thua cho 0 điểm.
  • Kết thúc vòng thi, các bài dự thi có số điểm tối thiểu sẽ rời khỏi giải đấu. Trong trường hợp hòa, người chơi có số điểm kiếm được ít nhất kể từ đầu giải đấu sẽ rời đi.
  • Nếu có nhiều hơn một người chơi, vòng tiếp theo sẽ bắt đầu.
  • Điểm KHÔNG mang qua vòng tiếp theo.

Nộp hồ sơ

Bạn sẽ gửi một người chơi cho mỗi câu trả lời. Bạn có thể gửi nhiều tệp cho một người chơi, miễn là chúng KHÔNG can thiệp vào các lần gửi khác. Để giữ cho mọi thứ trôi chảy, xin vui lòng:

  • Đặt tên tệp tiêu đề chính của bạn là <Custom>Player.hpp,
  • Đặt tên cho các tệp khác của bạn là <Custom>Player*.*, ví dụ MyLittlePlayer.txtnếu tên lớp của bạn là MyLittlePlayerhoặc EmoPlayerHates.cppnếu tên lớp của bạn là EmoPlayer.
  • Nếu tên của bạn có chứa Shooterhoặc các từ tương tự phù hợp với bối cảnh của giải đấu này, bạn không cần thêm Playervào cuối. Nếu bạn cảm thấy mạnh mẽ rằng tên trình của bạn hoạt động tốt hơn mà không có hậu tố Player, bạn cũng không cần phải thêm Player.
  • Đảm bảo rằng mã của bạn có thể được biên dịch và liên kết trong Windows.

Bạn có thể bình luận để yêu cầu làm rõ hoặc để phát hiện sơ hở. Hy vọng bạn thích cuộc đấu súng tương lai này và chúc bạn một năm mới hạnh phúc!

Làm rõ

  • Bạn được phép có hành vi ngẫu nhiên.
  • Các hành động không hợp lệ (bắn khi nạp đạn không đủ) được cho phép.
  • Nếu người chơi tạo đầu vào không hợp lệ, súng của họ sẽ phát nổ ngay lập tức.
  • Bạn được phép nghiên cứu các câu trả lời.
  • Bạn rõ ràng được phép ghi lại hành vi của đối thủ trong mỗi giải đấu.
  • Mỗi vòng, bạn sẽ chơi 100 trận đấu với mỗi đối thủ; tuy nhiên, thứ tự của 100 trận đấu là ngẫu nhiên - bạn không được đảm bảo chiến đấu với cùng một đối thủ 100 trận đấu liên tiếp.

Tài nguyên bổ sung

@flawr đã dịch nguồn C ++ được cung cấp sang Java làm tài liệu tham khảo nếu bạn muốn gửi các mục C ++.

Giao diện cho các bài nộp không phải C ++

Hiện được chấp nhận: Python 3, Java.

Vui lòng làm theo một trong các thông số kỹ thuật dưới đây:

Đặc tả giao diện 1: mã thoát

Trình của bạn sẽ chạy một lần mỗi lượt.

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.

Bạn có thể kiểm tra trình của bạn trong PythonPlayer\JavaPlayer\thư mục.

Đặc tả giao diện 2: stdin / stdout

(Tín dụng cho H Walters)

Trình của bạn sẽ chạy một lần mỗi giải đấu.

Có một yêu cầu cố định cho tất cả các mục về cách thực hiện I / O, vì cả stdin và stdout đều được kết nối với trình điều khiển giải đấu. Vi phạm điều này có thể dẫn đến bế tắc. Tất cả các mục PHẢI tuân theo thuật toán CHÍNH XÁC này (bằng mã giả):

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

Ở đây, F là một trong những 0, 1, 2, -, hoặc =cho load / bullet / plasma / metal / thermal. QUY TRÌNH có nghĩa là tùy ý trả lời những gì đối thủ của bạn đã làm (bao gồm cả theo dõi đạn của đối thủ nếu bạn đang làm điều này). Lưu ý rằng hành động của đối thủ cũng là một trong '0', '1', '2', '-' hoặc '=' và nằm trong ký tự thứ hai.

Bảng điểm cuối cùng

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 |

Giải đấu sẽ kéo dài đến ngày 1 tháng 2 năm 2017 trừ khi có ghi chú khác.


15
Nhân tiện, thử thách đầu tiên ấn tượng!
Martin Ender

3
Nếu bạn sẵn sàng chạy một số ngôn ngữ khác, bạn có thể cho phép Playertriển khai thực hiện quy trình khác để tính lần lượt hiện tại. Điều đó sẽ cho phép mọi người tham gia vào bất kỳ ngôn ngữ nào mà bạn vui lòng chạy trên máy của mình.
Martin Ender

5
Ngẫu nhiên cho phép? (Không phải lượt hoàn toàn ngẫu nhiên, chỉ là lựa chọn hành động 50/50 trong một tình huống nhất định)
FlipTack

2
Điểm kỹ thuật; "Bạn phải thừa kế Player::fight" / "bạn có thể thừa kế Player::perceive" ... trong cả hai trường hợp, thuật ngữ này được ghi đè , không phải kế thừa .
H Walters

3
Tôi nghĩ rằng bạn có một lỗi trong GunDuel.hppcả hai validAvalidBsử dụngactionA
AlexRacer

Câu trả lời:


9

MontePlayer

Người chơi này sử dụng thuật toán Tìm kiếm UCT Monte Carlo Tree Decoupling để quyết định những lựa chọn nào cần thực hiện. Nó theo dõi những gì kẻ thù làm để dự đoán hành động của nó. Nó mô phỏng kẻ thù như chính nó nếu nó thiếu dữ liệu.

Bot này thực sự tốt đối với mọi bot khác ngoại trừ cβ. Trong trận đấu 10000 đấu với cβ, Monte đã thắng 5246 trận đấu. Với một chút toán học, điều đó có nghĩa là Monte sẽ giành chiến thắng trong cuộc đấu tay đôi với 51,17% đến 53,74% thời gian (độ tin cậy 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

Các BlackHatPlayer

Người chơi BlackHat biết rằng đạn và khiên là một điều của quá khứ; những cuộc chiến thực sự được chiến thắng bởi những người có thể hack các chương trình của đối thủ.

Vì vậy, anh ta đặt một chiếc khiên kim loại cố định và bắt đầu làm việc của mình.

Lần đầu tiên anh ta được yêu cầu fight, anh ta cố gắng bản địa hóa kẻ thù trong trí nhớ. Với cấu trúc của đấu trường chiến đấu, gần như chắc chắn rằng trình biên dịch cuối cùng sẽ đặt địa chỉ của anh ta (được bọc trong một unique_ptr) và một trong những đối thủ chỉ là một bên cạnh nhau.

Vì vậy, BlackHat đi cẩn thận ngăn xếp, sử dụng một số phương pháp phỏng đoán đơn giản để đảm bảo không làm đổ nó, cho đến khi anh ta tìm thấy một con trỏ cho chính mình; sau đó kiểm tra xem các giá trị ở các vị trí liền kề có hợp lý với đối thủ của anh ta không - địa chỉ tương tự, địa chỉ tương tự của vtable, hợp lý typeid.

Nếu nó tìm thấy anh ta, anh ta hút bộ não của mình ra và thay thế chúng bằng những kẻ ngốc. Trong thực tế, điều này được thực hiện bằng cách thay thế con trỏ của đối thủ thành vtable bằng địa chỉ của Idiotvtable - một người chơi câm luôn bắn.

Nếu tất cả điều này thành công (và trong các thử nghiệm của tôi - gcc 6 trên Linux 64 bit, MinGW 4.8 trên rượu 32 bit - điều này hoạt động khá đáng tin cậy), chiến tranh đã chiến thắng. Bất cứ điều gì đối thủ làm ở vòng đầu tiên đều không quan trọng - tệ nhất là anh ta bắn chúng tôi, và chúng tôi có tấm khiên kim loại.

Từ bây giờ, chúng tôi có một thằng ngốc chỉ bắn; chúng tôi luôn có khiên chắn, vì vậy chúng tôi được bảo vệ và anh ta sẽ nổ tung sau 1 đến 3 vòng (tùy thuộc vào những gì bot ban đầu đã làm trong fightcuộc gọi đầu tiên của anh ta ).


Bây giờ: Tôi gần như chắc chắn rằng điều này sẽ bị loại ngay lập tức, nhưng thật buồn cười là tôi không vi phạm rõ ràng bất kỳ quy tắc nào được nêu ở trên:

Những gì bạn không được làm

  • Bạn KHÔNG được sử dụng bất kỳ phương pháp trực tiếp nào để nhận ra đối thủ của mình ngoài số nhận dạng đối thủ đã cho, hoàn toàn ngẫu nhiên vào đầu mỗi giải đấu. Bạn chỉ được phép đoán ai là người chơi thông qua lối chơi của họ trong một giải đấu.

BlackHat không cố gắng nhận ra đối thủ - thực ra, nó hoàn toàn không liên quan đến đối thủ là ai, cho rằng bộ não của anh ta được thay thế ngay lập tức.

  • Bạn KHÔNG được ghi đè bất kỳ phương thức nào trong lớp Trình phát không được khai báo ảo.
  • Bạn KHÔNG được khai báo hoặc khởi tạo bất cứ điều gì trong phạm vi toàn cầu.

Tất cả những gì xảy ra tại địa phương để các fightchức năng ảo.


// 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: đồng thời, theo nhận xét đầu tiên (và được đánh giá cao nhất) cho chủ đề sơ hở: "Lỗ hổng là một phần của những gì làm cho trò chơi thú vị. Ngay cả những người bình thường cũng có thể hài hước hoặc thông minh, tùy thuộc vào ngữ cảnh". IMO đây là bản gốc (ít nhất, tôi chưa bao giờ thấy bất cứ điều gì tương tự ở đây) và thú vị, khôn ngoan về kỹ thuật; đó là lý do tại sao tôi chia sẻ nó ở đây
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 luôn tăng kiến ​​thức về sơ hở tiêu chuẩn của chúng tôi :-)
Frenzy Li

2
@HWalters: Tôi đoán tôi sẽ phải chuyển sang #pragma once;-)
Matteo Italia

3
dường như đủ đơn giản để chạy mỗi cầu thủ trong một quy trình riêng biệt và sử dụng ổ cắm để liên lạc với trọng tài.
Jasen

19

Tiếp theo, điều đáng sợ nhất trong tất cả các sinh vật, đó là đến địa ngục và trở lại và chiến đấu với 900000 bot khác , ...

Các BotRobot

BotRobot được đặt tên, đào tạo và xây dựng tự động bằng thuật toán di truyền rất cơ bản.

Hai đội gồm 9 đội được thiết lập để chống lại nhau, trong mỗi thế hệ, mỗi robot từ đội 1 được đấu với từng robot của đội 2. Các robot có nhiều chiến thắng hơn thua, giữ lại bộ nhớ của mình, trở lại bước cuối cùng , và đã có một cơ hội để quên một cái gì đó, hy vọng xấu. Bản thân các bot là các bảng tra cứu được tôn vinh, trong đó nếu chúng tìm thấy thứ gì đó mà chúng chưa từng thấy trước đây, chúng chỉ cần chọn một tùy chọn hợp lệ ngẫu nhiên và lưu nó vào bộ nhớ. Phiên bản C ++ không làm điều này, nó nên học . Như đã nói trước đây, các bot chiến thắng giữ bộ nhớ tìm thấy mới này, vì rõ ràng nó hoạt động. Mất bot không, và giữ những gì họ bắt đầu với.

Cuối cùng, các cuộc chiến bot khá gần gũi, hiếm khi bế tắc. Người chiến thắng đã được chọn ra từ một nhóm của hai đội tiến hóa, đó là 100000 thế hệ.

BotRobot, với tạo ngẫu nhiên và nó BEAUTIFUL tên, là người may mắn.

Máy phát điện

bot.lua

Sửa đổi: Mặc dù robot khá thông minh so với chính mình và các robot được tạo ra tương tự khác, anh ta tỏ ra khá vô dụng trong các trận chiến thực tế. Vì vậy, tôi đã tái tạo bộ não của anh ấy chống lại một số bot đã được tạo ra.

Các kết quả, như có thể dễ dàng nhìn thấy, là một bộ não phức tạp hơn nhiều, với các tùy chọn cho đến khi kẻ thù có 12 viên đạn.

Tôi không chắc anh ta đã chiến đấu chống lại thứ gì lên tới 12 viên đạn, nhưng có gì đó đã làm.

Và tất nhiên, thành phẩm ...

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

Tôi ghét C ++ ngay bây giờ ...


@FrenzyLi Không chắc chắn làm thế nào tôi không nhận thấy điều đó, sửa nó ngay bây giờ.
ATaco

Chà, sau bản cập nhật này, bot dường như đã mở cố định 00.
Frenzy Li

Tôi hiểu tại sao bây giờ ... "1: 1" cho "0".
Frenzy Li

1
Một số người chơi ở đây đã sửa toàn bộ trò chơi của họ dựa trên lượt chơi, vì vậy tôi không nghĩ rằng việc mở cố định sẽ là một vấn đề
eis

10

CBetaPlayer (cβ)

Cân bằng Nash gần đúng.

Bot này chỉ là toán học ưa thích với một trình bao bọc mã.

Chúng ta có thể điều chỉnh lại điều này như một vấn đề lý thuyết trò chơi. Biểu thị một chiến thắng bằng +1 và thua -1. Bây giờ hãy để B (x, y) là giá trị của trò chơi nơi chúng ta có x đạn và đối thủ của chúng ta có y đạn. Lưu ý rằng B (a, b) = -B (b, a) và do đó B (a, a) = 0. Để tìm giá trị B theo các giá trị B khác, chúng ta có thể tính giá trị của ma trận xuất chi. Ví dụ: chúng ta có B (1, 0) được cho bởi giá trị của trò chơi con sau:

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

(Tôi đã loại bỏ các tùy chọn "xấu", còn gọi là các tùy chọn bị chi phối hoàn toàn bởi các giải pháp hiện có. Ví dụ: chúng tôi sẽ không thử bắn plasma vì chúng tôi chỉ có 1 đạn. Đối thủ của chúng tôi sẽ không bao giờ sử dụng bộ làm lệch nhiệt, vì chúng tôi sẽ không bao giờ bắn plasma.)

Lý thuyết trò chơi cho chúng ta biết cách tìm giá trị của ma trận hoàn trả này, giả sử các điều kiện kỹ thuật nhất định. Chúng tôi nhận được rằng giá trị của ma trận trên là:

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

Tiếp tục cho tất cả các trò chơi có thể và lưu ý rằng B (x, y) -> 1 là x -> vô cùng với y cố định, chúng ta có thể tìm thấy tất cả các giá trị B, từ đó cho phép chúng ta tính toán các bước di chuyển hoàn hảo!

Tất nhiên, lý thuyết hiếm khi phù hợp với thực tế. Việc giải phương trình cho các giá trị nhỏ của x và y nhanh chóng trở nên quá phức tạp. Để giải quyết vấn đề này, tôi đã giới thiệu cái mà tôi gọi là xấp xỉ cβ. Có 7 tham số cho xấp xỉ này: c0, 0, c1, 1, c, và k. Tôi giả sử rằng các giá trị B có dạng sau (mẫu cụ thể nhất trước tiên):

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)

Một số lý do sơ bộ về lý do tại sao tôi chọn các tham số này. Đầu tiên tôi biết rằng tôi chắc chắn muốn đối phó với việc có 0, 1 và 2 hoặc nhiều đạn riêng biệt, vì mỗi loại mở ra các tùy chọn khác nhau. Ngoài ra tôi nghĩ rằng một chức năng sinh tồn hình học sẽ là phù hợp nhất, bởi vì người chơi phòng thủ về cơ bản là đoán những gì di chuyển để thực hiện. Tôi hình dung rằng có 2 đạn trở lên về cơ bản là giống nhau, vì vậy tôi đã tập trung vào sự khác biệt. Tôi cũng muốn coi B (1, 0) là một trường hợp siêu đặc biệt vì tôi nghĩ nó sẽ xuất hiện rất nhiều. Sử dụng các hình thức gần đúng này đã đơn giản hóa các tính toán của các giá trị B rất nhiều.

Tôi đã giải gần đúng các phương trình kết quả để nhận từng giá trị B, sau đó tôi đưa lại vào ma trận để có được ma trận xuất chi. Sau đó, sử dụng một bộ giải lập trình tuyến tính, tôi tìm thấy các xác suất tối ưu để thực hiện mỗi lần di chuyển và đưa chúng vào chương trình.

Chương trình này là một bảng tra cứu vinh quang. Nếu cả hai người chơi có từ 0 đến 4 đạn, nó sẽ sử dụng ma trận xác suất để xác định ngẫu nhiên nên di chuyển. Mặt khác, nó cố gắng ngoại suy dựa trên bảng của nó.

Nó có vấn đề chống lại các bot xác định ngu ngốc, nhưng làm khá tốt với các bot hợp lý. Bởi vì tất cả các xấp xỉ, đôi khi điều này sẽ thua StudiousPlayer khi nó thực sự không nên.

Tất nhiên, nếu tôi làm điều này một lần nữa, có lẽ tôi sẽ cố gắng thêm các tham số độc lập hơn hoặc có thể là một dạng ansatz tốt hơn và đưa ra một giải pháp chính xác hơn. Ngoài ra, tôi (cố tình) bỏ qua giới hạn rẽ, bởi vì nó làm mọi thứ khó khăn hơn. Một sửa đổi nhanh có thể được thực hiện để luôn bắn plasma nếu chúng ta có đủ đạn và không còn đủ lượt.

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

Vì bạn không truyền tham số vào GetRandomDouble, bạn có thể xóa đối số tối đa.
Frenzy Li

@FrenzyLi, xin lỗi, cảm ơn!
George V. Williams

Bạn có phiền khi thêm một chút thông tin về trình phát của mình không, chẳng hạn như cách bạn đến với xác suất ... tenor?
Frenzy Li

2
Tôi yêu bot này. Tôi nghĩ rằng SP có lợi thế cho đến nay chỉ do tính quyết định của các mục khác; càng nhiều bot ngẫu nhiên (không tối ưu hóa) được thêm vào, giá vé CBP càng tốt. Điều này được hỗ trợ bởi thử nghiệm; trong các thử nghiệm nội bộ của tôi với các nghi phạm thông thường, SP luôn chiến thắng với CBP thứ hai ... tuy nhiên, trong một cuộc thi nhỏ liên quan đến CBP, SP và FP, CBP đã vượt lên trước 55% thời gian, với SP và FP vượt xa nhau.
H Walters

1
Nhân tiện, đây là một xấp xỉ chính xác ấn tượng của trạng thái cân bằng nash. Monte không cố gắng tìm chiến lược cân bằng, nhưng cách tốt nhất để chống lại bất kỳ đối thủ nào. Thực tế là nó chỉ thắng 52% trong số các cuộc đấu tay đôi giữa nó và cβ có nghĩa là cβ khá nguy hiểm gần với trạng thái cân bằng nash.
TheNumberOne

8

Tôi đang thiếu bình luận ở mọi nơi, vì vậy tôi chưa thể đặt câu hỏi của mình. Vì vậy, đây là một người chơi rất cơ bản để giành chiến thắng trước bot đầu tiên.

[Chỉnh sửa] Cảm ơn, bây giờ trạng thái trước đó không còn đúng nữa nhưng tôi nghĩ tốt hơn là giữ nó để chúng ta có thể hiểu ngữ cảnh của bot này.

Các Opportunist

Kẻ cơ hội thường lui tới cùng một câu lạc bộ súng như GunClubPlayers, tuy nhiên, anh ta đã cá cược với một người mới rằng anh ta có thể đánh bại mọi GunClubPlayers. Vì vậy, anh ta khai thác thói quen mà anh ta đã chú ý từ lâu và buộc mình không bắn mà chỉ chờ một chút để giành chiến thắng.

#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

Các BarricadePlayer

Người chơi Barricade tải một viên đạn đầu tiên, sau đó giữ một tấm khiên phù hợp (vẫn hơi ngẫu nhiên). Anh ta cũng tải một phát súng khác mỗi vòng 5. Mỗi vòng, có 15% cơ hội để bỏ qua algoritm (ngoại trừ tải lại lượt đầu tiên) và bắn một viên đạn. Khi kẻ thù không có đạn, nó sẽ tải. Nếu bằng cách nào đó mọi thứ đi sai, oh chàng trai, anh ta chỉ bắn.

Thay đổi mới nhất:

Cải thiện số ngẫu nhiên (cảm ơn 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
Bạn có muốn kiểm tra ít nhất nếu có đạn trước khi bắn không?
Pavel

8
Không. Tôi sống cuộc sống nguy hiểm. @Pavel
devR Rich

1
Không phải là vô nghĩa khi sử dụng bộ làm lệch nhiệt ở lượt thứ hai sao? Bạn không thể tải hai viên đạn trong lượt đầu tiên. Tôi nghĩ rằng ngay cả khi bạn muốn nó là ngẫu nhiên, bạn nên tránh sử dụng lá chắn nhiệt nếu đạn của đối thủ là 1 (hoặc ít hơn).
Southpaw Hare

1
Cảm ơn tất cả các đề xuất, tôi đã chỉnh sửa rất nhiều lớp. @SouthpawHare
devR Rich

2
Không getAmmoOpponentphải vậy getOpponentAmmo. Bạn cũng đang bỏ lỡ#endif // !__BARRICADE_PLAYER_HPP__
Blue

7

Các StudiousPlayer

Người chơi nghiên cứu nghiên cứu con mồi của nó, mô hình hóa từng đối thủ mà nó gặp phải. Người chơi này bắt đầu với một chiến lược cơ bản, được điều khiển ngẫu nhiên tại các địa điểm và tiến tới các chiến lược thích ứng đơn giản dựa trên các biện pháp thường xuyên phản ứng của đối thủ. Nó sử dụng một mô hình đơn giản của các đối thủ dựa trên cách họ phản ứng với các tổ hợp đạn.

#ifndef __STUDIOUS_PLAYER_H__
#define __STUDIOUS_PLAYER_H__

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

class StudiousPlayer final : public Player
{
public:
   using Player::GetRandomInteger;
   // Represents an opponent's action for a specific state.
   struct OpponentAction {
      OpponentAction(){}
      unsigned l=0;
      unsigned b=0;
      unsigned p=0;
      unsigned m=0;
      unsigned t=0;
   };
   // StudiousPlayer models every opponent that it plays,
   // and factors said model into its decisions.
   //
   // There are 16 states, corresponding to
   // 4 inner states (0,1,2,3) and 4 outer states
   // (0,1,2,3). The inner states represent our
   // (SP's) ammo; the outer represents the
   // Opponent's ammo.  For the inner or outer
   // states, 0-2 represent the exact ammo; and
   // 3 represents "3 or more".
   //
   // State n is (4*outer)+inner.
   //
   // State 0 itself is ignored, since we don't care
   // what action the opponent takes (we always load);
   // thus, it's not represented here.
   //
   // os stores states 1 through 15 (index 0 through 14).
   struct Opponent {
      std::vector<OpponentAction> os;
      Opponent() : os(15) {}
   };
   StudiousPlayer(size_t opponent)
      : Player(opponent)
      , strat(storedLs()[opponent])
      , ammoOpponent()
   {
   }
   Player::Action fight() {
      // Compute the current "ammo state".
      // For convenience here (aka, readability in switch),
      // this is a two digit octal number.  The lso is the
      // inner state, and the mso the outer state.
      unsigned ss,os;
      switch (ammoOpponent) {
      default: os=030; break;
      case 2 : os=020; break;
      case 1 : os=010; break;
      case 0 : os=000; break;
      }
      switch (getAmmo()) {
      default: ss=003; break;
      case 2 : ss=002; break;
      case 1 : ss=001; break;
      case 0 : ss=000; break;
      }
      // Store the ammo state.  This has a side effect
      // of causing actn() to return an OpponentAction
      // struct, with the opponent's history during this
      // state.
      osa = os+ss;
      // Get the opponent action pointer
      const OpponentAction* a=actn(osa);
      // If there's no such action structure, assume
      // we're just supposed to load.
      if (!a) return load();
      // Apply ammo-state based strategies:
      switch (osa) {
      case 001:
         // If opponent's likely to load, shoot; else load
         if (a->l > a->m) return bullet();
         return load();
      case 002:
      case 003:
         // Shoot in the way most likely to kill (or randomly)
         if (a->t > a->m+a->l) return bullet();
         if (a->m > a->t+a->l) return plasma();
         if (GetRandomInteger(1)) return bullet();
         return plasma();
      case 010:
         // If opponent tends to load, load; else defend
         if (a->l > a->b) return load();
         return metal();
      case 011:
         // Shoot if opponent tends to load
         if (a->l > a->b+a->m) return bullet();
         // Defend if opponent tends to shoot
         if (a->b > a->l+a->m) return metal();
         // Load if opponent tends to defend
         if (a->m > a->b+a->l) return load();
         // Otherwise randomly respond
         if (!GetRandomInteger(2)) return metal();
         if (!GetRandomInteger(1)) return load(); 
         return bullet();                         
      case 012:
      case 013:
         // If opponent most often shoots, defend
         if (a->b > a->l+a->m+a->t) return metal();
         // If opponent most often thermals, use bullet
         if (a->t > a->m) return bullet();
         // If opponent most often metals, use plasma
         if (a->m > a->t) return plasma();
         // Otherwise use a random weapon
         return (GetRandomInteger(1))?bullet():plasma();
      case 020:
         // If opponent most often loads or defends, load
         if (a->l+a->m+a->t > a->b+a->p) return load();
         // If opponent most often shoots bullets, raise metal
         if (a->b > a->p) return metal();
         // If opponent most often shoots plasma, raise thermal
         if (a->p > a->b) return thermal();
         // Otherwise raise random defense
         return (GetRandomInteger(1))?metal():thermal();
      case 021:
      case 031:
         // If opponent loads more often than not,
         if (a->l > a->m+a->b+a->p) {
            // Tend to shoot (67%), but possibly load (33%)
            return (GetRandomInteger(2))?bullet():load();
         }
         // If opponent metals more often than loads or shoots, load
         if (a->m > a->l+a->b+a->p) return load();
         // If opponent thermals (shrug) more often than loads or shoots, load
         if (a->t > a->l+a->b+a->p) return load();
         // If opponent tends to shoot bullets, raise metal
         if (a->b > a->p) return metal();
         // If opponent tends to shoot plasma, raise thermal
         if (a->p > a->b) return thermal();
         // Raise random shield
         return (GetRandomInteger(2))?metal():thermal();
      case 022:
         // If opponent loads or thermals more often than not, shoot bullet
         if (a->l+a->t > a->b+a->p+a->m) return bullet();
         // If opponent loads or metals more often than not, shoot plasma
         if (a->l+a->m > a->b+a->p+a->t) return plasma();
         // If opponent shoots more than loads or defends, defend
         if (a->b+a->p > a->l+a->m+a->t) {
            if (a->b > a->p) return metal();
            if (a->p > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // If opponent defends more than opponent shoots, load
         if (a->m+a->t > a->b+a->p) return load();
         // Use random substrategy;
         // load(33%)
         if (GetRandomInteger(2)) return load();
         // defend(33%)
         if (GetRandomInteger(1)) {
            if (a->b > a->p) return metal();
            if (a->b > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // Shoot in a way that most often kills (or randomly)
         if (a->m > a->t) return plasma();
         if (a->t > a->m) return bullet();
         return (GetRandomInteger(1))?bullet():plasma();
      case 023:
         // If opponent loads or raises thermal more often than not, shoot bullets
         if (a->l+a->t > a->b+a->p+a->m) return bullet();
         // If opponent loads or raises metal more often than not, shoot plasma
         if (a->l+a->m > a->b+a->p+a->t) return plasma();
         // If opponent shoots more than loads or defends, defend
         if (a->b+a->p > a->l+a->m+a->t) {
            if (a->b > a->p) return metal();
            if (a->p > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // If opponent defends more than shoots, shoot
         if (a->m+a->t > a->b+a->p) {
            if (a->m > a->t) return plasma();
            if (a->t > a->m) return bullet();
            return GetRandomInteger(1)?bullet():plasma();
         }
         // 50% defend
         if (GetRandomInteger(1)) {
            if (a->b > a->p) return metal();
            return thermal();
         }
         // 50% shoot
         if (a->m > a->t) return plasma();
         if (a->t > a->m) return bullet();
         return (GetRandomInteger(1))?bullet():plasma();
      case 030:
         // If opponent loads or shields more often than not, load
         if (a->l+a->m+a->t > a->b+a->p) return load();
         // If opponent tends to shoot, defend
         if (a->b+a->p >= a->l+a->m+a->t) {
            if (a->b > a->p) return metal();
            if (a->p > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // Otherwise, randomly shield (50%) or load
         if (GetRandomInteger(1)) {
            return (GetRandomInteger(1))?metal():thermal();
         }
         return load();
      case 032:
         // If opponent loads or thermals more often than not, shoot bullets
         if (a->l+a->t > a->b+a->p+a->m) return bullet();
         // If opponent loads or metals more often than not, shoot plasma
         if (a->l+a->m > a->b+a->p+a->t) return plasma();
         // If opponent shoots more often than loads or shields, defend
         if (a->b+a->p > a->l+a->m+a->t) {
            if (a->b > a->p) return metal();
            if (a->p > a->b) return thermal();
            return (GetRandomInteger(1))?metal():thermal();
         }
         // If opponent shields more often than shoots, load
         if (a->m+a->t > a->b+a->p) return load();
         // Otherwise use random strategy
         if (GetRandomInteger(2)) return load();
         if (GetRandomInteger(1)) {
            if (a->b > a->p) return metal();
            return thermal();
         }
         if (a->m > a->t) return plasma();
         if (a->t > a->m) return bullet();
         return (GetRandomInteger(1))?bullet():plasma();
      case 033:
         {
            // At full 3 on 3, apply random strategy
            // weighted by opponent's histogram of this state...
            // (the extra 1 weights towards plasma)
            unsigned sr=
               GetRandomInteger
               (a->l+a->t+a->p+a->b+a->m+1);
            // Shoot bullets proportional to how much
            // opponent loads or defends using thermal
            if (sr < a->l+a->t) return bullet();
            sr-=(a->l+a->t);
            // Defend with thermal proportional to how
            // much opponent attacks with plasma (tending to
            // waste his ammo)
            if (sr < a->p) return thermal();
            // Shoot plasma proportional to how
            // much opponent shoots bullets or raises metal
            return plasma();
         }
      }
      // Should never hit this; but rather than ruin everyone's fun,
      // if we do, we just load
      return load();
   }
   // Complete override; we use our opponent's model, not history.
   void perceive(Player::Action action) {
      // We want the ammo but not the history; since
      // the framework (Player::perceive) is "all or nothing", 
      // StudiousPlayer just tracks the ammo itself
      switch (action) {
      default: break;
      case Player::LOAD:   ++ammoOpponent; break;
      case Player::BULLET: --ammoOpponent; break;
      case Player::PLASMA: ammoOpponent-=2; break;
      }
      // Now we get the opponent's action based
      // on the last (incoming) ammo state
      OpponentAction* a = actn(osa);
      // ...if it's null just bail
      if (!a) return;
      // Otherwise, count the action
      switch (action) {
      case Player::LOAD    : ++a->l; break;
      case Player::BULLET  : ++a->b; break;
      case Player::PLASMA  : ++a->p; break;
      case Player::METAL   : ++a->m; break;
      case Player::THERMAL : ++a->t; break;
      }
   }
private:
   Opponent& strat;
   OpponentAction* actn(unsigned octalOsa) {
      unsigned ndx = (octalOsa%4)+4*(octalOsa/8);
      if (ndx==0) return 0;
      --ndx;
      if (ndx<15) return &strat.os[ndx];
      return 0;
   }
   unsigned osa;
   unsigned ammoOpponent;
   // Welcome, non-C++ persons, to the "Meyers style singleton".
   // "theMap" is initialized (constructed; initially empty)
   // the first time the declaration is executed.
   static std::unordered_map<size_t, Opponent>& storedLs() {
      static std::unordered_map<size_t, Opponent> theMap;
      return theMap;
   }
};

#endif

Lưu ý rằng điều này theo dõi thông tin về đối thủ theo các quy tắc của thử thách; xem phương thức "Meyers style singleton" scoped "archiveLs ()" ở phía dưới. (Một số người đã tự hỏi làm thế nào để làm điều này; bây giờ bạn biết!)


1
Tôi không biết nó được gọi là singleton theo phong cách Meyers cho đến khi tôi thấy điều này!
Frenzy Li

1
Đừng quá coi trọng thuật ngữ này - đó là một sự lạm dụng các thuật ngữ, vì "singleton" là một khởi tạo mẫu chứ không phải là một cấu trúc được khai báo, nhưng đó là cùng một kỹ thuật.
H Walters

6

Các GunClubPlayer

Cắt ra từ câu hỏi ban đầu. Điều này phục vụ như một ví dụ về việc thực hiện tối giản của một người chơi dẫn xuất. Người chơi này sẽ tham gia giải đấu.

Các GunClubPlayers muốn đi đến câu lạc bộ súng. Trong mỗi trận đấu, lần đầu tiên họ sẽ nạp đạn, sau đó bắn một viên đạn, và lặp lại quá trình này cho đến khi kết thúc của thế giới đấu. Họ không thực sự quan tâm liệu họ có chiến thắng hay không, và chỉ tập trung vào việc có một trải nghiệm thú vị.

// 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
Bạn không cần người khác sau khi tuyên bố trở lại, phải không? Tôi biết đó không phải là mã golf nhưng nó cảm thấy sai.
Pavel

2
@Pavel Chà, OK, vậy ... bây giờ ... nó giống như chơi golf.
Frenzy Li

5

Các PlasmaPlayer

Người chơi Plasma như bắn bu lông plasma của mình. Anh ta sẽ cố gắng tải và bắn càng nhiều càng tốt. Tuy nhiên, trong khi đối thủ có đạn plasma, anh ta sẽ sử dụng lá chắn nhiệt của mình (đạn dành cho kẻ yếu).

#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ảm ơn các nhà xây dựng! C ++ của tôi hơi rỉ sét và tôi không có trình biên dịch trên máy này.
Brian J

Không có gì! Tôi vẫn đang thêm nhiều mã (in bảng điểm, đọc tập lệnh bên ngoài, v.v.) cho dự án và thật may mắn khi chưa có bài dự thi nào bị hỏng.
Frenzy Li

Điều này sẽ hoạt động tốt cho bất kỳ đối thủ nào ngoài GunClub. Vâng, nó sẽ giết SadisticShooter (tốt nhất). @BrianJ
devR Rich

5

Rất SadisticShooter

Ông thà nhìn bạn đau khổ còn hơn giết bạn. Anh ta không ngu ngốc và sẽ che đậy bản thân theo yêu cầu.

Nếu bạn hoàn toàn nhàm chán và có thể dự đoán được, anh ta sẽ giết bạn ngay lập tức.

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

Tôi thấy bạn đã sửa nó.
devRicher

4

Các TurtlePlayer

TurtlePlayerlà một kẻ hèn nhát. Anh ta dành phần lớn thời gian để ẩn đằng sau những chiếc khiên của mình - do đó có tên. Đôi khi, anh ta có thể ra khỏi vỏ của mình (không có ý định chơi chữ) và có một phát súng, nhưng anh ta thường nằm thấp trong khi kẻ thù có đạn.


Bot này không đặc biệt tuyệt vời - tuy nhiên, mỗi KOTH cần một số mục ban đầu để chạy nó :)

Thử nghiệm địa phương cho thấy điều này chiến thắng cả hai GunClubPlayerOpportunist100% thời gian. Một trận chiến chống lại BotRobotPlayerdường như luôn luôn dẫn đến một trận hòa khi cả hai ẩn đằng sau tấm khiên của họ.

#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

Các DeceptivePlayer

Người chơi lừa đảo cố gắng nạp hai viên đạn và sau đó bắn một viên.

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

Tôi không viết mã trong c ++ nên mọi cải tiến về mã sẽ được hoan nghênh.


Chỉnh sửa của tôi là về định nghĩa modulo và macro. Không chắc chắn bạn sẽ thích nó, nhưng có lẽ DeceptivePlayerlà một cái tên tốt hơn?
Frenzy Li

@FrenzyLi Vâng, tôi thích nó, tôi sẽ đổi tên
Sxntk

1
@Sxntk Tôi thích sự trớ trêu khi người chơi này mong đợi những người có 2 viên đạn sẽ bắn plasma, nhưng bản thân anh ta sẽ cầm hai viên đạn và bắn một viên đạn.
Brian J

@Sxntk Bạn không có khả năng không trả lại bất cứ điều gì hiện tại. Một người chơi được phép nhiều hơn hai đạn. Vì vậy, nếu đối thủ của bạn có 3+ đạn, bạn không có hành động. Bạn có thể kết thúc với một khẩu súng nổ ở đâu đó. (tất nhiên, dù sao đó cũng có thể là kế hoạch tổng thể của bạn :))
Brian J

@BrianJ Cảm ơn, tôi sẽ suy nghĩ về nó, trong khi đó thì ta sẽ tha lừa dối đi điên và quyết định phải làm gì khi oponnent có 3+ đạn
Sxntk

2

HanSoloPlayer

Bắn trước! Vẫn đang làm việc để sửa đổi nó, nhưng điều này là khá tốt.

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

Các CamtoPlayer

CamtoPlayer ghét trận hòa và sẽ thoát ra khỏi vòng lặp không có vấn đề gì phải mất. (trừ tự tử)

Đây là chương trình C ++ đầu tiên của tôi có thể làm bất cứ điều gì, vì vậy đừng đánh giá nó quá khó.

Tôi biết nó có thể tốt hơn nhưng xin đừng chỉnh sửa nó.
Nếu bạn muốn sửa đổi mã chỉ cần bình luận một đề nghị.

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

Bạn đang quên một#endif // ! __CAMTO_HPP__
Blue

@muddyfish Cảm ơn bạn đã nói với tôi, tôi có ít hơn các biểu tượng đã dừng mã kết xuất! XD
Benjamin Philippe

Vẫn không hiện lên. Tôi sẽ khuyên bạn nên bỏ các thẻ HTML hoàn toàn và chỉ sử dụng markdown (nút "Mẫu mã" có "{}" trên đó). Trích dẫn bằng tay <>&là một nỗi đau.
H Walters

@HWalters Cảm ơn vì tiền boa!
Benjamin Philippe

Cảm ơn vì đã tham gia. Và một điều: xin vui lòng loại bỏ using namespace stdbởi vì nó liên quan đến giải đấu. Nếu bạn muốn gỡ lỗi, bạn có thể sử dụng, std::coutv.v.
Frenzy Li

1

Các SurvivorPlayer

Người chơi Survivor hành xử theo cách tương tự như Người chơi Rùa và Rào chắn. Anh ta sẽ không bao giờ thực hiện một hành động có thể dẫn đến cái chết của anh ta và thà thực thi một trận hòa hơn là để thua cuộc chiến.

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

Các FatedPlayer

Được tạo bởi Clotho, ghi điểm bởi Lachesis và bị Atropos giết chết ; Chiến lược duy nhất của người chơi này là sử dụng những gì họ biết về đạn để xác định hành động nào là hợp lý.

Tuy nhiên, nó không được chọn hành động; phần đó được để lại cho các vị thần.

#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

... bởi vì tôi muốn xem một người chơi ngẫu nhiên xếp hạng như thế nào.


1

Cụ thể

Cụ thểPlayer tuân theo một kế hoạch đơn giản là chọn một số hành động ngẫu nhiên (hợp lệ). Tuy nhiên, tính năng chính của nó là tìm ra các tình huống nhất định bằng cách phân tích số lượng đạn và di chuyển trước đó của đối thủ.

Đây là lần đầu tiên tôi viết bất cứ điều gì trong C ++ và lần đầu tiên cố gắng thực hiện bất kỳ loại viết bot cạnh tranh nào. Vì vậy, tôi hy vọng nỗ lực ít ỏi của mình ít nhất là làm điều gì đó thú vị. :)

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

Câu chuyện về sự sáng tạo của nó sẽ đến sau.

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

"Câu chuyện về sự sáng tạo của nó sẽ đến sau" đã hơn 3 tháng :)
HyperNeutrino
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.