Scrappers v0.1: โปรแกรมเมอร์ทหารรับจ้าง


22

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

ตัวอย่างเกม Scrappers

(ใช่โลโก้ถูกครอบตัดอย่างสนุกสนาน)

ยินดีต้อนรับสู่ Scrappers!

นี่เป็นรุ่นแรก ๆ ของ Scrappers ซึ่งยังไม่มีการรวบรวมเศษซากและโรงงาน มันเป็น "shoot 'em up"

คุณเป็นโปรแกรมเมอร์ทหารรับจ้างที่ได้รับมอบหมายให้สร้างโปรแกรมเพื่อจัดการบอทของคุณจากระยะไกลเพื่อชัยชนะเหนือกลุ่มสแครปเปอร์คู่ต่อสู้ บอทของคุณเป็นเครื่องจักรที่เหมือนแมงมุมซึ่งประกอบด้วยเครื่องกำเนิดไฟฟ้าและเครื่องกำเนิดไฟฟ้าที่แกนกลางของพวกเขาล้อมรอบไปด้วยอวัยวะต่าง ๆ มากมายที่มีการจับการตัดและการโจมตีอุปกรณ์ เครื่องกำเนิดไฟฟ้าสามารถผลิตพลังงาน 12 หน่วย (pu) ต่อหนึ่งขีด (หน่วยเวลาของ scrapper) คุณเป็นผู้ควบคุมวิธีการกระจายพลังนี้ท่ามกลางความต้องการหลักสามประการของบ็อต: การเคลื่อนไหว, เกราะ, และพลังยิง

บอท Scrapper เป็นเครื่องจักรที่คล่องตัวเป็นพิเศษและสามารถเคลื่อนที่ข้ามใต้และรอบ ๆ สิ่งกีดขวางที่พบได้อย่างง่ายดาย ดังนั้นการชนไม่ใช่สิ่งที่โปรแกรมของคุณต้องคำนึงถึง คุณมีอิสระที่จะจัดสรรทั้งหมดบางส่วนหรือไม่มี 12pu ที่พร้อมใช้งานสำหรับบอตของคุณสำหรับการเคลื่อนไหวตราบใดที่คุณจัดการเป็นจำนวนเต็ม การจัดสรร 0pu ให้กับฟังก์ชั่นการเคลื่อนไหวของบ็อตจะทำให้ไม่สามารถเคลื่อนที่ได้ การจัดสรร 2pu จะทำให้บอทสามารถย้าย 2 หน่วยระยะทาง (du) ต่อหนึ่งขีด 5pu จะส่งผลให้ 5du / tick, 11pu จะส่งผลให้ 11du / tick และอื่น ๆ

เครื่องกำเนิดไฟฟ้าโล่บอทของคุณฉายฟองพลังงานการโกลาหลรอบ ๆ ร่างกาย เกราะสามารถเบี่ยงเบนความเสียหายได้มากถึง 1 ดาเมจก่อนที่จะโผล่ออกมาดังนั้นจึงปล่อยบอทของคุณออกมาจนกว่ามันจะสร้างเครื่องกำเนิดพลังงานสร้างพลังงานเพียงพอที่จะสแนปโล่กลับเข้าที่ คุณมีอิสระในการจัดสรรทั้งหมด, บางส่วน, หรือไม่มี 12pu ที่พร้อมใช้งานสำหรับ bot ของคุณที่มีเกราะป้องกัน การจัดสรร 0pu ให้กับโล่ของบอทหมายความว่ามันจะไม่สร้างเกราะป้องกันขึ้นมา การจัดสรร 2pu จะอนุญาตให้บอทสร้างโล่ใหม่ 2 จาก 12 เห็บหรือทุกๆ 6 เห็บ 5pu จะส่งผลให้เกิด regeneration 5 จากทุก ๆ 12 ติ๊กเป็นต้น

ด้วยการสร้างประจุในเลเซอร์เชื่อมของพวกเขาบอทของคุณสามารถยิงลำแสงที่สร้างความเสียหายในระยะทางสั้น ๆ ได้อย่างแม่นยำ เช่นเดียวกับการสร้างโล่อัตราการยิงของบ็อตของคุณขึ้นอยู่กับพลังที่จัดสรรให้กับเลเซอร์ของพวกเขา การจัดสรร 0pu ให้กับเลเซอร์ของบอทหมายความว่ามันจะไม่ยิง การจัดสรร 2pu จะอนุญาตให้บอทยิง 2 จากทุก ๆ 12 ติ๊กเป็นต้น เลเซอร์ของบ็อตจะเคลื่อนที่จนกว่าจะพบวัตถุหรือกระจายไปสู่ความไร้ประโยชน์ดังนั้นจงระวังไฟที่เป็นมิตร แม้ว่าบอทของคุณจะค่อนข้างแม่นยำ แต่มันก็ไม่สมบูรณ์แบบ คุณควรคาดหวังความแปรปรวน +/- 2.5 องศาอย่างแม่นยำ เมื่อลำแสงเลเซอร์เคลื่อนที่ไปอนุภาคของมันจะถูกเบี่ยงเบนไปตามชั้นบรรยากาศจนกระทั่งลำแสงนั้นไม่เป็นอันตรายอย่างมีประสิทธิภาพในระยะทางที่เพียงพอ เลเซอร์จะสร้างความเสียหายได้ 1 จุดที่ระยะเผาขนและ 2.5% จะสร้างความเสียหายได้น้อยลงทุกความยาวบอทที่เคลื่อนที่

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

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

