Scrapper v0.1: Söldnerprogrammierer

22

In einer trostlosen, kriegszerrütteten Welt, in der Städte von Schlägern und Dieben überrannt wurden, hat sich die Zivilisation in Form kleiner, isolierter Industriegenossenschaften neu erfunden, die in der zuvor unbewohnten Landschaft verstreut sind. Die Existenz dieser Gemeinschaften ist abhängig von Teams von Söldnern, sogenannten "Scrappern", die das ungezähmte Gebiet nach wertvollen Materialien absuchen, um diese an die Coops zu verkaufen. Da diese Materialien immer knapper werden, wird das Verschrotten immer schwieriger und gefährlicher. Zerbrechliche menschliche Arbeiter wurden größtenteils durch Roboter-Stellvertreter ersetzt, sogenannte "Bots", und ein typischer Söldner ist eher ein erfahrener Programmierer als ein bewaffneter Schweißer. Mit der Abnahme der menschlichen Präsenz beim Verschrotten nimmt auch der Respekt zwischen Söldnergruppen untereinander ab. Bots sind so ausgestattet, dass sie Schrott nicht nur sammeln, sondern auch verteidigen und in einigen Fällen mit Gewalt aufnehmen können. Bot-Programmierer arbeiten unermüdlich an neuen Strategien, um konkurrierende Scrapper zu überlisten. Dies führt zu immer aggressiveren Bots und ist eine weitere Gefahr für Menschen, die sich außerhalb der Mauern ihrer Communitys bewegen.

Scrappers Spielbeispiel

(Ja, das Logo wird urkomisch beschnitten)

Willkommen bei Scrappers!

Dies ist eine frühe Version von Scrappers, in der keine Schrottsammlung und -fabriken implementiert wurden. Es ist im Grunde ein "Shoot'em Up".

Sie sind ein Söldner-Programmierer, der ein Programm erstellt, mit dem Sie Ihre Bots aus der Ferne zum Sieg über rivalisierende Scrapper-Gruppen führen können. Ihre Bots sind spinnenartige Maschinen, die im Kern aus Strom- und Schildgeneratoren bestehen und von vielen Anhängen umgeben sind, die mit Greif-, Schneid- und Angriffsgeräten ausgestattet sind. Der Stromgenerator kann 12 Leistungseinheiten (pu) pro Tick (Zeiteinheit eines Scrappers) erzeugen. Sie haben die Kontrolle darüber, wie diese Kraft auf die drei Hauptbedürfnisse eines Bots verteilt wird: Bewegung, Schilde und Feuerkraft.

Scrapper-Bots sind außergewöhnlich agile Maschinen, die sich leicht über, unter und um Hindernisse bewegen können, auf die sie stoßen. Daher ist Kollision nicht etwas, das Ihr Programm berücksichtigen muss. Es steht Ihnen frei, alle, einige oder keine der 12PUs, die Ihrem Bot zur Verfügung stehen, für die Bewegung zuzuweisen, solange Sie mit ganzen Zahlen handeln. Das Zuweisen von 0pu zu den Bewegungsfunktionen eines Bots würde ihn unbeweglich machen. Das Zuweisen von 2pu würde es einem Bot ermöglichen, sich 2 Entfernungseinheiten (du) pro Tick zu bewegen, 5pu würde 5du / Tick ergeben, 11pu würde 11du / Tick ergeben und so weiter.

Die Schildgeneratoren Ihrer Bots projizieren eine Blase ablenkender Energie um ihren Körper. Ein Schild kann vor dem Aufspringen bis zu 1 Schaden abwehren, sodass Ihr Bot ausgesetzt bleibt, bis der Schildgenerator genug Energie aufbaut, um den Schild wieder einzurasten. Es steht Ihnen frei, Ihrem Bot alle, einige oder keine der verfügbaren 12pu in Richtung seines Schildes zuzuweisen. Das Zuweisen von 0pu zum Schild eines Bots bedeutet, dass niemals ein Schild erzeugt wird. Wenn Sie 2pu zuweisen, kann ein Bot 2 von 12 Ticks oder einmal alle 6 Ticks einen neuen Schild generieren. 5pu würde zu einer Schildregeneration von 5 von 12 Ticks führen und so weiter.

