Scrappers v0.1: Programmeurs mercenaires

22

Dans un monde désolé et déchiré par la guerre, où les villes ont été envahies par des voyous et des voleurs, la civilisation s'est réinventée sous la forme de petites coopératives industrielles isolées, disséminées dans le paysage auparavant inhabité. L'existence de ces communautés dépend des équipes d'ouvriers mercenaires appelés "scrappers", qui recherchent sur le territoire sauvage des matériaux précieux à vendre aux coopératives. Comme ces matériaux se sont raréfiés, la mise au rebut est devenue une profession de plus en plus difficile et dangereuse. Les travailleurs humains fragiles ont été pour la plupart remplacés par des remplaçants robotiques à distance, appelés «bots», et un mercenaire typique est plus susceptible d'être un programmeur qualifié qu'un soudeur armé. Comme la présence humaine dans la démolition a diminué, le respect entre les groupes de mercenaires les uns pour les autres a également diminué. Les bots sont équipés non seulement pour collecter la ferraille, mais pour la défendre et, dans certains cas, la prendre de force. Les programmeurs de robots travaillent sans relâche à concevoir de nouvelles stratégies pour déjouer les scrappers rivaux, ce qui entraîne des bots de plus en plus agressifs et un autre danger pour les humains qui s'aventurent hors des murs de leurs communautés.

Exemple de jeu Scrappers

(ouais, le logo est recadré hilarante)

Bienvenue chez Scrappers!

Il s'agit d'une première version de Scrappers dans laquelle la collecte des rebuts et les usines n'ont pas été implémentées. Il s'agit essentiellement d'un "shoot 'em up".

Vous êtes un programmeur mercenaire chargé de créer un programme pour mener à distance vos robots vers la victoire sur des groupes de scrapper rivaux. Vos robots sont des machines en forme d'araignée composées de générateurs de puissance et de boucliers en leur cœur, entourés de nombreux appendices équipés d'outils de préhension, de coupe et d'attaque. Le générateur de puissance est capable de produire 12 unités de puissance (pu) par tick (unité de temps d'un scrapper). Vous contrôlez la façon dont ce pouvoir est réparti entre les trois besoins principaux d'un bot: le mouvement, les boucliers et la puissance de feu.

Les robots Scrapper sont des machines exceptionnellement agiles et peuvent facilement se déplacer sur, sous et autour des obstacles qu'ils rencontrent. Ainsi, la collision n'est pas quelque chose que votre programme doit prendre en considération. Vous êtes libre d'allouer tout, une partie ou aucune des 12 unités disponibles à votre bot pour le mouvement, tant que vous traitez en nombres entiers. Allouer 0pu aux fonctions de mouvement d'un bot le rendrait immobile. Allouer 2pu permettrait à un bot de déplacer 2 unités de distance (du) par tick, 5pu donnerait 5du / tick, 11pu donnerait 11du / tick, et ainsi de suite.

Les générateurs de bouclier de vos bots projettent une bulle d'énergie de déviation autour de leur corps. Un bouclier peut dévier jusqu'à 1 dégât avant d'éclater, laissant ainsi votre robot exposé jusqu'à ce que son générateur de bouclier génère suffisamment de puissance pour remettre le bouclier en place. Vous êtes libre d'allouer tout, une partie ou aucune des 12 unités disponibles à votre bot vers son bouclier. Allouer 0pu au bouclier d'un bot signifie qu'il ne générera jamais de bouclier. L'allocation de 2pu permettrait à un bot de générer un nouveau bouclier 2 sur 12 ticks, ou une fois toutes les 6 ticks. 5pu entraînerait la régénération du bouclier 5 fois sur 12 ticks, et ainsi de suite.

En accumulant une charge dans leurs lasers de soudage, vos robots peuvent tirer des faisceaux dommageables sur de courtes distances avec une assez bonne précision. Tout comme la génération de boucliers, la cadence de tir de vos bots dépend de la puissance allouée à leurs lasers. Allouer 0pu aux lasers d'un bot signifie qu'il ne se déclenchera jamais. Allouer 2pu permettrait à un bot de tirer 2 ticks sur 12, et ainsi de suite. Le laser d'un bot se déplacera jusqu'à ce qu'il rencontre un objet ou se disperse dans l'inutilité, alors faites attention au feu ami. Bien que vos robots soient assez précis, ils ne sont pas parfaits. Vous devez vous attendre à +/- 2,5 degrés de variance de précision. À mesure que le faisceau laser se déplace, ses particules sont progressivement déviées par l'atmosphère jusqu'à ce que le faisceau devienne effectivement inoffensif à une distance suffisante. Un laser inflige 1 dégât à bout portant, et 2,5% de dégâts en moins à chaque longueur de bot qu'il parcourt.

