ดวลปืนแห่งอนาคต


73

พื้นหลังในอนาคต

ในปี 2560 คุณและคู่ต่อสู้ของคุณจะเผชิญหน้ากันในการต่อสู้ด้วยปืนแห่งอนาคต อยู่ที่คุณมีประสบการณ์มากพอที่จะเอาชนะฝ่ายตรงข้ามของคุณหรือไม่ ตอนนี้เป็นเวลาที่จะขัดเกลาทักษะปืนของคุณในภาษาการเขียนโปรแกรมที่คุณชื่นชอบและต่อสู้กับราคาทั้งหมด!

ผลการแข่งขัน

การแข่งขันครั้งนี้สิ้นสุดลงในช่วงเช้าของ UTC กุมภาพันธ์ 2 ครั้ง 2017 ขอขอบคุณผู้เข้าแข่งขันของเราเรามีการแข่งขันมากมายที่น่าตื่นเต้น!

MontePlayer เป็นผู้ชนะคนสุดท้ายหลังจากการต่อสู้อย่างใกล้ชิดกับ CBetaPlayer และ StudiousPlayer นักสู้ guen duelers ทั้งสามคนได้ถ่ายรูปที่ระลึก:

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

ขอแสดงความยินดีกับผู้ชนะ! กระดานแต้มนำแบบละเอียดมีให้เห็นในตอนท้ายของโพสต์นี้

คำแนะนำทั่วไป

  • เยี่ยมชมพื้นที่เก็บข้อมูลอย่างเป็นทางการสำหรับซอร์สโค้ดที่ใช้ในทัวร์นาเมนต์นี้
  • รายการ C ++: โปรดรับPlayerชั้นเรียน
  • องค์กรไม่แสวงหา c ++ รายการ: เลือกหนึ่งในอินเตอร์เฟซในส่วนอินเตอร์เฟซสำหรับการส่ง ++-C บุหรี่
  • ขณะนี้ไม่อนุญาตให้ใช้ภาษา C ++: Python 3, Java

การดวล

  • ผู้เล่นแต่ละคนเริ่มด้วยปืนที่ไม่บรรจุกระสุนซึ่งสามารถบรรจุกระสุนได้ไม่ จำกัด
  • แต่ละเทิร์นผู้เล่นจะเลือกจากหนึ่งในการกระทำต่อไปนี้:
    • 0 - บรรจุกระสุน 1 นัดเข้าไปในปืน
    • 1- ยิงกระสุนใส่ฝ่ายตรงข้าม; ราคากระสุน 1 นัด
    • 2- ยิงลำแสงพลาสม่าที่ฝ่ายตรงข้าม; ราคากระสุน 2 นัด
    • - - ป้องกันกระสุนที่เข้ามาโดยใช้โล่โลหะ
    • = - ป้องกันลำแสงพลาสม่าที่เข้ามาโดยใช้ตัวกระจายความร้อน
  • หากผู้เล่นทั้งสองอยู่รอดหลังจากที่ 100 วันที่กลับกันพวกเขาทั้งไอเสียไปสู่ความตายซึ่งผลในการวาด

ผู้เล่นแพ้ปืนดวลหากพวกเขา

  • ไม่ไม่ใช้โล่โลหะเพื่อป้องกันกระสุนเข้า
  • ไม่ไม่ใช้ความร้อนดันเพื่อปกป้องพลาสม่าที่เข้ามา
  • ยิงปืนโดยไม่โหลดกระสุนเพียงพอซึ่งปืนของพวกเขาจะระเบิดเองและฆ่าเจ้าของ

คำเตือน

ตามคู่มือสำหรับเจ้าของปืนแห่งอนาคต :

  • โล่โลหะไม่สามารถป้องกันจากลำแสงพลาสมาที่เข้ามา ในทำนองเดียวกันตัวป้องกันความร้อนไม่สามารถป้องกันจากกระสุนที่เข้ามา
  • ลำแสงพลาสม่ามีพลังเหนือกระสุน (เพราะในอดีตต้องการกระสุนที่โหลดมากกว่า) ดังนั้นหากผู้เล่นยิงลำแสงพลาสม่าที่ฝ่ายตรงข้ามที่ยิงกระสุนในตาเดียวกันผู้แข่งขันจะถูกฆ่า
  • หากผู้เล่นทั้งสองยิงกระสุนใส่กันในเทิร์นเดียวกันกระสุนจะถูกยกเลิกและผู้เล่นทั้งสองจะมีชีวิตรอด ในทำนองเดียวกันหากผู้เล่นทั้งสองยิงลำแสงพลาสม่าไปด้วยกันในตาเดียวผู้เล่นทั้งสองจะมีชีวิตรอด

เป็นที่น่าสังเกตว่า:

  • คุณจะไม่ทราบว่าการกระทำของฝ่ายตรงข้ามในทางกลับกันจนกว่ามันจะจบลง
  • การเบี่ยงเบนลำแสงพลาสมาและกระสุนป้องกันจะไม่เป็นอันตรายต่อคู่ต่อสู้ของคุณ

ดังนั้นจึงมีการรวมการกระทำที่ถูกต้องรวม 25 รายการในแต่ละเทิร์น:

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

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

ตัวอย่างการต่อสู้

นี่คือการต่อสู้ที่ฉันเคยมีกับเพื่อน ย้อนกลับไปตอนนั้นเราไม่ค่อยรู้เรื่องการเขียนโปรแกรมมากนักดังนั้นเราจึงใช้ท่าทางมือและส่งสัญญาณด้วยความเร็วสองรอบต่อวินาที จากซ้ายไปขวาการกระทำของเรากลับ:

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

ตามกฎข้างต้นฉันแพ้ คุณเห็นเหตุผลไหม นั่นเป็นเพราะฉันยิงลำแสงพลาสมาสุดท้ายเมื่อฉันมีกระสุนเพียง 1 นัดเท่านั้นทำให้ปืนของฉันระเบิด


ผู้เล่น C ++

คุณในฐานะโปรแกรมเมอร์แห่งอนาคตผู้มีอารยธรรมจะไม่จัดการกับปืนโดยตรง แต่คุณต้องเขียนรหัสPlayerที่ต่อสู้กับคนอื่นแทน โดยการสืบทอดคลาสสาธารณะในโครงการ GitHub คุณสามารถเริ่มเขียนคำอธิบายแผนภูมิในเมืองของคุณได้

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