Durch den Aufbau einer Ladung in ihren Schweißlasern können Ihre Bots schädliche Strahlen über kurze Entfernungen mit angemessener Genauigkeit abfeuern. Wie bei der Schilderzeugung hängt die Feuerrate Ihrer Bots von der Leistung ab, die ihren Lasern zugewiesen wurde. Das Zuweisen von 0pu zu den Lasern eines Bots bedeutet, dass dieser niemals feuern wird. Wenn Sie 2pu zuweisen, kann ein Bot 2 von 12 Ticks abfeuern und so weiter. Der Laser eines Bots bewegt sich so lange, bis er auf ein Objekt trifft oder sich in Nutzlosigkeit auflöst. Achten Sie daher auf das freundliche Feuer. Obwohl Ihre Bots ziemlich genau sind, sind sie nicht perfekt. Sie sollten mit einer Genauigkeitsabweichung von +/- 2,5 Grad rechnen. Während sich der Laserstrahl fortbewegt, werden seine Partikel inkrementell von der Atmosphäre abgelenkt, bis der Strahl mit ausreichender Entfernung effektiv unschädlich wird. Ein Laser verursacht 1 Schaden aus nächster Nähe und 2,5% weniger Schaden mit jeder Botlänge, die er zurücklegt.

Scrapper-Bots sind autonom genug, um grundlegende Funktionen auszuführen, verlassen sich jedoch darauf, dass Sie, ihr Programmierer, sie als Gruppe nützlich machen. Als Programmierer können Sie für jeden einzelnen Bot die folgenden Befehle eingeben:

  • MOVE: Geben Sie die Koordinaten an, zu denen sich ein Bot bewegen soll.
  • ZIEL: Identifizieren Sie einen Bot, auf den Sie zielen und auf den Sie feuern möchten, wenn die Leistungszuweisung dies zulässt.
  • POWER: Verteilt die Kraft zwischen Bewegung, Schild und Feuerkraft.

Technische Spieldetails

Es gibt drei Programme, mit denen Sie vertraut sein müssen. Die Game Engine ist der Heavy Lifter und stellt eine TCP-API bereit, mit der die Player-Programme eine Verbindung herstellen. Das Player-Programm ist das, was Sie schreiben werden, und ich habe hier einige Beispiele mit Binärdateien bereitgestellt . Schließlich verarbeitet der Renderer die Ausgabe der Game Engine, um ein GIF des Kampfes zu erstellen.

Die Game Engine

Sie können die Spiele-Engine hier herunterladen . Wenn das Spiel gestartet wird, lauscht es auf Port 50000 (derzeit nicht konfigurierbar) nach Spielerverbindungen. Sobald es zwei Spielerverbindungen erhält, sendet es die BEREIT-Nachricht an die Spieler und beginnt das Spiel. Spielerprogramme senden Befehle über die TCP-API an das Spiel. Wenn das Spiel vorbei ist, wird eine JSON-Datei mit dem Namen scrappers.json (ebenfalls derzeit nicht konfigurierbar) erstellt. Dies ist, was der Renderer verwendet, um ein GIF des Spiels zu erstellen.

Die TCP-API

Spielerprogramme und die Game Engine kommunizieren miteinander, indem sie JSON-Zeichenfolgen mit Zeilenumbruch über eine TCP-Verbindung zurück und an vierter Stelle übergeben. Es gibt nur fünf verschiedene JSON-Nachrichten, die gesendet oder empfangen werden können.

Bereit Nachricht

Die BEREIT-Nachricht wird vom Spiel an die Spielerprogramme gesendet und nur einmal gesendet. Diese Nachricht teilt dem Spielerprogramm mit, wie seine Spieler-ID (PID) lautet, und enthält eine Liste aller Bots im Spiel. Die PID ist der einzige Weg, um zu bestimmen, welche Bots freundlich gegen den Feind sind. Mehr Infos zu den Botfeldern unten.

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

Bot Nachricht

