Scrappers v0.1: programadores mercenarios

22

En un mundo desolado y devastado por la guerra, donde las ciudades han sido invadidas por matones y ladrones, la civilización se ha reinventado en forma de pequeñas cooperativas industriales aisladas, dispersas por todo el paisaje previamente deshabitado. La existencia de estas comunidades depende de equipos de mercenarios llamados "scrappers", que buscan en el territorio indómito materiales valiosos para vender a las cooperativas. A medida que estos materiales se han vuelto más escasos, el desguace se ha convertido en una profesión cada vez más difícil y peligrosa. Los trabajadores humanos frágiles han sido reemplazados en su mayoría por sustitutos robóticos remotos, llamados "bots", y es más probable que un mercenario típico sea un programador experto que un soldador armado. A medida que la presencia humana en el desguace ha disminuido, también lo ha hecho el respeto entre los grupos mercenarios entre sí. Los bots están equipados no solo para recoger chatarra, sino para defenderla y, en algunos casos, tomarla por la fuerza. Los programadores de bots trabajan incansablemente ideando nuevas estrategias para burlar a los scrappers rivales, lo que resulta en bots cada vez más agresivos y otro peligro para los humanos que se aventuran fuera de los muros de sus comunidades.

Muestra de juego de Scrappers

(sí, el logotipo se recorta de forma divertida)

Bienvenido a Scrappers!

Esta es una versión temprana de Scrappers en la que la recolección de chatarra y las fábricas no se han implementado. Básicamente es un "dispara"

Eres un programador mercenario encargado de crear un programa para conducir de forma remota a tus bots a la victoria sobre los grupos rivales de scrapper. Sus robots son máquinas en forma de araña que consisten en generadores de energía y escudo en su núcleo, rodeados de muchos apéndices equipados con implementos de agarre, corte y ataque. El generador de energía es capaz de producir 12 unidades de energía (pu) por tic (unidad de tiempo de un scrapper). Usted tiene el control de cómo se distribuye este poder entre las tres necesidades principales de un bot: movimiento, escudos y potencia de fuego.

Los bots Scrapper son máquinas excepcionalmente ágiles, y pueden moverse fácilmente sobre, debajo y alrededor de cualquier obstáculo que encuentren. Por lo tanto, la colisión no es algo que su programa necesita tener en cuenta. Usted es libre de asignar todos, algunos o ninguno de los 12pu disponibles a su bot para su movimiento, siempre que trate con números enteros. Asignar 0pu a las funciones de movimiento de un bot lo dejaría inmóvil. La asignación de 2pu permitiría a un bot mover 2 unidades de distancia (du) por tick, 5pu resultaría en 5du / tick, 11pu resultaría en 11du / tick, y así sucesivamente.

Los generadores de escudo de tus robots proyectan una burbuja de energía deflectora alrededor de su cuerpo. Un escudo puede desviar hasta 1 daño antes de estallar, dejando así su bot expuesto hasta que su generador de escudo genere suficiente energía para volver a colocar el escudo en su lugar. Usted es libre de asignar todos, algunos o ninguno de los 12pu disponibles para su bot hacia su escudo. Asignar 0pu al escudo de un bot significa que nunca generará un escudo. Asignar 2pu permitiría a un bot generar un nuevo escudo 2 de 12 ticks, o una vez cada 6 ticks. 5pu resultaría en la regeneración del escudo 5 de cada 12 tics, y así sucesivamente.

Al acumular una carga en sus láseres de soldadura, sus robots pueden disparar haces dañinos en distancias cortas con bastante precisión. Al igual que la generación de escudos, la velocidad de disparo de tus bots depende de la potencia asignada a sus láseres. Asignar 0pu a los láseres de un bot significa que nunca disparará. Asignar 2pu permitiría que un bot dispare 2 de cada 12 tics, y así sucesivamente. El láser de un robot viajará hasta que encuentre un objeto o se disperse en la inutilidad, así que tenga en cuenta el fuego amigo. Aunque tus bots son bastante precisos, no son perfectos. Debe esperar +/- 2.5 grados de variación en la precisión. A medida que el rayo láser viaja, sus partículas son desviadas gradualmente por la atmósfera hasta que el rayo se vuelve efectivamente inofensivo con suficiente distancia. Un láser hace 1 daño a quemarropa y un 2.5% menos de daño cada longitud de bot que viaja.