รายละเอียดเกมทางเทคนิค

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

เกมเอ็นจิ้น

คุณสามารถดาวน์โหลดเกมเครื่องยนต์จากที่นี่ เมื่อเปิดตัวเกมมันจะเริ่มฟังพอร์ต 50000 (ปัจจุบันไม่สามารถกำหนดค่าได้) สำหรับการเชื่อมต่อของผู้เล่น เมื่อได้รับการเชื่อมต่อผู้เล่นสองคนมันจะส่งข้อความ READY ไปยังผู้เล่นและเริ่มเกม โปรแกรมเล่นส่งคำสั่งไปยังเกมผ่านทาง TCP API เมื่อเกมสิ้นสุดลงไฟล์ JSON ชื่อ scrappers.json (เช่นนั้นยังไม่สามารถกำหนดค่าได้ในปัจจุบัน) จะถูกสร้างขึ้น นี่คือสิ่งที่ renderer ใช้สร้าง GIF ของเกม

TCP API

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

ข้อความพร้อม

ข้อความ READY จะถูกส่งจากเกมไปยังโปรแกรมเล่นและส่งเพียงครั้งเดียว ข้อความนี้บอกโปรแกรมผู้เล่นว่า ID ผู้เล่น (PID) คืออะไรและแสดงรายการบอตทั้งหมดในเกม PID เป็นวิธีเดียวที่จะตัดสินว่าบอทใดที่เป็นมิตรกับศัตรู ข้อมูลเพิ่มเติมเกี่ยวกับฟิลด์ bot ด้านล่าง

{
    "Type":"READY",  // Message type
    "PID":1,         // Player ID
    "Bots":[         // Array of bots
        {
            "Type":"BOT",
            "PID":1,
            "BID":1,
            "X":-592,
            ...
        },
        ...
    ]
}

ข้อความ ธ ปท

ข้อความ BOT จะถูกส่งจากเกมไปยังโปรแกรมผู้เล่นและจะถูกส่งเมื่อคุณลักษณะของบอทเปลี่ยนไป ตัวอย่างเช่นเมื่อมีการคาดการณ์โล่หรือการเปลี่ยนแปลงสุขภาพข้อความ BOT จะถูกส่ง Bot ID (BID) จะไม่ซ้ำกันภายในผู้เล่นที่เฉพาะเจาะจง

{
    "Type":"BOT",   // Message type
    "PID":1,        // Player ID
    "BID":1,        // Bot ID
    "X":-592,       // Current X position
    "Y":-706,       // Current Y position
    "Health":12,    // Current health out of 12
    "Fired":false,  // If the Bot fired this tick
    "HitX":0,       // X position of where the shot landed
    "HitY":0,       // Y position of where the shot landed
    "Scrap":0,      // Future use. Ignore.
    "Shield":false  // If the Bot is currently shielded.
}

ย้ายข้อความ

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

{
    "Cmd":"MOVE",
    "BID":1,        // Bot ID
    "X":-592,       // Destination X coordinate
    "Y":-706,       // Destination Y coordinate
}

ข้อความเป้าหมาย

ข้อความเป้าหมายบอกให้บ็อตหนึ่งของคุณกำหนดเป้าหมายบ็อตอื่น

{
    "Cmd":"TARGET",
    "BID":1,        // Bot ID
    "TPID":0,       // The PID of the bot being targeted
    "TBID":0,       // The BID of the bot being targeted
}

ข้อความพลังงาน

ข้อความ POWER จัดสรร 12pu ที่มีให้บอทของคุณระหว่างการเคลื่อนไหวอาวุธและเกราะป้องกัน

{
    "Cmd":"POWER",
    "BID":1,        // Bot ID
    "FPow":4,       // Set fire power
    "MPow":4,       // Set move power
    "SPow":4,       // Set shield power
}

การแข่งขัน

หากคุณกล้าพอที่จะสำรวจดินแดนเปลี่ยวคุณจะเข้าร่วมทัวร์นาเมนต์กำจัดคู่ต่อสู้กับทหารรับจ้างของคุณ โปรดสร้างคำตอบสำหรับการส่งของคุณและวางรหัสของคุณหรือให้ลิงก์ไปยัง repo git, gist และอื่น ๆ ภาษาใดก็ได้ แต่คุณควรถือว่าฉันรู้อะไรเกี่ยวกับภาษาและมีคำแนะนำสำหรับการใช้โปรแกรมของคุณ สร้างผลงานส่งให้ได้มากเท่าที่คุณต้องการและอย่าลืมตั้งชื่อ!

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

--- Winner's Bracket ---

** Contestants will be randomly seeded **
__________________
                  |___________
__________________|           |
                              |___________
__________________            |           |
                  |___________|           |
__________________|                       |
                                          |________________
__________________                        |                |
                  |___________            |                |
__________________|           |           |                |
                              |___________|                |
__________________            |                            |
                  |___________|                            |
__________________|                                        |
                                                           |