Die BOT-Nachricht wird vom Spiel an die Spielerprogramme gesendet und wird gesendet, wenn sich die Attribute eines Bots ändern. Wenn beispielsweise Schilde projiziert werden oder sich der Zustand ändert, wird eine BOT-Nachricht gesendet. Die Bot-ID (BID) ist nur innerhalb eines bestimmten Spielers eindeutig.

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

Nachricht verschieben

Die MOVE-Nachricht ist ein Befehl vom Player-Programm an das Spiel (aber stellen Sie sich das als Befehl an einen Bot vor). Identifizieren Sie einfach den Bot, den Sie bewegen möchten, und die Koordinaten. Es wird davon ausgegangen, dass Sie Ihren eigenen Bot befehlen, sodass keine PID erforderlich ist.

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

Zielnachricht

Die TARGET-Nachricht weist einen Ihrer Bots an, auf einen anderen Bot abzuzielen.

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

Die POWER-Nachricht teilt die Ihrem Bot zur Verfügung stehenden 12pu zwischen Bewegung, Feuerkraft und Schildern neu auf.

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

Der Wettbewerb

Wenn Sie mutig genug sind, das ungezähmte Land zu erkunden, treten Sie gegen Ihre Söldner-Kollegen in einem Doppel-Eliminierungs-Turnier an. Bitte erstellen Sie eine Antwort für Ihren Beitrag und fügen Sie entweder Ihren Code ein oder stellen Sie einen Link zu einem Git-Repo, einem Gist usw. bereit. Jede Sprache ist akzeptabel, aber Sie sollten davon ausgehen, dass ich nichts über die Sprache weiß und Anweisungen zum Ausführen Ihres Programms beifüge. Erstellen Sie so viele Einreichungen, wie Sie möchten, und geben Sie ihnen Namen!

Die Beispiel-Player-Programme werden in das Turnier aufgenommen, daher empfehle ich dringend, Ihren Bot gegen sie zu testen. Das Turnier beginnt ungefähr zwei Wochen, nachdem wir vier einmalige Programmeinreichungen erhalten haben. Viel Glück!

--- Winner's Bracket ---

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

Andere wichtige Informationen

  • Das Spiel läuft mit 12 Ticks / Sekunde, so dass Sie nicht häufiger als alle 83 Millisekunden oder so Nachrichten erhalten.
  • Jeder Bot hat einen Durchmesser von 60du. Der Schild benötigt keinen zusätzlichen Platz. Mit einer Genauigkeit von +/- 2,5% wird die Wahrscheinlichkeit, einen Bot aus einer bestimmten Entfernung zu treffen, durch diese Grafik dargestellt:

Genauigkeitsgraph

  • Die Abnahme des Laserschadens über die Entfernung wird durch diese Grafik dargestellt:

Schadensabbaudiagramm

  • Die Genauigkeit und der Laserzerfall eines Bots ergeben zusammen den durchschnittlichen Schaden pro Schuss. Das heißt, der durchschnittliche Schaden, den ein Bot verursacht, wenn er aus einer bestimmten Entfernung feuert. Der Schaden pro Schuss wird durch diese Grafik dargestellt:

Schaden pro Schuss Grafik

  • Der Laser eines Bots entsteht auf halbem Weg zwischen dem Zentrum des Bots und seiner Kante. Wenn Sie Ihre Bots stapeln, wird dies zu einem freundlichen Feuer führen.
  • Feindliche Bots erscheinen ungefähr 1440du auseinander.
  • Das Spiel endet, wenn 120 Ticks (10 Sekunden) ohne zugefügten Schaden verstrichen sind.
  • Der Gewinner ist der Spieler mit den meisten Bots, dann die meiste Gesundheit, wenn das Spiel endet.

Grundlegendes zum gerenderten Bild

  • Spieler 1 wird durch Kreise und Spieler 2 durch Sechsecke dargestellt.
  • Die Farbe eines Bots repräsentiert seine Leistungsverteilung. Mehr Rot bedeutet, dass mehr Energie für das Feuern bereitgestellt wurde. Mehr Blau bedeutet mehr Schild. Mehr Grün bedeutet mehr Bewegung.
  • Das "Loch" im Körper eines Bots steht für Schaden. Je größer das Loch, desto mehr Schaden wurde genommen.
  • Die weißen Kreise, die einen Bot umgeben, sind sein Schild. Wenn ein Bot am Ende des Zuges einen Schild hat, wird dieser angezeigt. Wenn der Schild durch Schaden abgesprungen ist, wird er nicht angezeigt.
  • Die roten Linien zwischen den Bots stehen für die aufgenommenen Bilder.
  • Wenn ein Bot getötet wird, wird eine große rote "Explosion" angezeigt.
