Scrappers v0.1: programadores mercenários

22

Em um mundo desolado e devastado pela guerra, onde as cidades foram invadidas por bandidos e ladrões, a civilização se reinventou na forma de pequenas cooperativas industriais isoladas, espalhadas pela paisagem anteriormente desabitada. A existência dessas comunidades depende de equipes de trabalhadores mercenários chamados "raspadores", que procuram no território indomável materiais valiosos para vender às cooperativas. À medida que esses materiais se tornam mais escassos, a demolição se tornou uma profissão cada vez mais difícil e perigosa. Trabalhadores humanos frágeis foram substituídos principalmente por suportes robóticos remotos, chamados "bots", e um mercenário típico tem mais chances de ser um programador qualificado do que um soldador armado. Como a presença humana na demolição diminuiu, o mesmo aconteceu com o respeito entre os grupos mercenários. Os bots estão equipados não apenas para coletar sucata, mas para defendê-la e, em alguns casos, pegá-la à força. Os programadores de bot trabalham incansavelmente desenvolvendo novas estratégias para ser mais espertas que os scrappers rivais, resultando em bots cada vez mais agressivos e mais um perigo para os humanos que se aventuram fora dos muros de suas comunidades.

Amostra de jogo Scrappers

(sim, o logotipo é cortado hilário)

Bem-vindo ao Scrappers!

Esta é uma versão inicial do Scrappers, na qual a coleta e as fábricas de sucata não foram implementadas. É basicamente um "tiro neles".

Você é um programador mercenário encarregado de criar um programa para conduzir remotamente seus bots à vitória sobre grupos de scrappers rivais. Seus robôs são máquinas semelhantes a aranhas, que consistem em geradores de energia e blindagem em seu núcleo, cercados por muitos apêndices equipados com instrumentos de preensão, corte e ataque. O gerador de energia é capaz de produzir 12 unidades de energia (pu) por tick (unidade de tempo de um raspador). Você está no controle de como esse poder é distribuído entre as três principais necessidades de um bot: movimento, escudos e poder de fogo.

Os robôs scrapper são máquinas excepcionalmente ágeis e podem se mover facilmente, embaixo e em torno de todos os obstáculos que encontrarem. Portanto, colisão não é algo que seu programa precise levar em consideração. Você é livre para alocar todos, alguns ou nenhum dos 12pu disponíveis para o seu bot para movimento, desde que lide com números inteiros. Alocar 0pu às funções de movimento de um bot o tornaria imóvel. Alocar 2pu permitiria que um bot movesse 2 unidades de distância (du) por tick, 5pu resultaria em 5du / tick, 11pu resultaria em 11du / tick e assim por diante.

Os geradores de escudos de seus robôs projetam uma bolha de energia defletora em torno do corpo. Um escudo pode desviar até 1 de dano antes de aparecer, deixando seu bot exposto até que o gerador de escudo construa energia suficiente para encaixar o escudo de volta no lugar. Você é livre para alocar todos, alguns ou nenhum dos 12pu disponíveis para o seu bot em seu escudo. Alocar 0pu ao escudo de um bot significa que ele nunca irá gerar um escudo. Alocar 2pu permitiria a um bot gerar um novo escudo 2 de 12 ticks, ou uma vez a cada 6 ticks. 5pu resultaria na regeneração do escudo 5 em cada 12 ticks, e assim por diante.

Ao acumular uma carga em seus lasers de solda, seus robôs podem disparar vigas prejudiciais por curtas distâncias com precisão razoável. Como a geração de escudos, a taxa de tiro dos seus bots depende da energia alocada aos seus lasers. Alocar 0pu aos lasers de um bot significa que ele nunca será acionado. Alocar 2pu permitiria que um bot disparasse 2 em cada 12 ticks, e assim por diante. O laser de um bot viajará até encontrar um objeto ou se dispersar na inutilidade, portanto, esteja atento ao fogo amigo. Embora seus bots sejam bastante precisos, eles não são perfeitos. Você deve esperar +/- 2,5 graus de variação na precisão. À medida que o raio laser viaja, suas partículas são gradualmente desviadas pela atmosfera até que o raio se torne efetivamente inofensivo a uma distância suficiente. Um laser causa 1 ponto de dano à queima-roupa e 2,5% menos dano a cada comprimento de bot que viaja.

