Scrappers v0.1: programmatori mercenari


22

In un mondo desolato e devastato dalla guerra, dove le città sono state invase da delinquenti e ladri, la civiltà si è reinventata sotto forma di piccole cooperative isolate, industriali, sparse in tutto il paesaggio precedentemente disabitato. L'esistenza di queste comunità dipende da squadre di operai mercenari chiamati "scrappers", che cercano nel territorio selvaggio i materiali preziosi da vendere alle cooperative. Poiché questi materiali sono diventati sempre più scarsi, la demolizione è diventata una professione sempre più difficile e pericolosa. I fragili lavoratori umani sono stati per lo più sostituiti con stand-in robot remoti, chiamati "robot", e un mercenario tipico ha più probabilità di essere un abile programmatore che un saldatore armato. Poiché la presenza umana nella demolizione è diminuita, anche il rispetto tra i gruppi mercenari è diminuito. I robot sono attrezzati non solo per raccogliere rottami, ma anche per difenderli e in alcuni casi lo prendono con la forza. I programmatori di robot lavorano instancabilmente elaborando nuove strategie per superare in astuzia gli scrappers rivali, risultando in robot sempre più aggressivi e un altro pericolo per gli umani che si avventurano fuori dalle mura delle loro comunità.

Esempio di gioco Scrappers

(sì, il logo viene ritagliato in modo esilarante)

Benvenuto in Scrappers!

Questa è una versione iniziale di Scrappers in cui non sono state implementate la raccolta e le fabbriche di rottami. Fondamentalmente è uno "sparatutto".

Sei un programmatore mercenario incaricato di creare un programma per condurre i tuoi robot in remoto alla vittoria su gruppi di scrapper rivali. I tuoi robot sono macchine simili a ragni costituiti da generatori di potenza e scudo nel loro nucleo, circondati da molte appendici dotate di strumenti di presa, taglio e attacco. Il generatore di corrente è in grado di produrre 12 unità di potenza (unità di elaborazione) per tick (unità di tempo di uno scrapper). Hai il controllo di come questo potere viene distribuito tra i tre bisogni primari di un robot: movimento, scudi e potenza di fuoco.

I robot Scrapper sono macchine eccezionalmente agili e possono facilmente spostarsi sopra, sotto e intorno agli ostacoli che incontrano. Pertanto, la collisione non è qualcosa che il tuo programma ha bisogno di prendere in considerazione. Sei libero di allocare tutti, alcuni o nessuno dei 12pu disponibili per il tuo bot per il movimento, a patto che ti occupi di numeri interi. Allocare 0pu alle funzioni di movimento di un bot lo renderebbe immobile. Allocare 2pu consentirebbe a un bot di spostare 2 unità di distanza (du) per tick, 5pu comporterebbe 5du / tick, 11pu comporterebbe 11du / tick e così via.

I generatori di scudi dei tuoi robot proiettano una bolla di energia deflettiva attorno al loro corpo. Uno scudo può deviare fino a 1 danno prima di scoppiare, lasciando così il robot esposto fino a quando il suo generatore di scudo non sviluppa abbastanza potenza per far scattare lo scudo in posizione. Sei libero di allocare tutti, alcuni o nessuno dei 12pu disponibili per il tuo bot verso lo scudo. Allocare 0pu allo scudo di un bot significa che non genererà mai uno scudo. Allocare 2pu consentirebbe a un bot di generare un nuovo scudo 2 su 12 tick o una volta ogni 6 tick. 5pu comporterebbe una rigenerazione dello scudo 5 su ogni 12 tick e così via.

Costruendo una carica nei loro laser di saldatura, i tuoi robot possono sparare raggi dannosi su brevi distanze con una precisione assoluta. Come la generazione di scudi, la velocità di fuoco dei tuoi robot dipende dalla potenza assegnata ai loro laser. Allocare 0pu ai laser di un bot significa che non sparerà mai. Allocare 2pu consentirebbe a un bot di sparare 2 su ogni 12 tick e così via. Il laser di un robot viaggerà fino a quando non incontra un oggetto o si disperde nell'inutilità, quindi sii consapevole del fuoco amico. Sebbene i tuoi robot siano abbastanza precisi, non sono perfetti. Dovresti aspettarti una precisione di +/- 2,5 gradi di varianza. Mentre il raggio laser viaggia, le sue particelle vengono deviate in modo incrementale dall'atmosfera fino a quando il raggio diventa effettivamente innocuo con una distanza sufficiente. Un laser infligge 1 danno a distanza del punto in bianco e il 2,5% in meno di danni a ogni lunghezza del bot che percorre.