สิ่งที่คุณต้องหรือสามารถทำ

  • คุณต้องสืบทอดPlayerคลาสผ่านการสืบทอดสาธารณะและประกาศคลาสของคุณเป็นครั้งสุดท้าย
  • คุณต้องลบล้างPlayer::fightซึ่งจะคืนค่าที่ถูกต้องPlayer::Actionทุกครั้งที่มีการเรียกใช้
  • อีกทางเลือกหนึ่งแทนที่Player::perceiveและPlayer::declaredคอยจับตาดูการกระทำของคู่ต่อสู้และติดตามชัยชนะของคุณ
  • คุณสามารถใช้สมาชิกคงที่ส่วนตัวและวิธีการในชั้นเรียนของคุณเพื่อทำการคำนวณที่ซับซ้อนมากขึ้น
  • เลือกใช้ไลบรารีมาตรฐาน C ++ อื่น ๆ

สิ่งที่คุณต้องไม่ทำ

  • คุณต้องไม่ใช้วิธีการโดยตรงใด ๆ ในการจดจำคู่ต่อสู้ของคุณนอกเหนือจากตัวระบุของคู่ต่อสู้ที่กำหนดซึ่งจะสับในตอนเริ่มต้นของแต่ละทัวร์นาเมนต์ คุณได้รับอนุญาตเท่านั้นที่จะเดาได้ว่าผู้เล่นคนใดที่ผ่านการเล่นเกมในทัวร์นาเมนต์
  • คุณจะต้องไม่แทนที่วิธีการใด ๆ ในPlayerชั้นเรียนที่ไม่ได้ประกาศเสมือน
  • คุณต้องไม่ประกาศหรือเริ่มต้นสิ่งใดในขอบเขตส่วนกลาง
  • ตั้งแต่การเดบิวต์ของ (ตอนนี้ถูกตัดสิทธิ์) BlackHatPlayerผู้เล่นจะไม่ได้รับอนุญาตให้มองหรือแก้ไขสถานะของฝ่ายตรงข้ามของคุณ

ตัวอย่างการต่อสู้

กระบวนการของการดวลปืนดำเนินการโดยใช้GunDuelคลาส สำหรับการต่อสู้ตัวอย่างเช่นดูSource.cppในส่วนของการเริ่มต้นการต่อสู้

เราแสดงGunClubPlayer, HumanPlayerและGunDuelชั้นซึ่งสามารถพบได้ในTournament\ไดเรกทอรีของพื้นที่เก็บข้อมูล

ในการต่อสู้แต่ละครั้งGunClubPlayerจะโหลดกระสุน ยิงมัน ล้างและทำซ้ำ ในทุกเทิร์นHumanPlayerจะแจ้งให้คุณสำหรับการกระทำที่จะเล่นกับฝ่ายตรงข้ามของคุณ ควบคุมด้วยแป้นพิมพ์ของคุณมีตัวละคร0, 1, 2, และ- =บน Windows คุณสามารถใช้HumanPlayerเพื่อดีบักการส่งของคุณ

เริ่มต้นการต่อสู้

นี่คือวิธีที่คุณสามารถดีบักเครื่องเล่นของคุณผ่านทางคอนโซล

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

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

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

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

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

เกมตัวอย่าง

จำนวนน้อยที่สุดของผลัดที่คุณจำเป็นต้องพ่ายแพ้GunClubPlayerเป็น 3. นี่คือรีเพลย์จากการเล่นกับ0-1 GunClubPlayerจำนวนใน paranthesis คือจำนวนกระสุนที่บรรจุกระสุนสำหรับผู้เล่นแต่ละคนเมื่อการเลี้ยวสิ้นสุดลง

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

วิธีที่เร็วที่สุดที่จะเอาชนะได้โดยGunClubPlayerไม่ทำการเคลื่อนไหวที่ไม่ถูกต้องคือลำดับ0=เนื่องจากกระสุนยิงผ่านตัวกระจายความร้อน การเล่นซ้ำคือ

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

การแข่งขัน

การแข่งขันเป็นไปตามรูปแบบ "สถานะผู้เล่นคนสุดท้าย" ในทัวร์นาเมนต์การส่งที่ถูกต้องทั้งหมด (รวมถึงGunClubPlayer) จะอยู่ในกลุ่ม การส่งแต่ละครั้งจะถูกกำหนดตัวระบุแบบสุ่ม แต่ไม่ซ้ำกันที่จะยังคงเหมือนเดิมในระหว่างการแข่งขันทั้งหมด ในแต่ละรอบ:

  • การส่งแต่ละครั้งเริ่มต้นด้วย 0 คะแนนและจะเล่น 100 ดวลกับการส่งอื่น ๆ
  • การต่อสู้เพื่อชัยชนะแต่ละครั้งจะมอบ 1 แต้ม การวาดและการสูญเสียให้ 0 คะแนน
  • ในตอนท้ายของรอบการส่งที่มีคะแนนขั้นต่ำออกจากการแข่งขัน ในกรณีที่เสมอกันผู้เล่นที่มีคะแนนน้อยที่สุดจะได้รับตั้งแต่เริ่มต้นการแข่งขัน
  • หากมีผู้เล่นมากกว่าหนึ่งคนรอบต่อไปจะเริ่มขึ้น
  • คะแนนจะไม่นำไปสู่รอบต่อไป

ส่ง

คุณจะส่งผู้เล่นหนึ่งคนต่อคำตอบ คุณสามารถส่งหลายไฟล์สำหรับผู้เล่นตราบใดที่พวกเขาไม่ได้ยุ่งเกี่ยวกับการส่งอื่น ๆ หากต้องการให้สิ่งต่าง ๆ ราบรื่นโปรด:

  • ชื่อไฟล์ส่วนหัวหลักของคุณเป็น<Custom>Player.hpp,
  • ชื่อไฟล์อื่น ๆ ของคุณเป็น<Custom>Player*.*เช่นMyLittlePlayer.txtถ้าชื่อชั้นของคุณMyLittlePlayerหรือถ้าชื่อชั้นของคุณEmoPlayerHates.cppEmoPlayer
  • หากชื่อของคุณมีShooterหรือคำที่คล้ายกันที่เหมาะสมกับบริบทของทัวร์นาเมนต์นี้คุณไม่จำเป็นต้องเพิ่มPlayerในตอนท้าย ถ้าคุณรู้สึกอย่างแรงกล้าว่าชื่อส่งของคุณทำงานได้ดีขึ้นโดยไม่ต้องต่อท้ายคุณยังไม่จำเป็นต้องเพิ่มPlayerPlayer
  • ตรวจสอบให้แน่ใจว่าโค้ดของคุณสามารถรวบรวมและเชื่อมโยงกับ Windows ได้

คุณสามารถแสดงความคิดเห็นเพื่อขอคำชี้แจงหรือจุดช่องโหว่ หวังว่าคุณจะสนุกไปกับการดวลปืนแห่งอนาคตและขอให้คุณมีความสุขในปีใหม่!