Os robôs de raspador são autônomos o suficiente para lidar com funções básicas, mas confiam em você, seu programador, para torná-los úteis como um grupo. Como programador, você pode emitir os seguintes comandos para cada bot individual:

  • MOVE: Especifique as coordenadas para as quais um bot se moverá.
  • ALVO: Identifique um bot para mirar e disparar quando a alocação de energia permitir.
  • PODER: Redistribua o poder entre movimento, escudos e poder de fogo.

Detalhes técnicos do jogo

Existem três programas com os quais você precisará se familiarizar. O Game Engine é o levantador pesado e fornece uma API TCP à qual os programas do player se conectam. O programa do player é o que você escreverá, e eu forneci alguns exemplos com binários aqui . Finalmente, o renderizador processa a saída do Game Engine para produzir um GIF da batalha.

O mecanismo de jogo

Você pode baixar o mecanismo de jogo aqui . Quando o jogo é iniciado, ele começa a escutar na porta 50000 (atualmente não configurável) as conexões dos jogadores. Depois de receber duas conexões de jogadores, ele envia a mensagem PRONTA para os jogadores e inicia o jogo. Os programas do player enviam comandos para o jogo por meio da API TCP. Quando o jogo termina, um arquivo JSON chamado scrappers.json (também não está configurável no momento) é criado. É isso que o renderizador usa para criar um GIF do jogo.

A API TCP

Os programas dos jogadores e o mecanismo de jogo se comunicam passando as seqüências JSON terminadas em nova linha para trás e para quarta através de uma conexão TCP. Existem apenas cinco mensagens JSON diferentes que podem ser enviadas ou recebidas.

Mensagem pronta

A mensagem PRONTA é enviada do jogo para os programas do jogador e é enviada apenas uma vez. Esta mensagem informa ao programa do jogador qual é o seu ID de jogador (PID) e fornece uma lista de todos os bots no jogo. O PID é a única maneira de determinar quais Bots são amigáveis ​​contra inimigos. Mais informações nos campos de bot abaixo.

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

Bot Message

A mensagem BOT é enviada do jogo para os programas dos jogadores e é enviada quando os atributos de um bot são alterados. Por exemplo, quando os escudos são projetados ou a saúde muda, uma mensagem BOT é enviada. O Bot ID (BID) é único apenas dentro de um determinado jogador.

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

A mensagem MOVE é um comando do programa do jogador para o jogo (mas pense nisso como um comando para um bot). Basta identificar o bot que você deseja mover e as coordenadas. Supõe-se que você esteja comandando seu próprio bot, portanto, nenhum PID é necessário.

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

Mensagem de destino

A mensagem TARGET diz a um de seus bots para segmentar outro 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
}

Mensagem de energia

A mensagem POWER realoca o 12pu disponível para o seu bot entre movimento, poder de fogo e escudos.

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

A competição

Se você for corajoso o suficiente para explorar as terras indomáveis, participará de um torneio de dupla eliminação contra seus colegas mercenários. Por favor, crie uma resposta para o seu envio e cole seu código ou forneça um link para um repositório git, essência etc. Qualquer idioma é aceitável, mas você deve assumir que eu não sei nada sobre o idioma e incluir instruções para executar seu programa. Crie quantos envios quiser e não se esqueça de dar nomes a eles!

Os programas de amostra de jogadores serão incluídos no torneio, então eu recomendo testar seu bot contra eles. O torneio começará aproximadamente duas semanas após recebermos quatro envios de programas exclusivos. Boa sorte!

--- Winner's Bracket ---

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

Outras informações importantes

  • O jogo é executado a 12 ticks / segundo, para que você não receba mensagens com mais frequência do que a cada 83 milissegundos.
  • Cada bot tem 60du de diâmetro. O escudo não ocupa espaço adicional. Com uma precisão de +/- 2,5%, as chances de atingir um bot a uma certa distância são representadas por este gráfico:

gráfico de precisão

  • A deterioração dos danos do laser ao longo da distância é representada por este gráfico:

gráfico de deterioração de danos

  • A precisão de um bot e a deterioração do laser se combinam para calcular o dano médio por tiro. Ou seja, o dano médio que um bot causa quando dispara a uma certa distância. O dano por tiro é representado por este gráfico:

gráfico de dano por tiro

  • O laser de um bot se origina a meio caminho entre o centro do bot e sua borda. Assim, empilhar seus bots resultará em fogo amigável.
  • Os robôs inimigos aparecem aproximadamente 1440du separados.
  • O jogo termina se 120 ticks (10 segundos) passarem sem causar nenhum dano.
  • O vencedor é o jogador com mais bots e mais saúde quando o jogo termina.

Compreendendo a imagem renderizada

  • O jogador 1 é representado por círculos e o jogador 2 por hexágonos.
  • A cor de um bot representa sua alocação de energia. Mais vermelho significa que mais energia foi alocada para o disparo. Mais azul significa mais escudo. Mais verde significa mais movimento.
  • O "buraco" no corpo de um bot representa dano. Quanto maior o buraco, mais danos serão causados.
  • Os círculos brancos em torno de um bot são seu escudo. Se um bot tiver um escudo no final do turno, ele será mostrado. Se o escudo foi estourado ao sofrer danos, ele não é mostrado.
  • As linhas vermelhas entre os robôs representam as fotos tiradas.
  • Quando um bot é morto, uma grande "explosão" vermelha é mostrada.
Rip Leeb
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Dennis19 /

Respostas:

4

Extremista (Python 3)

Esse bot sempre dedica todo o seu poder a uma coisa: proteger se não estiver protegido, mover-se se estiver fora de posição e disparar de outra forma. Supera todos os robôs de amostra, exceto o prato da 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
pppery
fonte
Não estou familiarizado com python, mas parece haver vários problemas com sua submissão: 1) as linhas 212 120 não estão recuadas corretamente e 2) target_hp não está definido. Eu poderia corrigir (1) mas (2) está me impedindo de executar sua inscrição. Mas poderia ser minha falta de experiência com python.
Moogie
Que toda declaração se era sobra de alguma depuração e não é necessário em tudo
pppery
2

Menos imprudente ( Go )

go run main.go

Originalmente, planejava modificar levemente o programa de amostra Reckless Abandon para que os bots não disparassem se um bot amigável estivesse no caminho. Acabei com bots que escolhem novos alvos quando um amigo está no caminho, o que acho melhor. Vai vencer os dois primeiros programas.

O código não é perfeito. A lógica para determinar se um tiro é claro usa algumas suposições bastante aleatórias.

Não parece haver um mecanismo para atingir "ninguém". Esse pode ser um bom recurso para adicionar.

A API TCP é legal, pois qualquer idioma pode ser reproduzido, mas também significa muito código padrão. Se eu não estivesse familiarizado com o idioma em que os bots de amostra foram escritos, provavelmente não teria me motivado a brincar com isso. Uma coleção de amostras padrão em vários idiomas seria um ótimo complemento para seus outros repositórios git.

(a maioria do código a seguir é copiar / colar de um dos bots de amostra)

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
fonte
Esse programa supera minha submissão extremista?
pppery
Não @ppperry, não. É forragem de canhão, mas estou trabalhando em um segundo bot.
Naribe
2

Trigger Happy - Java 8

Trigger Happy é uma evolução simples do meu bot Bombard original, mas não mais viável. É um bot muito simples que simplesmente dispara sobre o inimigo atualmente alvejado se houver um tiro certeiro, caso contrário, realiza uma caminhada aleatória para tentar chegar a uma posição melhor. O tempo todo tentando ter um escudo.

No entanto, por toda a sua simplicidade, é muito eficaz. E destruirá prontamente os robôs de amostra.

Observe que existem vários bugs no bot, que às vezes disparam mesmo quando não são um tiro certeiro e podem não manter um escudo ... mas ainda é eficaz, basta enviar esta entrada como está

prato da morte vs gatilho feliz

Death-dish vs Trigger Happy

Código da seguinte maneira:

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 executar: java TriggerHappy

Moogie
fonte