Les robots Scrapper sont suffisamment autonomes pour gérer les fonctions de base, mais comptent sur vous, leur programmeur, pour les rendre utiles en tant que groupe. En tant que programmeur, vous pouvez émettre les commandes suivantes pour chaque bot individuel:

  • MOVE: Spécifiez les coordonnées vers lesquelles un bot se déplacera.
  • CIBLE: Identifiez un bot à viser et à tirer lorsque l'allocation de puissance le permet.
  • PUISSANCE: redistribuez la puissance entre le mouvement, les boucliers et la puissance de feu.

Détails techniques du jeu

Il y a trois programmes que vous devrez connaître. Le Game Engine est le lève-personne lourd et fournit une API TCP à laquelle les programmes des joueurs se connectent. Le programme du lecteur est ce que vous allez écrire, et j'ai fourni quelques exemples de binaires ici . Enfin, le Renderer traite la sortie du Game Engine pour produire un GIF de la bataille.

Le moteur de jeu

Vous pouvez télécharger le moteur de jeu ici . Lorsque le jeu sera lancé, il commencera à écouter sur le port 50000 (actuellement non configurable) les connexions des joueurs. Une fois qu'il reçoit deux connexions de joueurs, il envoie le message PRET aux joueurs et commence la partie. Les programmes des joueurs envoient des commandes au jeu via l'API TCP. Une fois le jeu terminé, un fichier JSON nommé scrappers.json (également non configurable actuellement) est créé. C'est ce que le moteur de rendu utilise pour créer un GIF du jeu.

L'API TCP

Les programmes des joueurs et le moteur de jeu communiquent en passant les chaînes JSON terminées par la nouvelle ligne en arrière et en quatrième sur une connexion TCP. Il n'y a que cinq messages JSON différents qui peuvent être envoyés ou reçus.

Message prêt

Le message PRET est envoyé du jeu aux programmes des joueurs et n'est envoyé qu'une seule fois. Ce message indique au programme du joueur quel est son ID de joueur (PID) et fournit une liste de tous les bots du jeu. Le PID est le seul moyen de déterminer quels robots sont amis contre ennemis. Plus d'informations sur les champs de bots ci-dessous.

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

Message du robot

Le message BOT est envoyé du jeu aux programmes des joueurs et est envoyé lorsque les attributs d'un bot changent. Par exemple, lorsque des boucliers sont projetés ou que la santé change, un message BOT est envoyé. Le Bot ID (BID) n'est unique qu'au sein d'un joueur particulier.

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

Déplacer le message

Le message MOVE est une commande du programme du joueur au jeu (mais pensez-y comme une commande à un bot). Identifiez simplement le bot que vous souhaitez déplacer et les coordonnées. On suppose que vous commandez votre propre bot, donc aucun PID n'est nécessaire.

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

Message cible

Le message TARGET indique à l'un de vos robots de cibler un autre robot.

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

Message de puissance

Le message POWER réalloue les 12pu disponibles pour votre bot entre le mouvement, la puissance de feu et les boucliers.

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

La compétition

Si vous êtes assez courageux pour explorer les terres sauvages, vous participerez à un tournoi à double élimination contre vos pairs mercenaires. Veuillez créer une réponse pour votre soumission et coller votre code ou fournir un lien vers un dépôt git, un gist, etc. Toute langue est acceptable, mais vous devez supposer que je ne sais rien de la langue et inclure des instructions pour exécuter votre programme. Créez autant de soumissions que vous le souhaitez et assurez-vous de leur donner des noms!

Les exemples de programmes de joueurs seront inclus dans le tournoi, donc je recommande fortement de tester votre bot contre eux. Le tournoi commencera environ deux semaines après que nous aurons reçu quatre soumissions de programme uniques. Bonne chance!

--- Winner's Bracket ---

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

Autres informations importantes

  • Le jeu fonctionne à 12 ticks / seconde, vous ne recevrez donc pas de messages plus fréquemment que toutes les 83 millisecondes environ.
  • Chaque bot a un diamètre de 60du. Le bouclier ne prend pas d'espace supplémentaire. Avec une précision de +/- 2,5%, les chances de frapper un bot à une certaine distance sont représentées par ce graphique:

graphique de précision

  • La décroissance des dommages du laser sur la distance est représentée par ce graphique:

graphique de dégradation des dommages

  • La précision d'un robot et la décroissance laser se combinent pour calculer ses dégâts moyens par tir. Autrement dit, les dégâts moyens qu'un bot causera lorsqu'il tirera à partir d'une certaine distance. Les dégâts par tir sont représentés par ce graphique:

graphique des dégâts par tir

  • Le laser d'un bot prend naissance à mi-chemin entre le centre du bot et son bord. Ainsi, l'empilement de vos robots se traduira par un tir ami.
  • Les bots ennemis apparaissent à environ 1440du de distance.
  • Le jeu se termine si 120 ticks (10 secondes) passent sans aucun dommage.
  • Le gagnant est le joueur avec le plus de bots, puis le plus de santé à la fin du jeu.

Comprendre l'image rendue

  • Le joueur 1 est représenté par des cercles et le joueur 2 par des hexagones.
  • La couleur d'un bot représente son allocation de puissance. Plus de rouge signifie que plus de puissance a été allouée au tir. Plus de bleu signifie plus de bouclier. Plus de vert signifie plus de mouvement.
  • Le "trou" dans le corps d'un bot représente des dégâts. Plus le trou est grand, plus les dégâts sont importants.
  • Les cercles blancs entourant un bot sont son bouclier. Si un bot a un bouclier à la fin du tour, il s'affiche. Si le bouclier a éclaté en subissant des dégâts, il n'est pas affiché.
  • Les lignes rouges entre les robots représentent les photos prises.
  • Lorsqu'un bot est tué, une grande "explosion" rouge s'affiche.
Rip Leeb
la source
Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Dennis

Réponses:

4

Extrémiste (Python 3)

Ce bot consacrera toujours tout son pouvoir à une chose: protéger s'il n'est pas protégé, se déplacer s'il n'est pas en position et tirer autrement. Bat tous les bots de l'échantillon à l'exception de Death-Dish.

import socket, sys, json
from types import SimpleNamespace
s=socket.socket()
s.connect(("localhost",50000))
f=s.makefile()
bots={1:{},2:{}}
def hook(obj):
    if "BID" in obj:
        try:
            bot = bots[obj["PID"]][obj["BID"]]
        except KeyError:
            bot = SimpleNamespace(**obj)
            bots[bot.PID][bot.BID] = bot
        else:
            bot.__dict__.update(obj)
        return bot
    return SimpleNamespace(**obj)
decoder = json.JSONDecoder(object_hook=hook)
PID = decoder.decode(f.readline()).PID
#side effect: .decode fills bots dictionary
def turtle(bot):
    send({"Cmd":"POWER","BID":bot.BID,"FPow":0,"MPow":0,"SPow":12})
    bot.firing = bot.moving = False
def send(msg):
    s.send(json.dumps(msg).encode("ascii")+b"\n")
for bot in bots[PID].values():
    turtle(bot)
target_bot = None
def calc_target_bot():
    ok_bots = []
    for bot2 in bots[(2-PID)+1].values():
        if bot2.Health < 12:
            ok_bots.append(bot2)
    best_bot = (None,2147483647)
    for bot2 in (ok_bots or bots[(2-PID)+1].values()):
        dist = bot_dist(bot, bot2)
        if dist < best_bot[1]:
            best_bot = bot2, dist
    return best_bot[0]
def bot_dist(bot, bot2):
    if isinstance(bot, tuple):
        bot=SimpleNamespace(X=bot[0],Y=bot[1])
    if isinstance(bot2, tuple):
        bot=SimpleNamespace(X=bot[0],Y=bot[1])
    distx = bot2.X - bot.X
    disty = bot2.Y - bot.Y
    return (distx**2+disty**2)**.5
LENGTH_Y = -80
LENGTH_X = 80
line = None
def move(bot, pos):
    bot.firing = False
    bot.moving = True
    send({"Cmd":"POWER","BID":bot.BID,"FPow":0,"MPow":12,"SPow":0})
    send({"Cmd":"MOVE","BID": bot.BID,"X":pos[0],"Y":pos[1]})
def go(bot, line):
    if line != None:
        position = (line[0]+LENGTH_X*(bot.BID-6),line[1]+LENGTH_Y*(bot.BID-6))
        if not close_pos(bot, position, 1.5):
            return True, position
    return False, None
def close_pos(bot, pos, f):
    if abs(bot.X - pos[0]) <= abs(LENGTH_X*f) or \
        abs(bot.Y - pos[1]) <= abs(LENGTH_Y*f):
        return True
def set_line(bot):
    global line
    newline = bot.X - LENGTH_X*(bot.BID - 6), bot.Y - LENGTH_Y*(bot.BID - 6)
    if line == None or bot_dist(line, target_bot) < (bot_dist(newline, target_bot) - 100):
        line = newline