การอธิบาย

  • คุณได้รับอนุญาตให้มีพฤติกรรมแบบสุ่ม
  • การกระทำที่ไม่ถูกต้อง (อนุญาตเมื่อกระสุนถูกโหลดไม่เพียงพอ)
  • หากผู้เล่นทำการป้อนที่ไม่ถูกต้องปืนของพวกเขาจะระเบิดทันที
  • คุณได้รับอนุญาตให้ศึกษาคำตอบ
  • คุณได้รับอนุญาตอย่างชัดเจนให้บันทึกพฤติกรรมของคู่ต่อสู้ในแต่ละทัวร์นาเมนต์
  • ในแต่ละรอบคุณจะเล่น 100 ดวลต่อคู่ต่อสู้แต่ละคู่ อย่างไรก็ตามคำสั่งของการดวล 100 ครั้งนั้นถูกสุ่ม - คุณไม่ได้รับประกันว่าจะต่อสู้กับคู่ต่อสู้ 100 คู่ต่อเนื่องกัน

แหล่งข้อมูลเพิ่มเติม

@flawr แปลแหล่ง C ++ ที่ให้มาลงใน Javaเป็นการอ้างอิงหากคุณต้องการส่งรายการ C ++

อินเตอร์เฟสสำหรับการส่งที่ไม่ใช่ C ++

ปัจจุบันได้รับการยอมรับ: Python 3, Java

โปรดปฏิบัติตามหนึ่งในข้อกำหนดด้านล่าง:

สเปคอินเตอร์เฟซ 1: รหัสทางออก

การส่งของคุณจะทำงานหนึ่งครั้งต่อเทิร์น

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

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

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

คุณสามารถทดสอบการส่งของคุณในPythonPlayer\และJavaPlayer\ไดเรกทอรี

ข้อมูลจำเพาะอินเตอร์เฟส 2: stdin / stdout

(ให้เครดิตกับ H Walters)

การส่งของคุณจะทำงานหนึ่งครั้งต่อทัวร์นาเมนต์

มีข้อกำหนดที่แน่นอนสำหรับรายการทั้งหมดเกี่ยวกับวิธีการทำ I / O เนื่องจากทั้ง stdin และ stdout เชื่อมต่อกับไดรเวอร์ทัวร์นาเมนต์ การละเมิดสิ่งนี้อาจนำไปสู่การหยุดชะงัก รายการทั้งหมดต้องเป็นไปตามอัลกอริทึมที่แน่นอนนี้(ในรหัสหลอก):

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

นี่เรนไฮน์เป็นหนึ่ง0, 1, 2, -หรือสำหรับ= load / bullet / plasma / metal / thermalกระบวนการหมายถึงการตอบสนองทางเลือกกับสิ่งที่คู่ต่อสู้ทำ (รวมถึงการติดตามกระสุนของฝ่ายตรงข้ามหากคุณทำเช่นนี้) โปรดทราบว่าการกระทำของฝ่ายตรงข้ามก็เป็นหนึ่งใน '0', '1', '2', '-' หรือ '=' และอยู่ในตัวละครที่สอง

สกอร์บอร์ดสุดท้าย

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

การแข่งขันจะมีอายุจนถึง1 กุมภาพันธ์ 2017เว้นแต่จะระบุไว้เป็นอย่างอื่น


15
ความท้าทายครั้งแรกที่น่าประทับใจโดยวิธี!
Martin Ender

3
หากคุณยินดีที่จะเรียกใช้ภาษาอื่น ๆ คุณสามารถอนุญาตให้มีPlayerการใช้งานที่เรียกใช้กระบวนการอื่นเพื่อคำนวณการเลี้ยวในปัจจุบัน ซึ่งจะช่วยให้ผู้คนมีส่วนร่วมในภาษาใด ๆ ที่คุณยินดีที่จะทำงานบนเครื่อง
Martin Ender

5
การสุ่มอนุญาตหรือไม่ (ไม่สุ่มหมุนอย่างสมบูรณ์เพียง 50/50 ตัวเลือกของการกระทำในบางสถานการณ์)
FlipTack

2
จุดทางเทคนิค "คุณต้องได้รับมรดกPlayer::fight'/' คุณสามารถได้รับมรดกPlayer::perceive" ... ในทั้งสองกรณีคำคือแทนที่ไม่ได้รับมรดก
H Walters

3
ฉันคิดว่าคุณมีข้อบกพร่องGunDuel.hppทั้งในvalidAและvalidBใช้actionA
AlexRacer

คำตอบ:


9

MontePlayer

ผู้เล่นนี้ใช้อัลกอริธึมการค้นหาต้นไม้แบบแยกคู่ UCT Monte Carlo เพื่อตัดสินใจเลือกสิ่งที่ควรทำ มันติดตามสิ่งที่ศัตรูทำเพื่อทำนายการกระทำของมัน มันจำลองศัตรูด้วยตัวมันเองถ้ามันขาดข้อมูล

บอทนี้ทำงานได้ดีกับบอทอื่น ๆ ยกเว้น c except ในการแข่งขัน 10,000 ดวลกับcβมอนเต้ชนะ 5246 ดวล ด้วยคณิตศาสตร์เล็กน้อยนั่นหมายความว่า Monte จะชนะการต่อสู้เมื่อเทียบกับ 51.17% เป็น 53.74% ของเวลา (ความมั่นใจ 99%)

#ifndef __Monte_PLAYER_HPP__
#define __Monte_PLAYER_HPP__

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


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

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

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


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

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

    struct Stat
    {
        int wins;
        int attempts;

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

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

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

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

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

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


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

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

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

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

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

public:

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

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

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

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

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

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

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

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

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

        return (Action)move;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return game;
    }

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

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

        //Choose random opponent action.
        int opponentAction;

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

        validMoves = 0;

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

        return doTurn(game, myAction, opponentAction);
    }

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

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

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

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

        return best;

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

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

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

#endif // !__Monte_PLAYER_HPP__

25

BlackHatPlayer

ผู้เล่น BlackHat รู้ว่ากระสุนและโล่เป็นเรื่องของอดีต สงครามที่เกิดขึ้นจริงนั้นชนะโดยผู้ที่สามารถแฮ็กโปรแกรมของคู่ต่อสู้ได้

ดังนั้นเขาสวมเกราะป้องกันโลหะและเริ่มทำสิ่งที่เขาทำ

ครั้งแรกที่เขาจะขอให้fightเขาพยายามที่จะ จำกัด วงศัตรูของเขาในความทรงจำ เมื่อพิจารณาถึงโครงสร้างของเวทีการต่อสู้มันเกือบจะแน่ใจว่าคอมไพเลอร์จะวางที่อยู่ของเขา (ห่อในunique_ptr) และคู่ต่อสู้คนใดคนหนึ่งติดกับอีกคนหนึ่ง