--- Loser's Bracket ---                                    |___________
                                                           |
___________                                                |
           |___________                                    |
___________|           |___________                        |
                       |           |                       |
            ___________|           |                       |
                                   |___________            |
___________                        |           |           |
           |___________            |           |___________|
___________|           |___________|           |
                       |                       |
            ___________|            ___________|

ข้อมูลสำคัญอื่น ๆ

  • เกมทำงานที่ 12 เห็บ / วินาทีดังนั้นคุณจะไม่ได้รับข้อความบ่อยกว่าทุก ๆ 83 มิลลิวินาทีหรือมากกว่านั้น
  • บอทแต่ละอันมีเส้นผ่านศูนย์กลาง 60 องศา โล่ไม่เพิ่มพื้นที่ว่าง ด้วยความแม่นยำ +/- 2.5% อัตราต่อรองของการกดปุ่มบอทที่ระยะทางที่กำหนดจะถูกแสดงด้วยกราฟนี้:

กราฟความแม่นยำ

  • กราฟของการชำรุดของเลเซอร์ในระยะไกลแสดงโดย:

กราฟแสดงความเสียหาย

  • ความแม่นยำของบอทและการสลายตัวของเลเซอร์รวมกันเพื่อคำนวณความเสียหายเฉลี่ยต่อการยิง นั่นคือค่าเฉลี่ยดาเมจของบ็อตจะเกิดขึ้นเมื่อมันยิงจากระยะไกล กราฟนี้จะแสดงความเสียหายต่อนัด:

ความเสียหายต่อกราฟการยิง

  • เลเซอร์ของบ็อตมาครึ่งทางระหว่างกึ่งกลางของบอทกับขอบ ดังนั้นการวางบอทของคุณจะส่งผลให้เกิดกองไฟที่เป็นมิตร
  • บอทศัตรูวางไข่ห่างกันประมาณ 1,404 อัน
  • เกมจะจบลงหากมี 120 เห็บ (10 วินาที) ผ่านไปโดยไม่มีความเสียหายใด ๆ เกิดขึ้น
  • ผู้ชนะคือผู้เล่นที่มีบอทมากที่สุดจากนั้นจะมีสุขภาพที่ดีที่สุดเมื่อจบเกม

ทำความเข้าใจกับภาพที่แสดงผล

  • ผู้เล่น 1 แสดงโดยวงกลมและผู้เล่น 2 เป็นรูปหกเหลี่ยม
  • สีของบ็อตแสดงถึงการจัดสรรพลังงาน สีแดงมากขึ้นหมายถึงพลังงานมากขึ้นได้รับการจัดสรรให้กับการยิง สีน้ำเงินมากกว่าหมายถึงโล่เพิ่มเติม สีเขียวมากขึ้นหมายถึงการเคลื่อนไหวมากขึ้น
  • "รู" ในร่างกายของบอทแสดงถึงความเสียหาย ยิ่งหลุมยิ่งมีความเสียหายมากขึ้น
  • วงกลมสีขาวรอบ ๆ บ็อตเป็นเกราะกำบัง ถ้าบอทมีเกราะป้องกันในตอนท้ายของเทิร์นมันจะปรากฏขึ้น หากโล่ถูกเปิดโดยได้รับความเสียหายจะไม่ปรากฏขึ้น
  • เส้นสีแดงระหว่างบอตเป็นตัวแทนของภาพที่ถ่าย
  • เมื่อบอทถูกฆ่าจะมีการ "ระเบิด" สีแดงขนาดใหญ่ขึ้น

ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Dennis

คำตอบ:


4

พวกหัวรุนแรง (Python 3)

บอทนี้จะอุทิศพลังทั้งหมดของมันไปยังสิ่งหนึ่งเสมอ: ป้องกันถ้ามันไม่ได้ถูกป้องกันย้ายถ้ามันอยู่นอกตำแหน่งและยิงเป็นอย่างอื่น เอาชนะบอทตัวอย่างทั้งหมดยกเว้นเด ธ - จาน

import socket, sys, json
from types import SimpleNamespace
s=socket.socket()
s.connect(("localhost",50000))
f=s.makefile()
bots={1:{},2:{}}
def hook(obj):
    if "BID" in obj:
        try:
            bot = bots[obj["PID"]][obj["BID"]]
        except KeyError:
            bot = SimpleNamespace(**obj)
            bots[bot.PID][bot.BID] = bot
        else:
            bot.__dict__.update(obj)
        return bot
    return SimpleNamespace(**obj)
decoder = json.JSONDecoder(object_hook=hook)
PID = decoder.decode(f.readline()).PID
#side effect: .decode fills bots dictionary
def turtle(bot):
    send({"Cmd":"POWER","BID":bot.BID,"FPow":0,"MPow":0,"SPow":12})
    bot.firing = bot.moving = False
def send(msg):
    s.send(json.dumps(msg).encode("ascii")+b"\n")
for bot in bots[PID].values():
    turtle(bot)
target_bot = None
def calc_target_bot():
    ok_bots = []
    for bot2 in bots[(2-PID)+1].values():
        if bot2.Health < 12:
            ok_bots.append(bot2)
    best_bot = (None,2147483647)
    for bot2 in (ok_bots or bots[(2-PID)+1].values()):
        dist = bot_dist(bot, bot2)
        if dist < best_bot[1]:
            best_bot = bot2, dist
    return best_bot[0]
