Scrappers v0.1:Mer兵プログラマー


22

都市が凶悪犯や泥棒によって圧倒されている荒廃した戦争で荒廃した世界では、文明は以前は無人の風景に散らばっていた小さな孤立した産業協同組合の形でそれ自体を改革しました。これらのコミュニティの存在は、「スクレイパー」と呼ばれるmerc兵労働者のチームに依存しており、彼らは小屋に売るための貴重な材料を手付かずの領域で探します。これらの材料がますます不足するにつれて、廃棄はますます困難で危険な職業になっています。壊れやすい人間の労働者は、ほとんどが「ロボット」と呼ばれるリモートロボットスタンドインに置き換えられており、典型的なmerc兵は武装した溶接機よりも熟練したプログラマである可能性が高くなります。廃棄における人間の存在が減少するにつれて、merc兵グループ同士の尊敬もまた減りました。ボットは、スクラップを収集するだけでなく、それを守るために装備されており、場合によってはそれを強制的に取得します。ボットプログラマーは、ライバルスクラッパーをしのぐための新しい戦略をたゆみなく考案し、攻撃的なボットを増やし、コミュニティの壁の外に飛び出す人間にとっては別の危険をもたらします。

スクレイパーゲームのサンプル

(ええ、ロゴは陽気に切り取られます)

Scrappersへようこそ!

これはスクラップ収集と工場が実装されていない初期バージョンのスクレイパーです。基本的には「撃つ」ことです。

あなたは、ライバルのスクレーパーグループを勝ち抜くためにボットをリモートで実行するプログラムを作成するmerc兵プログラマです。ボットは、把持と切断、攻撃用具を装備した多くの付属物に囲まれた、電源とシールドジェネレーターを中心にしたクモのような機械です。発電機は、ティック(スクレーパーの時間単位)ごとに12の電力ユニット(pu)を生成できます。あなたは、この力がボットの3つの主要なニーズにどのように分配されるかを制御します:移動、シールド、および火力。

スクレイパーボットは非常に機敏なマシンであり、遭遇する障害物の上、下、および周囲を簡単に移動できます。したがって、衝突はプログラムで考慮する必要のあるものではありません。整数で処理する限り、移動に使用できる12puのすべて、一部、またはすべてを自由に割り当てることができます。ボットの移動関数に0puを割り当てると、ボットは動かなくなります。2puを割り当てると、ボットはティックごとに2距離単位(du)移動でき、5puは5du / tickになり、11puは11du / tickになります。

ボットのシールドジェネレーターは、体の周りに偏向エネルギーの泡を投影します。シールドはポッピングする前に最大1ダメージまで撓むことができるため、シールドジェネレーターがシールドを元の位置に戻すのに十分な電力を生成するまでボットを露出したままにします。ボットが利用できる12puのすべて、一部、またはどれもをシールドに割り当てることは自由です。ボットのシールドに0puを割り当てることは、ボットがシールドを生成しないことを意味します。2puを割り当てると、ボットは12ティックのうち2ティック、または6ティックごとに1回、新しいシールドを生成できます。5puでは、12ティックごとに5シールドの再生成が行われます。

溶接レーザーに電荷を蓄積することにより、ボットは短距離で損傷ビームをかなりの精度で発射できます。シールドの生成と同様に、ボットの発射速度はレーザーに割り当てられたパワーに依存します。ボットのレーザーに0puを割り当てることは、ボットが発砲しないことを意味します。2puを割り当てると、ボットは12ティックごとに2発を発射できるようになります。ボットのレーザーは、物体に遭遇するか、無用に分散するまで移動するため、友好的な火に注意してください。ボットは非常に正確ですが、完全ではありません。精度に+/- 2.5度のばらつきがあるはずです。レーザービームが進むにつれて、その粒子は、ビームが十分な距離で効果的に無害になるまで、大気によって徐々に偏向されます。レーザーは、ポイントブランクの範囲で1ダメージを与え、移動するボットの長さごとにダメージを2.5%減らします。

Scrapperボットは基本的な機能を処理するのに十分な自律性を備えていますが、プログラマーであるあなたに依存してグループとして役立つようにします。プログラマーとして、各ボットに対して次のコマンドを発行できます。

  • MOVE:ボットが移動する座標を指定します。
  • ターゲット:電力割り当てが許可されたときに狙いを定めて発砲するボットを特定します。
  • パワー:移動、シールド、火力の間でパワーを再配分します。

テクニカルゲームの詳細

あなたが精通する必要がある3つのプログラムがあります。ゲームエンジンは重いリフターで、プレーヤープログラムが接続することをTCPのAPIを提供します。プレイヤープログラムはあなたが書くものであり、ここでバイナリを使用したいくつかの例を提供しまし。最後に、レンダラーはゲームエンジンからの出力を処理して、戦闘のGIFを生成します。

ゲームエンジン

ここからゲームエンジンダウンロードできます。ゲームが起動されると、ポート50000(現在は構成不可)でプレーヤー接続のリッスンを開始します。2つのプレーヤー接続を受信すると、READYメッセージをプレーヤーに送信し、ゲームを開始します。プレーヤープログラムは、TCP APIを介してコマンドをゲームに送信します。ゲームが終了すると、scrappers.jsonという名前のJSONファイル(現在は構成できません)が作成されます。これは、レンダラーがゲームのGIFを作成するために使用するものです。

TCP API

プレーヤープログラムとゲームエンジンは、改行で終了するJSON文字列をTCP接続でやり取りすることで通信します。送信または受信できるJSONメッセージは5つだけです。