ดังนั้น BlackHat เดินกองอย่างระมัดระวังโดยใช้ฮิวริสติกแบบง่าย ๆ เพื่อให้แน่ใจว่าไม่ได้ underflow จนกว่าจะพบตัวชี้กับตัวเอง จากนั้นจะตรวจสอบว่าค่าในตำแหน่งที่อยู่ติดกันมีเหตุผลของฝ่ายตรงข้ามของเขา - ที่อยู่ใกล้เคียงกันอยู่ที่คล้ายกันของ vtable typeidที่เป็นไปได้

หากมันสามารถหาเขาเขาดูดสมองของเขาและแทนที่พวกเขาด้วยคนบ้าคนใจร้อน ในทางปฏิบัติสิ่งนี้ทำได้โดยแทนที่ตัวชี้ของฝ่ายตรงข้ามเป็น vtable ด้วยที่อยู่ของIdiotvtable - ผู้เล่นใบ้ที่มักจะยิง

หากทั้งหมดนี้ประสบความสำเร็จ (และในการทดสอบของฉัน - gcc 6 บน Linux 64 บิต, MinGW 4.8 บนไวน์ 32 บิต - สิ่งนี้ทำงานได้อย่างน่าเชื่อถือมาก) สงครามชนะ ไม่ว่าฝ่ายตรงข้ามจะทำอะไรในรอบแรกไม่สำคัญ - ที่แย่ที่สุดเขายิงเราและเรามีเกราะโลหะติดอยู่

จากนี้ไปเรามีคนบ้าที่เพิ่งถ่ายทำ เรามีโล่ของเราอยู่เสมอดังนั้นเราจึงได้รับการปกป้องและเขาจะระเบิดใน 1 ถึง 3 รอบ (ขึ้นอยู่กับสิ่งที่บอทดั้งเดิมทำในการfightโทรครั้งแรกของเขา)


ตอนนี้: ฉันเกือบแน่ใจว่าสิ่งนี้ควรถูกตัดสิทธิ์ทันที แต่มันตลกที่ฉันไม่ได้ละเมิดกฎใด ๆ ที่ระบุข้างต้นอย่างชัดเจน:

สิ่งที่คุณต้องไม่ทำ

  • คุณจะต้องไม่ใช้วิธีการโดยตรงใด ๆ ในการจดจำคู่ต่อสู้ของคุณนอกเหนือจากตัวระบุของคู่ต่อสู้ที่ให้มาซึ่งจะถูกสุ่มอย่างสมบูรณ์เมื่อเริ่มต้นการแข่งขันแต่ละครั้ง คุณได้รับอนุญาตเท่านั้นที่จะเดาได้ว่าผู้เล่นคนใดที่ผ่านการเล่นเกมภายในทัวร์นาเมนต์

BlackHat ไม่พยายามจดจำคู่ต่อสู้ - จริง ๆ แล้วมันไม่เกี่ยวข้องอย่างสมบูรณ์ว่าคู่ต่อสู้คือใครเพราะสมองของเขาถูกแทนที่ทันที

  • คุณต้องไม่แทนที่วิธีการใด ๆ ในคลาสผู้เล่นที่ไม่ได้ประกาศเสมือน
  • คุณต้องไม่ประกาศหรือเริ่มต้นสิ่งใดในขอบเขตส่วนกลาง

ทุกอย่างเกิดขึ้นภายในfightฟังก์ชันเสมือน


// BlackHatPlayer.hpp

#ifndef __BLACKHAT_PLAYER_HPP__
#define __BLACKHAT_PLAYER_HPP__

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

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

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

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

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

        // Our adversary
        Player *c = nullptr;

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

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

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

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

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

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

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

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

#endif // !__BLACKHAT_PLAYER_HPP__

6
@TheNumberOne: เช่นเดียวกับความคิดเห็นแรก (และ upvoted ที่สุด) ในช่องโหว่ของเธรด: "Loopholes เป็นส่วนหนึ่งของสิ่งที่ทำให้เกมน่าสนใจแม้แต่คนทั่วไปก็สามารถสนุกสนานหรือฉลาดขึ้นอยู่กับบริบท" IMO นี้เป็นต้นฉบับ (อย่างน้อยฉันไม่เคยเห็นอะไรที่คล้ายกันที่นี่) และน่าสนใจพอสมควรวิศวกรรมฉลาด นั่นเป็นเหตุผลที่ฉันแบ่งปันที่นี่
Matteo Italia

3
#ifdef __BLACKHAT_PLAYER_HPP__#error "Dependency issue; to compile, please include this file before BlackHatPlayer.hpp"#else#define __BLACKHAT_PLAYER_HPP__#endif
H วอลเตอร์ส

1
@MatteoItalia BlackHat เพิ่มความรู้เกี่ยวกับช่องโหว่มาตรฐานของเราเสมอ :-)
Frenzy Li

2
@HWalters: ฉันเดาว่าฉันจะต้องเปลี่ยนเป็น#pragma once;-)
Matteo Italia

3
ดูเหมือนง่ายพอที่จะเรียกใช้ผู้เล่นแต่ละคนในกระบวนการแยกต่างหากและใช้ซ็อกเก็ตเพื่อสื่อสารกับผู้ตัดสิน
Jasen

19

ต่อไปสิ่งมีชีวิตที่น่ากลัวที่สุดก็คือไปสู่นรกและย้อนกลับไปและต่อสู้กับบอทอื่น ๆ อย่างแท้จริง 900,000 บอ ...

BotRobot

BotRobot ได้รับการตั้งชื่อให้ฝึกฝนและสร้างโดยอัตโนมัติโดยอัลกอริทึมพันธุกรรมขั้นพื้นฐาน

ทีมสองทีมจาก 9 ทีมถูกสร้างขึ้นเพื่อต่อต้านแต่ละคนในแต่ละรุ่นหุ่นยนต์แต่ละตัวจากทีม 1 จะถูกนำไปวางกับหุ่นยนต์แต่ละทีมของ 2 ทีมหุ่นยนต์ที่ชนะมากกว่าการสูญเสียเก็บความทรงจำไว้ และมีโอกาสลืมบางสิ่งบางอย่างหวังว่าไม่ดี บอทนั้นเป็นตารางการค้นหาที่มีชื่อเสียงซึ่งหากพวกเขาพบบางสิ่งที่ไม่เคยเห็นมาก่อนพวกเขาจะเลือกตัวเลือกที่ถูกต้องแบบสุ่มและบันทึกลงในหน่วยความจำ c ++ รุ่นไม่ทำเช่นนี้มันควรจะได้เรียนรู้ ตามที่ระบุไว้ก่อนหน้านี้บอทที่ชนะจะเก็บความทรงจำที่ค้นพบใหม่นี้ไว้อย่างชัดเจน การสูญเสียบอทไม่และรักษาสิ่งที่พวกเขาเริ่มต้นไว้