def bot_dist(bot, bot2):
    if isinstance(bot, tuple):
        bot=SimpleNamespace(X=bot[0],Y=bot[1])
    if isinstance(bot2, tuple):
        bot=SimpleNamespace(X=bot[0],Y=bot[1])
    distx = bot2.X - bot.X
    disty = bot2.Y - bot.Y
    return (distx**2+disty**2)**.5
LENGTH_Y = -80
LENGTH_X = 80
line = None
def move(bot, pos):
    bot.firing = False
    bot.moving = True
    send({"Cmd":"POWER","BID":bot.BID,"FPow":0,"MPow":12,"SPow":0})
    send({"Cmd":"MOVE","BID": bot.BID,"X":pos[0],"Y":pos[1]})
def go(bot, line):
    if line != None:
        position = (line[0]+LENGTH_X*(bot.BID-6),line[1]+LENGTH_Y*(bot.BID-6))
        if not close_pos(bot, position, 1.5):
            return True, position
    return False, None
def close_pos(bot, pos, f):
    if abs(bot.X - pos[0]) <= abs(LENGTH_X*f) or \
        abs(bot.Y - pos[1]) <= abs(LENGTH_Y*f):
        return True
def set_line(bot):
    global line
    newline = bot.X - LENGTH_X*(bot.BID - 6), bot.Y - LENGTH_Y*(bot.BID - 6)
    if line == None or bot_dist(line, target_bot) < (bot_dist(newline, target_bot) - 100):
        line = newline
def move_or_fire(bot):
    global target_bot, line
    if not target_bot:
        target_bot = calc_target_bot()
    followline, place = go(bot, line)
    if not target_bot:
        #Game should be over, but just in case ...
        return turtle(bot)
    elif bot_dist(bot, target_bot) > 2000:
        line = None
        position = (target_bot.X, target_bot.Y)
        position = (position[0]+LENGTH_X*(bot.BID-6),position[1]+LENGTH_Y*(bot.BID-6))
        move(bot, position)
    elif followline:
        set_line(bot)
        move(bot, place)
    elif any(close_pos(bot, (bot2.X, bot2.Y), .6) for bot2 in bots[PID].values() if bot != bot2):
        try:
            move(bot, place)
        except TypeError:
            turtle(bot)
        set_line(bot)
        #Let the conflicting bots resolve
    else:
        set_line(bot)
        bot.firing = True
        bot.moving = False
        send({"Cmd":"POWER","BID":bot.BID,"FPow":12,"MPow":0,"SPow":0})
        send({"Cmd":"TARGET","BID": bot.BID,
              "TPID":target_bot.PID,"TBID":target_bot.BID})
def dead(bot):
    del bots[bot.PID][bot.BID]
def parse_message():
    global target_bot
    line = f.readline()
    if not line:
        return False
    bot = decoder.decode(line)
    assert bot.Type == "BOT"
    del bot.Type
    if bot.PID == PID:
        if bot.Health <= 0:
            dead(bot)
        elif not bot.Shield:
            turtle(bot)
        else:
            move_or_fire(bot)
    elif target_bot and (bot.BID == target_bot.BID):
        target_bot = bot
        if target_bot.Health <= 0:
            target_bot = None
            dead(bot)
            for bot in bots[PID].values():
                if bot.firing or bot.moving:
                    move_or_fire(bot)
    elif bot.Health <= 0:
        dead(bot)
    assert bot.Health > 0 or bot.BID not in bots[bot.PID]
    return True
while parse_message():
    pass

ฉันไม่คุ้นเคยกับงูหลาม แต่ดูเหมือนว่าจะมีปัญหาหลายอย่างกับการส่งของคุณ: 1) บรรทัด 212 120 ไม่ได้ถูกเยื้องอย่างถูกต้องและ 2) target_hp ไม่ได้กำหนดไว้ ฉันสามารถแก้ไขได้ (1) แต่ (2) กำลังห้ามไม่ให้ส่งข้อเสนอของคุณ แต่อาจเป็นการขาดประสบการณ์ของฉันกับงูหลาม
Moogie

ทั้งหมดนั้นถ้าคำสั่งเหลือจากการดีบักและไม่จำเป็นเลย
pppery

2

ประมาทน้อยกว่า ( ไป )

go run main.go

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

รหัสไม่สมบูรณ์ ตรรกะในการพิจารณาว่าช็อตนั้นชัดเจนใช้การทายผลแบบสุ่มสวย ๆ บ้างไหม

ดูเหมือนจะไม่มีกลไกในการกำหนดเป้าหมาย "ไม่มีใคร" นั่นอาจเป็นคุณสมบัติที่ดีในการเพิ่ม

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

(ส่วนใหญ่ของรหัสต่อไปนี้เป็นการคัดลอก / วางจากหนึ่งในบอทตัวอย่าง)

package main

import (
    "bufio"
    "encoding/json"
    "flag"
    "io"
    "log"
    "math"
    "math/rand"
    "net"
    "time"
)

const (
    MaxHealth int = 12
    BotSize float64 = 60
)