レディメッセージ

READYメッセージは、ゲームからプレーヤープログラムに送信され、一度だけ送信されます。このメッセージは、プレーヤープログラムにプレーヤーID(PID)が何であるかを伝え、ゲーム内のすべてのボットのリストを提供します。PIDは、どのボットが味方か敵かを判断する唯一の方法です。以下のボットフィールドの詳細。

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

ボットメッセージ

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
}

対象メッセージ

TARGETメッセージは、ボットの1つに他のボットをターゲットにするよう指示します。

{
    "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
}

競争

あなたが手つかずの土地を探索するのに十分勇気があるなら、あなたはmerc兵仲間との二重排除トーナメントに参加します。投稿に対する回答を作成し、コードを貼り付けるか、gitリポジトリ、要点などへのリンクを提供してください。どの言語でも構いませんが、言語について何も知らないと仮定し、プログラムの実行手順を含めてください。好きなだけ投稿を作成し、必ず名前を付けてください!

サンプルプレイヤーのプログラムは、私は非常に彼らに対するあなたのボットをテストをお勧めしますので、トーナメントに含まれます。トーナメントは、4つのユニークなプログラムの提出を受けてから約2週間後に始まります。がんばろう!

--- Winner's Bracket ---

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

その他の重要な情報

  • ゲームは毎秒12ティックで実行されるため、83ミリ秒ごとよりも頻繁にメッセージを受信することはありません。
  • 各ボットの直径は60duです。シールドは追加のスペースを必要としません。+/- 2.5%の精度では、特定の距離でボットに当たる確率は次のグラフで表されます。

精度グラフ

  • 距離に対するレーザー損傷の減衰は、次のグラフで表されます。

損傷減衰グラフ

  • ボットの精度とレーザー減衰が組み合わされて、ショットあたりの平均ダメージが計算されます。つまり、特定の距離から発砲したときにボットが引き起こす平均的なダメージです。ショットあたりのダメージは次のグラフで表されます。

ショットごとのダメージグラフ

  • ボットのレーザーは、ボットの中心とエッジの中間地点で発生します。したがって、ボットを積み重ねると、友好的な射撃が行われます。
  • 敵のボットは約1440du離れて出現します。
  • 120ティック(10秒)経過してもダメージが与えられない場合、ゲームは終了します。
  • 勝者は、ボットが最も多いプレーヤーであり、ゲームが終了すると最も健康になります。

レンダリングされた画像を理解する

  • プレーヤー1は円で、プレーヤー2は六角形で表されます。
  • ボットの色は、その電力割り当てを表します。赤が多いほど、発砲により多くの電力が割り当てられていることを意味します。青が多いほどシールドが大きくなります。より多くの緑はより多くの動きを意味します。
  • ボットのボディの「穴」は損傷を表します。穴が大きいほど、より多くのダメージを受けます。
  • ボットを囲む白い円はシールドです。ボットがターンの終わりにシールドを持っている場合、それが表示されます。ダメージを受けてシールドが破裂した場合、表示されません。
  • ボット間の赤い線は、撮影したショットを表します。
  • ボットが殺されると、大きな赤い「爆発」が表示されます。

コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
デニス

回答:


4

過激派(Python 3)

このボットは、その力のすべてを常に1つのことに費やします。シールドされていない場合はシールドし、位置がずれている場合は移動し、そうでなければ発射します。デスディッシュを除くすべてのサンプルボットを破ります。

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

私はPythonに不慣れですが、あなたの提出には複数の問題があるようです:1)行212 120が正しくインデントされていない、2)target_hpが定義されていません。(1)を修正することはできましたが、(2)を使用して送信を実行できなくなりました。しかし、それは私のpythonの経験不足かもしれません。
ムージー

そのifステートメント全体が一部のデバッグから残ったものであり、まったく必要ありません
-pppery

2

より無謀な(Go

go run main.go

元々、フレンドリーなボットが邪魔をした場合にボットが発動しないように、Reckless Abandonサンプルプログラムをわずかに変更する予定でした。友人が邪魔をしているときに新しいターゲットを選択するボットになりました。最初の2つのプログラムを破ります。

コードは完璧ではありません。ショットがクリアかどうかを判断するロジックは、かなりランダムな推測を使用します。

「誰も」をターゲットにするメカニズムはないようです。それは追加するのに良い機能かもしれません。

TCP APIは、どの言語でも再生できるという点で優れていますが、多くの定型コードも意味します。サンプルボットが記述された言語に慣れていなければ、おそらくこれをいじくり回そうとは思わなかっただろう。さまざまな言語の定型サンプルのコレクションは、他のgitリポジトリに追加するのに最適です。

(次のコードのほとんどは、サンプルボットの1つからコピー/貼り付けされています)

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はありません。それは大砲の餌ですが、私は2番目のボットに取り組んでいます。
成部

2

トリガーハッピー-Java 8

トリガーハッピーは、元のボマードボットの単純な進化ですが、もはや実行可能ではありません。これは非常にシンプルなボットであり、明確なショットがあれば現在ターゲットになっている敵に単純に発砲し、そうでなければランダムウォークを実行してより良い位置に到達しようとします。常にシールドを取得しようとしています。

ただし、その単純さのために非常に効果的です。そして、サンプルボットを簡単に破壊します。

ボットには複数のバグがあり、明確なショットではない場合でも発砲し、シールドを維持できない場合があります...しかし、それはまだ有効であるため、このエントリをそのまま送信します

デスディッシュvsトリガーハッピー

デスディッシュvsトリガーハッピー

次のコード:

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.