ในท้ายที่สุดการต่อสู้ของบอทก็ค่อนข้างใกล้ชิด ผู้ชนะได้รับเลือกออกจากสระว่ายน้ำของทั้งสองทีมโพสต์วิวัฒนาการซึ่งเป็นคนรุ่น 100000

BotRobot ที่สร้างขึ้นแบบสุ่มและชื่อBEAUTIFULถือเป็นผู้โชคดี

เครื่องกำเนิดไฟฟ้า

bot.lua

การตรวจทานแก้ไข: แม้ว่าหุ่นยนต์จะฉลาดกับตัวเองและหุ่นยนต์ที่สร้างขึ้นในทำนองเดียวกัน แต่ก็พิสูจน์ได้ว่าไร้ประโยชน์ในการต่อสู้จริง ดังนั้นฉันจึงสร้างสมองของเขาใหม่กับบอทที่สร้างขึ้นแล้วบางส่วน

ผลลัพธ์ที่สามารถมองเห็นได้ง่ายคือสมองที่ซับซ้อนมากขึ้นโดยมีตัวเลือกขึ้นอยู่กับผู้เล่นศัตรูที่มีกระสุน12นัด

ฉันไม่แน่ใจว่าเขาต่อสู้กับสิ่งใดได้ถึง 12 กระสุน แต่มีบางอย่างเกิดขึ้น

และแน่นอนผลิตภัณฑ์สำเร็จรูป ...

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

#ifndef __BOT_ROBOT_PLAYER_HPP__
#define __BOT_ROBOT_PLAYER_HPP__

#include "Player.hpp"

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

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

        int toDo = 3;

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

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

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

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

#endif // !__BOT_ROBOT_PLAYER_HPP__

ฉันเกลียด C ++ ตอนนี้ ...


@FrenzyLi ไม่แน่ใจว่าฉันไม่ได้สังเกตว่าแก้ไขได้แล้ว
ATaco

ดีหลังจากการปรับปรุงนี้ ธ 00ปทดูเหมือนว่าจะมีการเปิดตัวคงที่ของ
Frenzy Li

ฉันเห็นว่าทำไมตอนนี้ ... "1: 1" ให้ "0"
Frenzy Li

1
ผู้เล่นหลายคนที่นี่มีการแก้ไขเกมของพวกเขาทั้งขึ้นอยู่กับผลัดกันดังนั้นผมจึงไม่คิดว่าการเปิดรับการแก้ไขควรจะเป็นปัญหา
EIS

10

CBetaPlayer (cβ)

สมดุลแนชโดยประมาณ

บอทนี้เป็นเพียงคณิตศาสตร์แฟนซีที่มีตัวห่อรหัส

เราสามารถใส่กรอบใหม่นี้เป็นปัญหาทฤษฎีเกม แสดงว่าชนะโดย +1 และแพ้โดย -1 ทีนี้ให้ B (x, y) เป็นค่าของเกมที่เรามี x ammo และคู่ต่อสู้ของเรามี y ammo โปรดทราบว่า B (a, b) = -B (b, a) และอื่น ๆ B (a, a) = 0 เพื่อค้นหาค่า B ในแง่ของค่า B อื่น ๆ เราสามารถคำนวณค่าเมทริกซ์ผลตอบแทน ตัวอย่างเช่นเรามี B (1, 0) ที่กำหนดโดยค่าของเกมย่อยต่อไปนี้:

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

(ฉันได้ลบตัวเลือก "ไม่ดี" ซึ่งเป็นตัวเลือกที่ควบคุมโดยโซลูชันที่มีอยู่ตัวอย่างเช่นเราจะไม่พยายามยิงพลาสมาเนื่องจากเรามีกระสุนเพียงนัดเดียวเช่นกันฝ่ายตรงข้ามของเราจะไม่ใช้ตัวเปลี่ยนความร้อน เนื่องจากเราจะไม่ยิงพลาสมา)

ทฤษฎีเกมช่วยให้เราทราบวิธีการค้นหาค่าของเมทริกซ์ผลตอบแทนนี้โดยสมมติว่ามีเงื่อนไขทางเทคนิคบางอย่าง เราได้ค่าของเมทริกซ์ด้านบนคือ:

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

ดำเนินการต่อสำหรับเกมที่เป็นไปได้ทั้งหมดและสังเกตว่า B (x, y) -> 1 เป็น x -> อินฟินิตี้ที่มีค่า y คงที่เราสามารถหาค่า B ทั้งหมดซึ่งจะช่วยให้เราคำนวณการเคลื่อนไหวที่สมบูรณ์แบบ!

แน่นอนว่าทฤษฎีแทบจะไม่สอดคล้องกับความเป็นจริง การแก้สมการสำหรับค่าแม้แต่น้อยของ x และ y อย่างรวดเร็วกลายเป็นความซับซ้อนเกินไป เพื่อที่จะจัดการกับสิ่งนี้ฉันแนะนำสิ่งที่ฉันเรียกว่าการประมาณค่า มีพารามิเตอร์ 7 ตัวสำหรับการประมาณนี้: c0, β0, c1, β1, c, βและ k ฉันคิดว่าค่า B เอารูปแบบต่อไปนี้ (แบบฟอร์มเฉพาะส่วนใหญ่ก่อน):

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

เหตุผลคร่าวๆว่าทำไมฉันถึงเลือกพารามิเตอร์เหล่านี้ ครั้งแรกฉันรู้ว่าฉันต้องการจัดการกับกระสุน 0, 1 และ 2 หรือมากกว่านั้นอย่างแน่นอนเนื่องจากแต่ละตัวเลือกเปิดแตกต่างกัน นอกจากนี้ฉันคิดว่าฟังก์ชั่นการอยู่รอดทางเรขาคณิตจะเหมาะสมที่สุดเพราะผู้เล่นฝ่ายรับคาดเดาว่าจะต้องทำอะไร ฉันคิดว่าการมีกระสุน 2 ลูกขึ้นไปนั้นเหมือนกันดังนั้นฉันจึงมุ่งเน้นไปที่ความแตกต่างแทน ฉันต้องการรักษา B (1, 0) เป็นกรณีพิเศษสุดเพราะฉันคิดว่ามันจะปรากฏขึ้นมาก การใช้แบบฟอร์มเหล่านี้ทำให้การคำนวณค่า B ง่ายขึ้นอย่างมาก