var (
    // TCP connection to game.
    gameConn net.Conn
    // Queue of incoming messages
    msgQueue chan MsgQueueItem
)

// MsgQueueItem is a simple vehicle for TCP
// data on the incoming message queue.
type MsgQueueItem struct {
    Msg string
    Err error
}

// Command contains all the fields that a player might
// pass as part of a command. Fill in the fields that
// matter, then marshal into JSON and send.
type Command struct {
    Cmd  string
    BID  int
    X    int
    Y    int
    TPID int
    TBID int
    FPow int
    MPow int
    SPow int
}

// Msg is used to unmarshal every message in order
// to check what type of message it is.
type Msg struct {
    Type string
}

// BotMsg is used to unmarshal a BOT representation
// sent from the game.
type BotMsg struct {
    PID, BID   int
    X, Y       int
    Health     int
    Fired      bool
    HitX, HitY int
    Scrap      int
    Shield     bool
}

// ReadyMsg is used to unmarshal the READY
// message sent from the game.
type ReadyMsg struct {
    PID  int
    Bots []BotMsg
}

// Create our game data storage location
var gdb GameDatabase

func main() {

    var err error
    gdb = GameDatabase{}
    msgQueue = make(chan MsgQueueItem, 1200)

    // What port should we connect to?
    var port string
    flag.StringVar(&port, "port", "50000", "Port that Scrappers game is listening on.")
    flag.Parse()

    // Connect to the game
    gameConn, err = net.Dial("tcp", ":"+port)
    if err != nil {
        log.Fatalf("Failed to connect to game: %v\n", err)
    }
    defer gameConn.Close()

    // Process messages off the incoming message queue
    go processMsgs()

    // Listen for message from the game, exit if connection
    // closes, add message to message queue.
    reader := bufio.NewReader(gameConn)
    for {
        msg, err := reader.ReadString('\n')
        if err == io.EOF {
            log.Println("Game over (connection closed).")
            return
        }
        msgQueue <- MsgQueueItem{msg, err}
    }
}

func runStrategy() {

    // LESS RECKLESS ABANDON
    // - For three seconds, all bots move as fast as possible in a random direction.
    // - After three seconds, split power between speed and firepower.
    // - Loop...
    //     - Identify the enemy bot with the lowest health.
    //     - If a friendly bot is in the way, pick someone else.
    //     - If there's a tie, pick the one closest to the group.
    //     - Everybody moves towards and targets the bot.

    var myBots []*GDBBot

    // Move quickly in random direction.
    // Also, might as well get a shield.
    myBots = gdb.MyBots()
    for _, bot := range myBots {
        send(bot.Power(0, 11, 1))
        radians := 2.0 * math.Pi * rand.Float64()
        x := bot.X + int(math.Cos(radians)*999)
        y := bot.Y + int(math.Sin(radians)*999)
        send(bot.Move(x, y))
    }

    // Wait three seconds
    time.Sleep(3 * time.Second)

    // Split power between speed and fire
    for _, bot := range myBots {
        send(bot.Power(6, 6, 0))
    }

    for { // Loop indefinitely

        // Find a target

        candidates := gdb.TheirBots()

        // Order by health
        reordered := true
        for reordered {
            reordered = false
            for n:=1; n<len(candidates); n++ {
                if candidates[n].Health < candidates[n-1].Health {
                    temp := candidates[n-1]
                    candidates[n-1] = candidates[n]
                    candidates[n] = temp
                    reordered = true
                }
            }
        }

        // Order by closeness

        // My swarm position is...
        ttlX, ttlY := 0, 0
        myBots = gdb.MyBots() // Refresh friendly bot list
        for _, bot := range myBots {
            ttlX += bot.X
            ttlY += bot.Y
        }
        avgX := ttlX / len(myBots)
        avgY := ttlY / len(myBots)

        // Sort
        reordered = true
        for reordered {
            reordered = false
            for n:=1; n<len(candidates); n++ {
                thisDist := distance(avgX, avgY, candidates[n].X, candidates[n].Y)
                lastDist := distance(avgX, avgY, candidates[n-1].X, candidates[n-1].Y)
                if thisDist < lastDist {
                    temp := candidates[n-1]
                    candidates[n-1] = candidates[n]
                    candidates[n] = temp
                    reordered = true
                }
            }
        }

        // For all my bots, try to find the weakest enemy that my bot has a clear shot at
        myBots = gdb.MyBots()
        for _, bot := range myBots {
            for _, enemy := range candidates {

                clear := clearShot(bot, enemy)
                if clear {

                    // Target and move towards
                    send(bot.Target(enemy))
                    send(bot.Follow(enemy))
                    break
                }
                log.Println("NO CLEAR SHOT")
            }
        }

        time.Sleep(time.Second / 24)
    }
}