Los bots Scrapper son lo suficientemente autónomos como para manejar funciones básicas, pero confían en usted, su programador, para que sean útiles como grupo. Como programador, puede emitir los siguientes comandos para cada bot individual:

  • MOVER: especifique las coordenadas hacia las cuales se moverá un bot.
  • OBJETIVO: Identifique un bot para apuntar y disparar cuando la asignación de poder lo permita.
  • PODER: Redistribuir el poder entre movimiento, escudos y potencia de fuego.

Detalles técnicos del juego

Hay tres programas con los que deberá familiarizarse. El motor de juego es el pesado elevador y proporciona una API de TCP que los programas de reproducción de conectarse. El programa del reproductor es lo que escribirás, y he proporcionado algunos ejemplos con binarios aquí . Finalmente, el Renderer procesa la salida del Game Engine para producir un GIF de la batalla.

El motor del juego

Puedes descargar el motor del juego desde aquí . Cuando se inicia el juego, comenzará a escuchar en el puerto 50000 (actualmente no configurable) para las conexiones de los jugadores. Una vez que recibe las conexiones de dos jugadores, envía el mensaje LISTO a los jugadores y comienza el juego. Los programas del jugador envían comandos al juego a través de la API TCP. Cuando termina el juego, se crea un archivo JSON llamado scrappers.json (también, actualmente no configurable). Esto es lo que usa el procesador para crear un GIF del juego.

La API TCP

Los programas de los jugadores y el motor del juego se comunican pasando cadenas JSON terminadas en nueva línea hacia atrás y cuarto a través de una conexión TCP. Hay solo cinco mensajes JSON diferentes que se pueden enviar o recibir.

Mensaje listo

El mensaje READY se envía desde el juego a los programas del jugador y solo se envía una vez. Este mensaje le dice al programa del jugador cuál es su ID de jugador (PID) y proporciona una lista de todos los bots en el juego. El PID es la única forma de determinar qué Bots son amigos contra enemigos. Más información en los campos de bot a continuación.

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

Mensaje de bot

El mensaje BOT se envía desde el juego a los programas del jugador y se envía cuando cambian los atributos de un bot. Por ejemplo, cuando se proyectan escudos, o la salud cambia, se envía un mensaje BOT. La ID del bot (BID) solo es única dentro de un jugador en particular.

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

Mover mensaje

El mensaje MOVE es un comando del programa del jugador al juego (pero considérelo como un comando para un bot). Simplemente identifique el bot que desea mover y las coordenadas. Se asume que estás al mando de tu propio bot, por lo que no es necesario ningún PID.

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

Mensaje de destino

El mensaje OBJETIVO le dice a uno de tus bots que apunte a otro 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
}

Mensaje de poder

El mensaje PODER reasigna la 12pu disponible para su bot entre movimiento, potencia de fuego y escudos.

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

La competencia

Si eres lo suficientemente valiente como para explorar las tierras indomables, entrarás en un torneo de doble eliminación contra tus compañeros mercenarios. Cree una respuesta para su envío y pegue su código o proporcione un enlace a un git repo, gist, etc. Cualquier idioma es aceptable, pero debe asumir que no sé nada sobre el idioma e incluir instrucciones para ejecutar su programa. ¡Crea tantas presentaciones como quieras y asegúrate de darles nombres!

Los programas de jugadores de muestra se incluirán en el torneo, por lo que recomiendo probar su bot contra ellos. El torneo comenzará aproximadamente dos semanas después de recibir cuatro presentaciones únicas del programa. ¡Buena suerte!

--- Winner's Bracket ---

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

Otra información importante

  • El juego se ejecuta a 12 tics / segundo, por lo que no recibirá mensajes con más frecuencia que cada 83 milisegundos más o menos.
  • Cada bot tiene 60du de diámetro. El escudo no ocupa espacio adicional. Con la precisión de +/- 2.5%, las probabilidades de golpear a un bot a cierta distancia se representan en este gráfico:

gráfico de precisión

  • La disminución del daño del láser a lo largo de la distancia se representa en este gráfico:

gráfico de deterioro de daños

  • La precisión de un bot y la desintegración del láser se combinan para calcular su daño promedio por disparo. Es decir, el daño promedio que causará un bot cuando dispare desde cierta distancia. El daño por disparo está representado por este gráfico:

gráfico de daño por disparo

  • El láser de un robot se origina a medio camino entre el centro del robot y su borde. Por lo tanto, apilar tus bots resultará en fuego amigo.
  • Los bots enemigos se generan aproximadamente a 1440du de distancia.
  • El juego termina si pasan 120 ticks (10 segundos) sin ningún daño infligido.
  • El ganador es el jugador con más bots, luego la mayor cantidad de salud cuando termina el juego.

Comprender la imagen renderizada

  • El jugador 1 está representado por círculos y el jugador 2 por hexágonos.
  • El color de un bot representa su asignación de potencia. Más rojo significa que se ha asignado más poder al disparo. Más azul significa más escudo. Más verde significa más movimiento.
  • El "agujero" en el cuerpo de un bot representa el daño. Cuanto más grande es el agujero, más daño se ha recibido.
  • Los círculos blancos que rodean a un bot son su escudo. Si un bot tiene un escudo al final del turno, se muestra. Si el escudo se rompió al recibir daño, no se muestra.
  • Las líneas rojas entre los robots representan tomas tomadas.
  • Cuando se mata a un bot, se muestra una gran "explosión" roja.
Rip Leeb
fuente
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Dennis

Respuestas:

4

Extremista (Python 3)

Este bot siempre dedicará todo su poder a una cosa: proteger si no está protegido, moverse si está fuera de posición y disparar de lo contrario. Vence a todos los bots de muestra, excepto el plato mortal.

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
pppery
fuente
No estoy familiarizado con Python, pero parece haber múltiples problemas con su envío: 1) las líneas 212 120 no están sangradas correctamente y 2) target_hp no está definido. Podría arreglar (1) pero (2) me impide ejecutar su envío. Pero podría ser mi falta de experiencia con Python.
Moogie
Toda esa declaración if fue sobrante de alguna depuración y no es necesaria en absoluto
pppery
2

Menos imprudente ( ir )

go run main.go

Originalmente, planeé modificar ligeramente el programa de muestra Reckless Abandon para que los bots no se dispararan si un bot amigable estuviera en el camino. Terminé con bots que eligen nuevos objetivos cuando hay un amigo en el camino, lo que creo que es mejor. Ganará a los dos primeros programas.

El código no es perfecto. La lógica para determinar si un disparo es claro utiliza algunas conjeturas bastante aleatorias.

No parece haber un mecanismo para apuntar a "nadie". Esa podría ser una buena característica para agregar.

La API TCP es agradable porque cualquier lenguaje puede reproducirse, pero también significa mucho código repetitivo. Si no estuviera familiarizado con el idioma en el que se escribieron los bots de muestra, probablemente no habría estado motivado para jugar con esto. Una colección de muestras repetitivas en varios idiomas sería una gran adición a sus otros repositorios git.

(la mayor parte del siguiente código es copiar / pegar de uno de los bots de muestra)

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
fuente
¿Este programa supera mi sumisión extremista?
pppery
No @ppperry, no lo hace. Es carne de cañón, pero estoy trabajando en un segundo bot.
Naribe
2

Trigger Happy - Java 8

Trigger Happy es una evolución simple de mi robot original, pero ya no viable, Bombard. Es un bot muy simple que simplemente disparará al enemigo objetivo actual si hay un disparo claro, de lo contrario realiza una caminata aleatoria para tratar de llegar a una mejor posición. Todo el tiempo intentando tener un escudo.

Sin embargo, a pesar de su simplicidad, es muy eficaz. Y destruirá fácilmente los bots de muestra.

Tenga en cuenta que hay varios errores con el bot, que a veces se dispararán incluso cuando no sea un tiro claro y es posible que no mantengan un escudo ... pero sigue siendo efectivo, por lo que solo enviará esta entrada tal como está

plato de muerte vs gatillo feliz

Death-dish vs Trigger Happy

Codifique de la siguiente manera:

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

Para compilar: javac TriggerHappy.java

Para ejecutar: java TriggerHappy

Moogie
fuente