ฉันประมาณแก้ไขสมการที่เกิดขึ้นเพื่อให้ได้ค่า B แต่ละค่าซึ่งฉันจะใส่กลับเข้าไปในเมทริกซ์เพื่อรับเมทริกซ์ผลตอบแทน จากนั้นใช้เครื่องมือแก้ปัญหาการเขียนโปรแกรมเชิงเส้นฉันพบความน่าจะเป็นที่ดีที่สุดที่จะย้ายแต่ละครั้งและผลักพวกเขาเข้าไปในโปรแกรม

โปรแกรมนี้เป็นตารางค้นหาแบบสดุดี หากผู้เล่นทั้งสองมีกระสุนระหว่าง 0 ถึง 4 มันจะใช้เมทริกซ์ความน่าจะเป็นในการพิจารณาว่าควรทำการเคลื่อนที่แบบใด มิฉะนั้นจะพยายามประมาณค่าตามตาราง

มันมีปัญหากับบ็อตที่กำหนดแบบงี่เง่า แต่ทำได้ดีกับบอทที่มีเหตุผล เนื่องจากการประมาณทั้งหมดสิ่งนี้จะสูญเสียไปยัง StudiousPlayer เป็นบางครั้งเมื่อไม่ควรทำ

แน่นอนถ้าฉันจะทำเช่นนี้อีกครั้งฉันอาจจะลองเพิ่มพารามิเตอร์ที่เป็นอิสระมากขึ้นหรืออาจจะเป็นรูปแบบ ansatz ที่ดีกว่าและหาวิธีแก้ปัญหาที่แม่นยำกว่านี้ นอกจากนี้ฉัน (จงใจ) เพิกเฉยต่อข้อ จำกัด เนื่องจากมันทำให้สิ่งต่าง ๆ ยากขึ้น อาจมีการดัดแปลงอย่างรวดเร็วเพื่อยิงพลาสมาถ้าเรามีกระสุนเพียงพอและเลี้ยวซ้ายไม่เพียงพอ

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

#ifndef __CBETA_PLAYER_HPP__
#define __CBETA_PLAYER_HPP__

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

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

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

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

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

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

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

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

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

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


};

#endif // !__CBETA_PLAYER_HPP__

เนื่องจากคุณไม่ผ่านพารามิเตอร์เข้าGetRandomDoubleคุณสามารถลบอาร์กิวเมนต์สูงสุด
Frenzy Li

@FrenzyLi ขออภัยขอบคุณ!
George V. Williams

คุณจะเพิ่มข้อมูลอีกเล็กน้อยบนเครื่องเล่นของคุณเช่นคุณมาถึงความน่าจะเป็น ... เมตริกซ์หรือไม่
Frenzy Li

2
ฉันรักบอทนี้ ฉันคิดว่า SP มีข้อได้เปรียบจนถึงขณะนี้เนื่องจากระดับของรายการอื่นเท่านั้น ยิ่งมีการเพิ่มบอตแบบสุ่มมากขึ้น นี่คือการสนับสนุนจากการทดสอบ ในการทดสอบภายในของฉันกับผู้ต้องสงสัยตามปกติ SP จะชนะด้วย CBP วินาทีเสมอ ... อย่างไรก็ตามในการแข่งขันขนาดเล็กที่เกี่ยวข้องกับ CBP, SP และ FP, CBP ลดเวลา 55% ไปข้างหน้าด้วย SP และ FP ที่มีความเท่าเทียมกัน
H Walters

1
อย่างไรก็ตามนี่คือการประมาณความสมดุลของแนชที่แม่นยำอย่างน่าประทับใจ มอนเตไม่ได้พยายามหากลยุทธ์ความสมดุล แต่เป็นการเคลื่อนไหวที่ดีที่สุดเมื่อเทียบกับคู่ต่อสู้ ความจริงมันชนะเพียง 52% ของการดวลระหว่างมันและcβหมายความว่าcβนั้นค่อนข้างใกล้เคียงกับสมดุลของแนช
TheNumberOne

8

ฉันไม่มีความคิดเห็นในทุกที่ดังนั้นฉันจึงยังไม่สามารถถามคำถามได้ ดังนั้นนี่คือผู้เล่นขั้นพื้นฐานที่จะเอาชนะบอทแรก

[แก้ไข] ขอบคุณตอนนี้สถานะก่อนหน้านี้ไม่เป็นความจริงอีกต่อไป แต่ฉันคิดว่ามันจะดีกว่าที่จะเก็บไว้เพื่อให้เราสามารถเข้าใจบริบทของบอทนี้

Opportunist

นักฉวยโอกาสมักใช้ปืนคลับเดียวกันกับ GunClubPlayers อย่างไรก็ตามเขาได้เดิมพันกับผู้เล่นใหม่ที่สามารถเอาชนะ GunClubPlayers ทุกคนได้ ดังนั้นเขาจึงใช้ประโยชน์จากนิสัยที่เขาสังเกตุมานานแล้วและบังคับให้ตัวเองไม่ต้องยิง แต่รอเพียงนิดเดียวที่จะชนะ

#ifndef __OPPORTUNIST_PLAYER_HPP__
#define __OPPORTUNIST_PLAYER_HPP__

#include <string>
#include <vector>

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

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

7

BarricadePlayer

ผู้เล่น Barricade โหลดกระสุนรอบแรกจากนั้นเก็บโล่ที่เหมาะสม (ยังคงสุ่ม) เขายังบรรจุกระสุนอีกนัดทุกรอบที่ 5 ทุกรอบมีโอกาส 15% ที่จะไม่สนใจ algoritm (ยกเว้นการรีเทิร์นเทิร์นแรก) และยิงกระสุน เมื่อศัตรูไม่มีกระสุนมันจะโหลด หากทุกอย่างผิดพลาดโอ้เด็กเขาก็ยิง

การเปลี่ยนแปลงใหม่ล่าสุด:

ปรับปรุงตัวเลขสุ่ม (ขอบคุณ 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
อย่างน้อยคุณต้องการตรวจสอบว่ามีกระสุนก่อนยิงหรือไม่?
พาเวล

8
ไม่ฉันใช้ชีวิตที่อันตราย @Pavel
devRicher

1
การใช้ชุดป้องกันความร้อนในเทิร์นที่สองนั้นไม่มีประโยชน์หรือไม่? คุณไม่สามารถโหลดกระสุนสองนัดในเทิร์นแรก ฉันคิดว่าถึงแม้ว่าคุณต้องการให้เป็นแบบสุ่มคุณควรหลีกเลี่ยงการใช้แผงป้องกันความร้อนหากกระสุนของฝ่ายตรงข้ามเป็น 1 (หรือน้อยกว่า)
Southpaw Hare

1
ขอบคุณสำหรับคำแนะนำทั้งหมดฉันแก้ไขชั้นเรียนได้มาก @ SouthpawHare
devRicher

2
มันไม่ได้getAmmoOpponent getOpponentAmmoคุณยังพลาด#endif // !__BARRICADE_PLAYER_HPP__
สีน้ำเงิน

7

StudiousPlayer

ผู้เล่นที่ศึกษาเหยื่อของมันการสร้างแบบจำลองของฝ่ายตรงข้ามที่พบ ผู้เล่นนี้เริ่มต้นด้วยกลยุทธ์ขั้นพื้นฐานการสุ่มในสถานที่และดำเนินการกับกลยุทธ์การปรับตัวที่ง่ายขึ้นอยู่กับมาตรการบ่อยของการตอบสนองของฝ่ายตรงข้าม มันใช้รูปแบบที่เรียบง่ายของฝ่ายตรงข้ามตามวิธีที่พวกเขาตอบสนองต่อการรวมกันของกระสุน

#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

โปรดทราบว่าสิ่งนี้จะติดตามข้อมูลเกี่ยวกับคู่ต่อสู้ตามกฎของการท้าทาย ดูวิธี "เมเยอร์สสไตล์ซิงเกิลตัน" scoped "ที่เก็บไว้ ()" ที่ด้านล่าง (บางคนสงสัยว่าจะทำอย่างไรตอนนี้คุณรู้แล้ว!)


1
ฉันไม่รู้ว่ามันถูกเรียกว่าสไตล์เมเยอร์สซิงเกิลจนกระทั่งฉันเห็นสิ่งนี้!
Frenzy Li

1
อย่าใช้คำอย่างจริงจังเกินไป - เป็นการใช้คำที่ไม่เหมาะสมเนื่องจาก "ซิงเกิล" เป็นอินสแตนซ์ของการสร้างอินสแตนซ์แทนที่จะเป็นโครงสร้างที่ประกาศ แต่เป็นเทคนิคเดียวกัน
H Walters

6

GunClubPlayer

ตัดออกจากคำถามเดิม สิ่งนี้ทำหน้าที่เป็นตัวอย่างของการใช้งานที่เรียบง่ายของผู้เล่นที่ได้รับมา ผู้เล่นนี้จะเข้าร่วมการแข่งขัน

คนGunClubPlayerที่ชอบไปชมรมปืน ในระหว่างการดวลแต่ละครั้งพวกเขาจะโหลดกระสุนจากนั้นจึงยิงกระสุนและทำซ้ำกระบวนการนี้จนกว่าจะสิ้นสุดการต่อสู้โลก พวกเขาไม่สนใจว่าพวกเขาจะชนะหรือไม่และมุ่งเน้นไปที่การมีประสบการณ์ที่สนุกสนานด้วยตนเองเท่านั้น

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

#ifndef __GUN_CLUB_PLAYER_HPP__
#define __GUN_CLUB_PLAYER_HPP__

#include "Player.hpp"

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

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

#endif // !__GUN_CLUB_PLAYER_HPP__

1
คุณไม่ต้องการสิ่งอื่นหลังจากคำสั่งส่งคืนใช่มั้ย ฉันรู้ว่ามันไม่ใช่รหัสกอล์ฟ แต่รู้สึกผิด
Pavel

2
@ โพลเวลโอเคดังนั้น ... มัน ... ประเภทของนักกอล์ฟตอนนี้
Frenzy Li

5

PlasmaPlayer

ผู้เล่นพลาสม่าชอบยิงสลักเกลียวพลาสมาของเขา เขาจะพยายามโหลดและยิงให้ได้มากที่สุด อย่างไรก็ตามในขณะที่ฝ่ายตรงข้ามมีกระสุนพลาสม่าเขาจะใช้แผ่นป้องกันความร้อน (กระสุนเป็นจุดอ่อน)

#ifndef __PLASMA_PLAYER_HPP__
#define __PLASMA_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

#endif // !__PLASMA_PLAYER_HPP__

@FrenzyLi ขอขอบคุณผู้สร้าง! C ++ ของฉันดูสนิมเล็กน้อยและฉันไม่มีคอมไพเลอร์ในเครื่องนี้
Brian J

ยินดีต้อนรับคุณ! ฉันยังคงเพิ่มโค้ดเพิ่มเติม (พิมพ์กระดานคะแนนอ่านสคริปต์ภายนอก ฯลฯ ) ไปยังโครงการและโชคดีมากที่ยังไม่มีการส่งข้อมูลใด ๆ
Frenzy Li

สิ่งนี้จะทำงานได้ดีสำหรับคู่ต่อสู้ใด ๆ นอกเหนือจาก GunClub ใช่มันจะฆ่า SadisticShooter (ดีที่สุด) @BrianJ
devRicher

5

เป็นอย่างมาก SadisticShooter

เขาอยากจะดูคุณทรมานมากกว่าที่จะฆ่าคุณ เขาไม่ได้โง่และจะปกปิดตัวเองตามที่ต้องการ

หากคุณน่าเบื่อและคาดเดาได้อย่างเต็มที่เขาจะฆ่าคุณทันที

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

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

#ifndef __SAD_SHOOTER_PLAYER_HPP__
#define __SAD_SHOOTER_PLAYER_HPP__

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

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

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

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

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

#endif // !__SAD_SHOOTER_PLAYER_HPP__

ฉันเห็นคุณซ่อมมัน
devRicher

4

TurtlePlayer

TurtlePlayerเป็นคนขี้ขลาด เขาใช้เวลาส่วนใหญ่ซ่อนอยู่หลังโล่ของเขา - ดังนั้นชื่อ บางครั้งเขาอาจออกมาจากเปลือกหอยของเขา (ไม่มีการเล่นสำนวนเจตนา) และมีการยิง แต่โดยปกติเขาอยู่ในระดับต่ำในขณะที่ศัตรูมีกระสุน


บอทนี้ไม่ยอดเยี่ยมเป็นพิเศษ - อย่างไรก็ตาม KOTH ทุกคนต้องการรายการเริ่มต้นเพื่อให้มันทำงาน :)

การทดสอบในท้องถิ่นพบว่าสิ่งนี้ชนะทั้งสองอย่างGunClubPlayerและOpportunist100% ของเวลา การต่อสู้กับBotRobotPlayerดูเหมือนจะส่งผลเสมอในขณะที่ทั้งคู่ซ่อนอยู่หลังโล่

#include "Player.hpp"

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