func clearShot(bot, enemy *GDBBot) bool {

    deg45rad := math.Pi*45/180
    deg30rad := math.Pi*30/180
    deg15rad := math.Pi*15/180
    deg5rad := math.Pi*5/180

    myBots := gdb.MyBots()
    enmyAngle := math.Atan2(float64(enemy.Y-bot.Y), float64(enemy.X-bot.X))

    for _, friend := range myBots {

        dist := distance(bot.X, bot.Y, friend.X, friend.Y)
        angle := math.Atan2(float64(friend.Y-bot.Y), float64(friend.X-bot.X))
        safeAngle := angle

        if dist < BotSize*3 {
            safeAngle = deg45rad/2
        } else if dist < BotSize*6 {
            safeAngle = deg30rad/2
        } else if dist < BotSize*9 {
            safeAngle = deg15rad/2
        } else {
            safeAngle = deg5rad/2
        }

        if angle <= enmyAngle+safeAngle &&  angle >= enmyAngle-safeAngle {
            return false
        }
    }

    return true
}

func processMsgs() {

    for {
        queueItem := <-msgQueue
        jsonmsg := queueItem.Msg
        err := queueItem.Err

        if err != nil {
            log.Printf("Unknown error reading from connection: %v", err)
            continue
        }

        // Determine the type of message first
        var msg Msg
        err = json.Unmarshal([]byte(jsonmsg), &msg)
        if err != nil {
            log.Printf("Failed to marshal json message %v: %v\n", jsonmsg, err)
            return
        }

        // Handle the message type

        // The READY message should be the first we get. We
        // process all the data, then kick off our strategy.
        if msg.Type == "READY" {

            // Unmarshal the data
            var ready ReadyMsg
            err = json.Unmarshal([]byte(jsonmsg), &ready)
            if err != nil {
                log.Printf("Failed to marshal json message %v: %v\n", jsonmsg, err)
            }

            // Save our player ID
            gdb.PID = ready.PID
            log.Printf("My player ID is %v.\n", gdb.PID)

            // Save the bots
            for _, bot := range ready.Bots {
                gdb.InsertUpdateBot(bot)
            }

            // Kick off our strategy
            go runStrategy()

            continue
        }

        // The BOT message is sent when something about a bot changes.
        if msg.Type == "BOT" {

            // Unmarshal the data
            var bot BotMsg
            err = json.Unmarshal([]byte(jsonmsg), &bot)
            if err != nil {
                log.Printf("Failed to marshal json message %v: %v\n", jsonmsg, err)
            }

            // Update or add the bot
            gdb.InsertUpdateBot(bot)

            continue
        }

        // If we've gotten to this point, then we
        // were sent a message we don't understand.
        log.Printf("Recieved unknown message type \"%v\".", msg.Type)
    }
}

///////////////////
// GAME DATABASE //
///////////////////

// GameDatabase stores all the data
// sent to us by the game.
type GameDatabase struct {
    Bots []GDBBot
    PID  int
}

// GDBBot is the Bot struct for the Game Database.
type GDBBot struct {
    BID, PID int
    X, Y     int
    Health   int
}

// InserUpdateBot either updates a bot's info,
// deletes a dead bot, or adds a new bot.
func (gdb *GameDatabase) InsertUpdateBot(b BotMsg) {

    // If this is a dead bot, remove and ignore
    if b.Health <= 0 {

        for i := 0; i < len(gdb.Bots); i++ {
            if gdb.Bots[i].BID == b.BID && gdb.Bots[i].PID == b.PID {
                gdb.Bots = append(gdb.Bots[:i], gdb.Bots[i+1:]...)
                return
            }
        }
        return
    }

    // Otherwise, update...
    for i, bot := range gdb.Bots {
        if b.BID == bot.BID && b.PID == bot.PID {
            gdb.Bots[i].X = b.X
            gdb.Bots[i].Y = b.Y
            gdb.Bots[i].Health = b.Health
            return
        }
    }

    // ... or Add
    bot := GDBBot{}
    bot.PID = b.PID
    bot.BID = b.BID
    bot.X = b.X
    bot.Y = b.Y
    bot.Health = b.Health
    gdb.Bots = append(gdb.Bots, bot)
}

// MyBots returns a pointer array of GDBBots owned by us.
func (gdb *GameDatabase) MyBots() []*GDBBot {
    bots := make([]*GDBBot, 0)
    for i, bot := range gdb.Bots {
        if bot.PID == gdb.PID {
            bots = append(bots, &gdb.Bots[i])
        }
    }
    return bots
}

// TheirBots returns a pointer array of GDBBots NOT owned by us.
func (gdb *GameDatabase) TheirBots() []*GDBBot {
    bots := make([]*GDBBot, 0)
    for i, bot := range gdb.Bots {
        if bot.PID != gdb.PID {
            bots = append(bots, &gdb.Bots[i])
        }
    }
    return bots
}

// Move returns a command struct for movement.
func (b *GDBBot) Move(x, y int) Command {
    cmd := Command{}
    cmd.Cmd = "MOVE"
    cmd.BID = b.BID
    cmd.X = x
    cmd.Y = y
    return cmd
}

// Follow is a convenience function which returns a
// command stuct for movement using a bot as a destination.
func (b *GDBBot) Follow(bot *GDBBot) Command {
    cmd := Command{}
    cmd.Cmd = "MOVE"
    cmd.BID = b.BID
    cmd.X = bot.X
    cmd.Y = bot.Y
    return cmd
}

// Target returns a command struct for targeting a bot.
func (b *GDBBot) Target(bot *GDBBot) Command {
    cmd := Command{}
    cmd.Cmd = "TARGET"
    cmd.BID = b.BID
    cmd.TPID = bot.PID
    cmd.TBID = bot.BID
    return cmd
}