Rip Leeb
quelle
Kommentare sind nicht für eine längere Diskussion gedacht. Diese Unterhaltung wurde in den Chat verschoben .
Dennis

Antworten:

4

Extremist (Python 3)

Dieser Bot wird immer seine ganze Kraft einer Sache widmen: Abschirmen, wenn er nicht abgeschirmt ist, Bewegen, wenn er nicht in Position ist, und anderweitig schießen. Übertrifft alle Sample-Bots mit Ausnahme von Death Dish.

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
Pfeffer
quelle
Ich bin mit Python nicht vertraut, aber es scheint mehrere Probleme mit Ihrer Eingabe zu geben: 1) Zeilen 212 und 120 werden nicht richtig eingerückt und 2) target_hp ist nicht definiert. Ich konnte (1) beheben, aber (2) hindert mich daran, Ihre Übermittlung auszuführen. Aber es könnte mein Mangel an Erfahrung mit Python sein.
Moogie
Die gesamte if-Anweisung ist von einigen Debugging-
Vorgängen
2

Weniger rücksichtslos ( Go )

go run main.go

Ursprünglich wollte ich das Reckless Abandon-Beispielprogramm leicht modifizieren, damit Bots nicht feuern, wenn ein freundlicher Bot im Weg wäre. Am Ende hatte ich Bots, die neue Ziele auswählten, wenn ein Freund im Weg war. Ich denke, das ist besser. Es wird die ersten beiden Programme schlagen.

Der Code ist nicht perfekt. Die Logik, um zu bestimmen, ob ein Schuss klar ist, verwendet eine ziemlich zufällige Vermutung.

Es scheint keinen Mechanismus zu geben, um "niemanden" ins Visier zu nehmen. Das könnte ein gutes Feature sein.

Die TCP-API ist insofern nett, als jede Sprache abgespielt werden kann, aber es bedeutet auch eine Menge Boilerplate-Code. Wenn ich nicht mit der Sprache vertraut wäre, in der die Beispiel-Bots geschrieben wurden, wäre ich wahrscheinlich nicht motiviert gewesen, damit herumzuspielen. Eine Sammlung von Boilerplate-Beispielen in verschiedenen Sprachen ist eine großartige Ergänzung für Ihre anderen Git-Repos.

(Der größte Teil des folgenden Codes stammt aus einem der Beispiel-Bots.)

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))
}
Naribe
quelle
Schlägt dieses Programm meine extremistische Unterwerfung?
Paprika
Nein @ppperry, tut es nicht. Es ist Kanonenfutter, aber ich arbeite an einem zweiten Bot.
Naribe
2

Auslöser Happy - Java 8

Trigger Happy ist eine einfache Weiterentwicklung meines ursprünglichen, aber nicht mehr lebensfähigen Bombard-Bots. Es ist ein sehr einfacher Bot, der einfach auf den aktuell anvisierten Feind feuert, wenn ein klarer Schuss abgegeben wird. Andernfalls führt er einen zufälligen Spaziergang durch, um eine bessere Position zu erreichen. Die ganze Zeit versucht, einen Schild zu haben.

Trotz seiner Einfachheit ist es sehr effektiv. Und zerstören leicht die Beispielroboter.

Beachten Sie, dass es mit dem Bot mehrere Fehler gibt, die manchmal auch dann ausgelöst werden, wenn kein klarer Schuss abgegeben wurde und möglicherweise keinen Schild beibehalten werden

Death Dish vs Trigger glücklich

Death Dish gegen Trigger Happy

Code wie folgt:

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;
  }
}

So kompilieren Sie: javac TriggerHappy.java

Zum Ausführen: Java TriggerHappy

Moogie
quelle