class TurtlePlayer final : public Player {

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

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

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

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

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

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

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

4

DeceptivePlayer

ผู้เล่นหลอกลวงพยายามโหลดกระสุนสองนัดแล้วยิงหนึ่งนัด

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

#ifndef __DECEPTIVE_PLAYER_HPP__
#define __DECEPTIVE_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

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

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

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

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

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

#endif // !__DECEPTIVE_PLAYER_HPP__

ฉันไม่ได้รหัสใน c ++ ดังนั้นการปรับปรุงใด ๆ ของรหัสจะได้รับการต้อนรับ


การแก้ไขของฉันอยู่ในข้อกำหนดของโมดูโลและมาโคร ไม่แน่ใจว่าคุณจะชอบ แต่อาจDeceptivePlayerเป็นชื่อที่ดีกว่า
Frenzy Li

@FrenzyLi ใช่ฉันชอบฉันจะเปลี่ยนชื่อ
Sxntk

1
@Sxntk ฉันชอบประชดที่ผู้เล่นนี้คาดหวังว่าคนที่มีกระสุน 2 นัดเพื่อยิงพลาสมา แต่ตัวเขาเองจะถือกระสุนสองนัดและยิงกระสุน
Brian J

@Sxntk คุณไม่มีความเป็นไปได้ที่จะไม่คืนสิ่งใดในปัจจุบัน ผู้เล่นได้รับอนุญาตมากกว่าสองกระสุน ดังนั้นถ้าฝ่ายตรงข้ามของคุณมีกระสุน 3+ ลูกคุณจะไม่ทำอะไรเลย คุณอาจจบลงด้วยปืนระเบิดที่ไหนสักแห่ง (แน่นอนว่าอาจเป็นแผนแม่บทของคุณอยู่ดี :))
Brian J

@BrianJ ขอบคุณฉันจะคิดเกี่ยวกับมันในขณะที่ฉันจะปล่อยให้หลอกลวงไปบ้าและตัดสินใจว่าจะทำอย่างไรเมื่อ oponnent มี 3+ กระสุน
Sxntk

2

HanSoloPlayer

ยิงก่อน! ยังคงทำงานเพื่อแก้ไขมัน แต่มันค่อนข้างดี

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

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

#ifndef __HAN_SOLO_PLAYER_HPP__
#define __HAN_SOLO_PLAYER_HPP__

#include "Player.hpp"

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

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

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

#endif // !__HAN_SOLO_PLAYER_HPP__

2

CamtoPlayer

CamtoPlayer HATESดึงออกมาและจะแตกออกเป็นลูปไม่ว่าจะทำอะไรก็ตาม (ยกเว้นการฆ่าตัวตาย)

เป็นโปรแกรม C ++ แรกที่ทำทุกอย่างดังนั้นอย่าตัดสินยากเกินไป

ฉันรู้ว่ามันอาจจะดีกว่า แต่โปรดอย่าแก้ไขมัน
หากคุณต้องการแก้ไขรหัสเพียงแค่แสดงความคิดเห็นข้อเสนอแนะ

#ifndef __CAMTO_HPP__
#define __CAMTO_HPP__

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

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

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

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

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

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

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

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

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

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

    }
};

#endif // !__CAMTO_HPP__

คุณลืม#endif // ! __CAMTO_HPP__
สีฟ้า

@muddyfish ขอบคุณที่บอกฉันฉันมีสัญลักษณ์น้อยกว่าที่หยุดโค้ดไม่ให้เรนเดอร์! XD
Benjamin Philippe

ยังไม่ปรากฏขึ้น ฉันจะแนะนำให้ทิ้งแท็ก HTML ทั้งหมดและเพียงแค่ใช้ markdown (ปุ่ม "ตัวอย่างโค้ด" ที่มี "{}" อยู่) การพูดด้วยตนเอง<>&เป็นความเจ็บปวด
H Walters

@ HWWalters ขอบคุณสำหรับเคล็ดลับ!
Benjamin Philippe

ขอขอบคุณสำหรับการมีส่วนร่วม. และสิ่งหนึ่ง: โปรดลบusing namespace stdเพราะมันรบกวนการแข่งขัน หากคุณต้องการแก้ปัญหาคุณสามารถใช้std::coutฯลฯ
Frenzy Li

1

SurvivorPlayer

ผู้เล่นที่รอดชีวิตจะมีพฤติกรรมคล้าย ๆ กับ Turtle และ Barricade Player เขาจะไม่ดำเนินการใด ๆ ที่อาจนำไปสู่ความตายของเขาได้

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

#ifndef __SURVIVOR_PLAYER_HPP__
#define __SURVIVOR_PLAYER_HPP__

#include "Player.hpp"

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

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

#endif // !__SURVIVOR_PLAYER_HPP__

1

FatedPlayer

ทำโดย Clotho คะแนนโดย Lachesis และถูกฆ่าโดย Atropos ; กลยุทธ์เดียวของผู้เล่นนี้คือใช้สิ่งที่รู้เกี่ยวกับกระสุนเพื่อตัดสินว่าการกระทำใดมีเหตุผล

อย่างไรก็ตามมันไม่ได้เลือกการกระทำ ส่วนนั้นก็ถูกทิ้งให้อยู่กับพระเจ้า

#ifndef __FATEDPLAYER_H__
#define __FATEDPLAYER_H__

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

#endif

... เพราะฉันต้องการดูว่าผู้เล่นสุ่มจัดอันดับอย่างไร


1

SpecificPlayer

SpecificPlayerติดตามแผนการง่าย ๆ ในการเลือกการกระทำแบบสุ่ม (ที่ถูกต้อง) อย่างไรก็ตามมันเป็นคุณสมบัติหลักคือมันมองออกไปในบางสถานการณ์โดยการวิเคราะห์จำนวนกระสุนและการเคลื่อนไหวก่อนหน้าของคู่ต่อสู้

นี่เป็นครั้งแรกที่ฉันเขียนอะไรใน C ++ และครั้งแรกที่พยายามเขียน bot ที่แข่งขันได้ ดังนั้นฉันหวังว่าอย่างน้อยความพยายามของฉันจะทำสิ่งที่น่าสนใจ :)

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

#ifndef __SPECIFIC_PLAYER_HPP__
#define __SPECIFIC_PLAYER_HPP__

#include "Player.hpp"

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

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

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

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

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

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

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

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

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

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

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

        return returnval;
    }

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

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

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

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

#endif // !__SPECIFIC_PLAYER_HPP__

1

NotSoPatientPlayer

เรื่องราวของการสร้างมันจะมาในภายหลัง

// NotSoPatientPlayer.hpp

#ifndef __NOT_SO_PATIENT_PLAYER_HPP__
#define __NOT_SO_PATIENT_PLAYER_HPP__

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

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

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

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

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

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

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

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

"เรื่องราวของการสร้างมันจะมาในภายหลัง" มันผ่านไปนานกว่า 3 เดือน :)
HyperNeutrino
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.