// Power returns a command struct for seting the power of a bot.
func (b *GDBBot) Power(fire, move, shield int) Command {
    cmd := Command{}
    cmd.Cmd = "POWER"
    cmd.BID = b.BID
    cmd.FPow = fire
    cmd.MPow = move
    cmd.SPow = shield
    return cmd
}

////////////////////
// MISC FUNCTIONS //
////////////////////

// Send marshals a command to JSON and sends to the game.
func send(cmd Command) {
    bytes, err := json.Marshal(cmd)
    if err != nil {
        log.Fatalf("Failed to mashal command into JSON: %v\n", err)
    }
    bytes = append(bytes, []byte("\n")...)
    gameConn.Write(bytes)
}

// Distance calculates the distance between two points.
func distance(xa, ya, xb, yb int) float64 {
    xdist := float64(xb - xa)
    ydist := float64(yb - ya)
    return math.Sqrt(math.Pow(xdist, 2) + math.Pow(ydist, 2))
}

โปรแกรมนี้ชนะงานส่งหัวรุนแรงของฉันหรือไม่
pppery

ไม่ @ppperry ไม่เป็นเช่นนั้น มันเป็นอาหารสัตว์ปืนใหญ่ แต่ฉันกำลังทำงานกับบอทที่สอง
Naribe

2

Trigger Happy - Java 8

Trigger Happy เป็นวิวัฒนาการที่เรียบง่ายจากต้นตำรับของฉัน แต่ไม่มี Bombard bot มันเป็นบอทที่ง่ายมากที่จะยิงใส่ศัตรูที่เป็นเป้าหมายในปัจจุบันถ้ามีการยิงที่ชัดเจน ตลอดเวลาที่พยายามจะมีเกราะ

อย่างไรก็ตามสำหรับความเรียบง่ายทั้งหมดมันมีประสิทธิภาพมาก และจะทำลายบ็อตตัวอย่างอย่างง่ายดาย

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

จานความตายเทียบกับความสุข

Death-dish vs Trigger Happy

รหัสดังต่อไปนี้:

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

//import visual.Viewer;

public class TriggerHappy {

  static final int BOT_RADIUS = 30;
private static final double WALK_MAX_DIRECTION_CHANGE = Math.PI/3;
  static Bot targetedBot;

  enum BotState
  {
    INIT,
    RANDOM_WALK,
    FIRING,
    SHIELDING,
    DEAD,
    END
  }

  enum Power
  {
    MOVE,
    FIRE,
    SHIELD
  }


  private static PrintStream out;
private static List<Bot> enemyBots;
private static List<Bot> myBots;
//private static Viewer viewer;

  public static void main(String[] args) throws Exception
  {
    InetAddress localhost = Inet4Address.getLocalHost();
    Socket socket = new Socket(localhost, 50000);
    InputStream is = socket.getInputStream();
    out = new PrintStream(socket.getOutputStream());

    // read in the game configuration
    String line = readLine(is);
    Configuration config = new Configuration(line);
  //  viewer = new Viewer(line);

    myBots = config.bots.values().stream().filter(b->b.playerId==config.playerId).collect(Collectors.toList());
    enemyBots = config.bots.values().stream().filter(b->b.playerId!=config.playerId).collect(Collectors.toList());


    // set initial target
    targetedBot = enemyBots.get(enemyBots.size()/2);
    myBots.forEach(bot->target(bot,targetedBot));

    for (line = readLine(is);line!=null;line = readLine(is))
    {
//      viewer.update(line);
      // read in next bot update message from server
      Bot updatedBot = new Bot(line);
      Bot currentBot = config.bots.get(updatedBot.uniqueId);
      currentBot.update(updatedBot);

      // check for bot health
      if (currentBot.health<1)
      {
        // remove dead bots from lists
        currentBot.state=BotState.DEAD;
        if (currentBot.playerId == config.playerId)
        {
          myBots.remove(currentBot);
        }
        else
        {
          enemyBots.remove(currentBot);

          // change target if the targetted bot is dead
          if (currentBot == targetedBot)
          {
            if (enemyBots.size()>0)
            {
              targetedBot = enemyBots.get(enemyBots.size()/2);
              myBots.forEach(bot->target(bot,targetedBot));
            }
            // no more enemies... set bots to end state
            else
            {
              myBots.forEach(bot->bot.state = BotState.END);
            }
          }
        }
      }
      else
      {
          // ensure our first priority is shielding
          if (!currentBot.shield && currentBot.state!=BotState.SHIELDING)
          {
              currentBot.state=BotState.SHIELDING;
              shield(currentBot);
          }
          else
          {
              // not game end...
              if (currentBot.state != BotState.END)
              {
                // command to fire if we have a clear shot
                if (clearShot(currentBot))
                {
                    currentBot.state=BotState.FIRING;
                    fire(currentBot);
                }
                // randomly walk to try and get into a better position to fire
                else
                {
                    currentBot.state=BotState.RANDOM_WALK;
                    currentBot.dir+=Math.random()*WALK_MAX_DIRECTION_CHANGE - WALK_MAX_DIRECTION_CHANGE/2;
                    move(currentBot, (int)(currentBot.x+Math.cos(currentBot.dir)*100), (int) (currentBot.y+Math.sin(currentBot.dir)*100));
                }

              }
          }
      }
    }
    is.close();
    socket.close();
  }

// returns true if there are no friendly bots in firing line... mostly
private static boolean clearShot(Bot originBot)
{

    double originToTargetDistance = originBot.distanceFrom(targetedBot);
    for (Bot bot : myBots)
    {
        if (bot != originBot)
        {
            double x1 = originBot.x - bot.x;
            double x2 = targetedBot.x - bot.x;
            double y1 = originBot.y - bot.y;
            double y2 = targetedBot.y - bot.y;
            double dx = x2-x1;
            double dy = y2-y1;
            double dsquared = dx*dx + dy*dy;
            double D = x1*y2 - x2*y1;
            if (1.5*BOT_RADIUS * 1.5*BOT_RADIUS * dsquared > D * D && bot.distanceFrom(targetedBot) < originToTargetDistance)
            {
                return false;
            }
        }
    }

    return true;

}