I robot Scrapper sono abbastanza autonomi da gestire le funzioni di base, ma fanno affidamento su di te, il loro programmatore, per renderli utili come gruppo. Come programmatore, puoi emettere i seguenti comandi per ogni singolo bot:

  • SPOSTA: specifica le coordinate verso le quali si sposterà un bot.
  • OBIETTIVO: identificare un robot a cui puntare e sparare quando l'allocazione di potenza lo consente.
  • POTENZA: ridistribuire il potere tra movimento, scudi e potenza di fuoco.

Dettagli tecnici del gioco

Esistono tre programmi che devi conoscere. Il motore di gioco è il sollevatore pesante e fornisce un'API TCP che i programmi giocatore connettersi. Il programma del lettore è ciò che scriverai, e ho fornito alcuni esempi con i binari qui . Infine, il Renderer elabora l'output del Game Engine per produrre una GIF della battaglia.

Il motore di gioco

Puoi scaricare il motore di gioco da qui . All'avvio del gioco, inizierà l'ascolto sulla porta 50000 (attualmente non configurabile) per le connessioni dei giocatori. Una volta che riceve due connessioni giocatori, invia il messaggio PRONTO ai giocatori e inizia il gioco. I programmi del giocatore inviano i comandi al gioco tramite l'API TCP. Al termine del gioco, viene creato un file JSON chiamato scrappers.json (anche attualmente non configurabile). Questo è ciò che il renderer utilizza per creare una GIF del gioco.

L'API TCP

I programmi del giocatore e il motore di gioco comunicano passando le stringhe JSON terminate da una riga indietro e la quarta su una connessione TCP. Esistono solo cinque diversi messaggi JSON che possono essere inviati o ricevuti.

Messaggio pronto

Il messaggio PRONTO viene inviato dal gioco ai programmi del giocatore e viene inviato una sola volta. Questo messaggio indica al programma del giocatore qual è il suo ID giocatore (PID) e fornisce un elenco di tutti i robot nel gioco. Il PID è l'unico modo per determinare quali robot sono amici o nemici. Maggiori informazioni sui campi bot di seguito.

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

Messaggio Bot

Il messaggio BOT viene inviato dal gioco ai programmi del giocatore e viene inviato quando gli attributi di un bot cambiano. Ad esempio, quando si proiettano gli scudi o si modificano le condizioni di salute, viene inviato un messaggio BOT. L'ID Bot (BID) è unico solo all'interno di un determinato giocatore.

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

Sposta messaggio

Il messaggio MOVE è un comando dal programma del giocatore al gioco (ma pensalo come un comando a un bot). Basta identificare il bot che si desidera spostare e le coordinate. Si presume che tu stia comandando il tuo bot, quindi non è necessario alcun PID.

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

Messaggio di destinazione

Il messaggio TARGET dice a uno dei tuoi robot di scegliere come target un altro bot.

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

Messaggio di alimentazione

Il messaggio POWER rialloca la 12pu disponibile per il tuo bot tra movimento, potenza di fuoco e scudi.

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

La competizione

Se sei abbastanza coraggioso da esplorare le terre selvagge, entrerai in un torneo a doppia eliminazione contro i tuoi pari mercenari. Si prega di creare una risposta per l'invio e incollare il codice o fornire un collegamento a un repository git, gist, ecc. Qualsiasi lingua è accettabile, ma si dovrebbe presumere che io non sappia nulla della lingua e includa le istruzioni per l'esecuzione del programma. Crea tutti gli invii che desideri e assicurati di dare loro i nomi!

I programmi del giocatore campione saranno inclusi nel torneo, quindi consiglio vivamente di testare il bot contro di loro. Il torneo avrà inizio circa due settimane dopo che avremo ricevuto quattro proposte di programma uniche. In bocca al lupo!

--- Winner's Bracket ---

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

Altre informazioni importanti

  • Il gioco funziona a 12 tick / secondo, quindi non riceverai messaggi più frequentemente di circa 83 millisecondi circa.
  • Ogni bot ha un diametro di 60du. Lo scudo non occupa spazio aggiuntivo. Con una precisione del +/- 2,5%, le probabilità di colpire un bot a una certa distanza sono rappresentate da questo grafico:

grafico di precisione

  • Il decadimento del danno laser sulla distanza è rappresentato da questo grafico:

grafico di decadimento del danno

  • La precisione di un robot e il decadimento laser si combinano per calcolare il danno medio per colpo. Cioè, il danno medio che un bot causerà quando spara da una certa distanza. Il danno per colpo è rappresentato da questo grafico:

danno per colpo grafico

  • Il laser di un robot ha origine a metà strada tra il centro del robot e il suo bordo. Pertanto, impilare i tuoi robot si tradurrà in fuoco amico.
  • I robot nemici generano circa 1440du di distanza.
  • Il gioco termina se passano 120 tick (10 secondi) senza subire danni.
  • Il vincitore è il giocatore con il maggior numero di robot, quindi la maggior parte della salute al termine del gioco.

Comprensione dell'immagine renderizzata

  • Il giocatore 1 è rappresentato da cerchi e il giocatore 2 da esagoni.
  • Il colore di un bot rappresenta la sua allocazione di potenza. Più rosso significa che più potenza è stata assegnata al fuoco. Più blu significa più scudo. Più verde significa più movimento.
  • Il "buco" nel corpo di un robot rappresenta un danno. Più grande è il buco, maggiore è il danno subito.
  • I cerchi bianchi che circondano un robot sono lo scudo. Se un bot ha uno scudo alla fine del turno, viene mostrato. Se lo scudo è stato fatto esplodere subendo danni, non viene mostrato.
  • Le linee rosse tra i robot rappresentano gli scatti effettuati.
  • Quando un robot viene ucciso, viene mostrata una grande "esplosione" rossa.

I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Dennis,

Risposte:


4

Extremist (Python 3)

Questo robot dedicherà sempre tutto il suo potere a una cosa: proteggere se non è protetto, muoversi se è fuori posizione e sparare altrimenti. Batte tutti i robot campione tranne il piatto della morte.

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

Non ho familiarità con Python, ma sembrano esserci più problemi con la tua richiesta: 1) le righe 212 120 non sono rientrate correttamente e 2) target_hp non è definito. Potrei risolvere il problema (1) ma (2) mi impedisce di eseguire la tua richiesta. Ma potrebbe essere la mia mancanza di esperienza con Python.
Moogie,

Quell'intera istruzione if era rimasta da alcuni debug e non è affatto necessaria
pppery

2

Meno spericolato ( vai )

go run main.go

Inizialmente, avevo pianificato di modificare leggermente il programma di esempio Reckless Abandon in modo che i robot non si attivassero se un robot amico fosse in mezzo. Ho finito con i robot che scelgono nuovi obiettivi quando un amico è in mezzo, il che credo sia meglio. Batterà i primi due programmi.

Il codice non è perfetto. La logica per determinare se un tiro è chiaro utilizza alcune congetture piuttosto casuali.

Non sembra esserci un meccanismo per colpire "nessuno". Potrebbe essere una buona caratteristica da aggiungere.

L'API TCP è interessante in quanto qualsiasi lingua può giocare, ma significa anche molto codice boilerplate. Se non avessi familiarità con la lingua in cui erano stati scritti i robot campione, probabilmente non sarei stato motivato a giocarci. Una raccolta di campioni di piastre in varie lingue sarebbe una grande aggiunta agli altri repository git.

(la maggior parte del codice seguente è copia / incolla da uno dei bot di esempio)

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

Questo programma supera la mia sottomissione estremista?
pepery

No @ppperry, non lo fa. È un foraggio da cannone, ma sto lavorando a un secondo robot.
Naribe,

2

Trigger Happy - Java 8

Trigger Happy è una semplice evoluzione del mio bot Bombard originale, ma non più praticabile. È un robot molto semplice che sparerà semplicemente sul nemico attualmente bersaglio se c'è un tiro chiaro, altrimenti esegue una camminata casuale per cercare di ottenere una posizione migliore. Sempre cercando di avere uno scudo.

Tuttavia, per tutta la sua semplicità è molto efficace. E distruggerà prontamente i robot campione.

Nota, ci sono più bug con il bot come a volte sparerà anche quando non è un tiro chiaro e potrebbe non mantenere uno scudo ... ma è ancora efficace quindi invierà questa voce così com'è

piatto della morte contro grilletto felice

Death-dish vs Trigger Happy

Codice come segue:

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

Per compilare: javac TriggerHappy.java

Per eseguire: java TriggerHappy

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.