def move_or_fire(bot):
    global target_bot, line
    if not target_bot:
        target_bot = calc_target_bot()
    followline, place = go(bot, line)
    if not target_bot:
        #Game should be over, but just in case ...
        return turtle(bot)
    elif bot_dist(bot, target_bot) > 2000:
        line = None
        position = (target_bot.X, target_bot.Y)
        position = (position[0]+LENGTH_X*(bot.BID-6),position[1]+LENGTH_Y*(bot.BID-6))
        move(bot, position)
    elif followline:
        set_line(bot)
        move(bot, place)
    elif any(close_pos(bot, (bot2.X, bot2.Y), .6) for bot2 in bots[PID].values() if bot != bot2):
        try:
            move(bot, place)
        except TypeError:
            turtle(bot)
        set_line(bot)
        #Let the conflicting bots resolve
    else:
        set_line(bot)
        bot.firing = True
        bot.moving = False
        send({"Cmd":"POWER","BID":bot.BID,"FPow":12,"MPow":0,"SPow":0})
        send({"Cmd":"TARGET","BID": bot.BID,
              "TPID":target_bot.PID,"TBID":target_bot.BID})
def dead(bot):
    del bots[bot.PID][bot.BID]
def parse_message():
    global target_bot
    line = f.readline()
    if not line:
        return False
    bot = decoder.decode(line)
    assert bot.Type == "BOT"
    del bot.Type
    if bot.PID == PID:
        if bot.Health <= 0:
            dead(bot)
        elif not bot.Shield:
            turtle(bot)
        else:
            move_or_fire(bot)
    elif target_bot and (bot.BID == target_bot.BID):
        target_bot = bot
        if target_bot.Health <= 0:
            target_bot = None
            dead(bot)
            for bot in bots[PID].values():
                if bot.firing or bot.moving:
                    move_or_fire(bot)
    elif bot.Health <= 0:
        dead(bot)
    assert bot.Health > 0 or bot.BID not in bots[bot.PID]
    return True
while parse_message():
    pass
pppery
la source
Je ne suis pas familier avec python, mais il semble qu'il y ait plusieurs problèmes avec votre soumission: 1) les lignes 212 120 ne sont pas en retrait correctement et 2) target_hp n'est pas défini. Je pourrais corriger (1) mais (2) m'empêche d'exécuter votre soumission. Mais cela pourrait être mon manque d'expérience avec python.
Moogie
Cette instruction if entière a été laissée par un débogage et n'est pas du tout nécessaire
pppery
2

Moins téméraire ( aller )

go run main.go

À l'origine, je prévoyais de modifier légèrement le programme d'échantillonnage Abandon téméraire afin que les robots ne se déclenchent pas si un robot ami était sur le chemin. Je me suis retrouvé avec des robots qui choisissent de nouvelles cibles lorsqu'un ami est sur le chemin, ce qui, je suppose, est mieux. Ça va battre les deux premiers programmes.

Le code n'est pas parfait. La logique pour déterminer si un tir est clair utilise des suppositions assez aléatoires.

Il ne semble pas y avoir de mécanisme pour cibler "personne". Cela pourrait être une bonne fonctionnalité à ajouter.

L'API TCP est agréable dans la mesure où n'importe quel langage peut jouer, mais cela signifie également beaucoup de code standard. Si je ne connaissais pas la langue dans laquelle les exemples de robots étaient écrits, je n'aurais probablement pas été motivé à jouer avec ça. Une collection d'échantillons passe-partout dans différentes langues serait un excellent ajout à vos autres dépôts git.

(la plupart du code suivant est un copier / coller de l'un des exemples de robots)

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
la source
Ce programme bat-il ma soumission extrémiste?
pppery
Non @ppperry, ce n'est pas le cas. C'est de la chair à canon, mais je travaille sur un deuxième bot.
Naribe
2

Trigger Happy - Java 8

Trigger Happy est une simple évolution de mon robot Bombard d'origine, mais qui n'est plus viable. C'est un bot très simple qui tirera simplement sur l'ennemi actuellement ciblé s'il y a un tir clair, sinon effectue une marche aléatoire pour essayer de se mettre dans une meilleure position. Tout le temps essayant d'avoir un bouclier.

Cependant, malgré toute sa simplicité, il est très efficace. Et détruira facilement les robots d'échantillonnage.

Remarque, il existe plusieurs bogues avec le bot, qui se déclenchent parfois même si ce n'est pas un tir clair et peuvent ne pas maintenir de bouclier ... mais il est toujours efficace, donc soumettra cette entrée telle quelle

plat de la mort vs déclencheur heureux

Death-dish vs Trigger Happy

Code comme suit:

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

Pour compiler: javac TriggerHappy.java

Pour exécuter: java TriggerHappy

Moogie
la source