  static class Bot
  {
    int playerId;
    int botId;
    int x;
    int y;
    int health;
    boolean fired;
    int hitX;
    int hitY;
    double dir = Math.PI*2*Math.random();
    boolean shield;
    int uniqueId;
    BotState state = BotState.INIT;
    Power power = Power.SHIELD;


    Bot(String line)
    {
      String[] tokens = line.split(",");
      playerId = extractInt(tokens[1]);
      botId = extractInt(tokens[2]);
      x = extractInt(tokens[3]);
      y = extractInt(tokens[4]);
      health = extractInt(tokens[5]);
      fired = extractBoolean(tokens[6]);
      hitX = extractInt(tokens[7]);
      hitY = extractInt(tokens[8]);
      shield = extractBoolean(tokens[10]);
      uniqueId = playerId*10000+botId;
    }

    Bot()
    {
    }

    double distanceFrom(Bot other)
    {
        return distanceFrom(new Point(other.x,other.y));
    }

    double distanceFrom(Point other)
    {
        double deltaX = x - other.x;
        double deltaY = y - other.y;
        return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
    }

    void update(Bot other)
    {
      x = other.x;
      y = other.y;
      health = other.health;
      fired = other.fired;
      hitX = other.hitX;
      hitY = other.hitY;
      shield = other.shield;
    }
  }

  static class Configuration
  {
    BotState groupState = BotState.INIT;
    HashMap<Integer,Bot> bots = new HashMap<>();
    boolean isOpponentInitiated;
    int playerId;

    Configuration(String line) throws Exception
    {
      String[] tokens = line.split("\\[");
      playerId = extractInt(tokens[0].split(",")[1]);

      for (String token : tokens[1].split("\\|"))
      {
        Bot bot = new Bot(token);
        bots.put(bot.uniqueId,bot);
      }
    }
  }

  /**
   * Reads a line of text from the input stream. Blocks until a new line character is read.
   * NOTE: This method should be used in favor of BufferedReader.readLine(...) as BufferedReader buffers data before performing
   * text line tokenization. This means that BufferedReader.readLine() will block until many game frames have been received. 
   * @param in a InputStream, nominally System.in
   * @return a line of text or null if end of stream.
   * @throws IOException
   */
  static String readLine(InputStream in) throws IOException
  {
     StringBuilder sb = new StringBuilder();
     int readByte = in.read();
     while (readByte>-1 && readByte!= '\n')
     {
        sb.append((char) readByte);
        readByte = in.read();
     }
     return readByte==-1?null:sb.toString().replace(",{", "|").replaceAll("}", "");

  }

  final static class Point
  {
    public Point(int x2, int y2) {
        x=x2;
        y=y2;
    }
    int x;
    int y;
  }

  public static int extractInt(String token)
  {
    return Integer.parseInt(token.split(":")[1]);
  }

  public static boolean extractBoolean(String token)
  {
    return Boolean.parseBoolean(token.split(":")[1]);
  }

  static void distributePower(Bot bot, int fire, int move, int shield)
  {
    out.println("{\"Cmd\":\"POWER\",\"BID\":"+bot.botId+",\"FPow\":"+fire+",\"MPow\":"+move+",\"SPow\":"+shield+"}");
//  viewer.distributePower(bot.botId, fire, move, shield);
  }

  static void shield(Bot bot)
  {
    distributePower(bot,0,0,12);
    bot.power=Power.SHIELD;
  }

  static void move(Bot bot, int x, int y)
  {
    distributePower(bot,0,12,0);
    out.println("{\"Cmd\":\"MOVE\",\"BID\":"+bot.botId+",\"X\":"+x+",\"Y\":"+y+"}");
  }
  static void target(Bot bot, Bot target)
  {
    out.println("{\"Cmd\":\"TARGET\",\"BID\":"+bot.botId+",\"TPID\":"+target.playerId+",\"TBID\":"+target.botId+"}");
  }

  static void fire(Bot bot)
  {
    distributePower(bot,12,0,0);
    bot.power=Power.FIRE;
  }
}

ในการรวบรวม: javac TriggerHappy.java

วิธีเรียกใช้: java TriggerHappy

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