Scrappers v0.1: программисты-наемники

22

В пустынном, разрушенном войной мире, где города были захвачены головорезами и ворами, цивилизация заново изобрела себя в виде небольших изолированных промышленных кооперативов, разбросанных по ранее необитаемому ландшафту. Существование этих сообществ зависит от групп наемных рабочих, называемых «скребками», которые ищут на необузданной территории ценные материалы для продажи в курятники. По мере того как эти материалы становятся все более скудными, утилизация становится все более сложной и опасной профессией. Хрупкие люди-работники в основном были заменены удаленными роботизированными помощниками, называемыми «ботами», и типичный наемник с большей вероятностью будет опытным программистом, чем вооруженным сварщиком. По мере того, как уменьшалось присутствие людей на слом, также росло и уважение между наемными группами друг к другу. Боты экипированы не только для сбора лома, но и для его защиты, а в некоторых случаях забирают его силой. Программисты ботов работают не покладая рук, разрабатывая новые стратегии, чтобы перехитрить конкурирующих скребков, что приводит к росту агрессивности ботов и еще одной опасности для людей, которые выходят за пределы своих сообществ.

Образец игры Scrappers

(да, логотип обрезается весело)

Добро пожаловать в Scrappers!

Это ранняя версия Scrappers, в которой сбор отходов и фабрики не были реализованы. Это в основном «стреляй».

Вы - наемный программист, которому поручено создать программу для удаленного ведения ваших ботов к победе над конкурирующими группами. Ваши боты - это паукообразные машины, состоящие из силовых и щитовых генераторов в своей основе, окруженные множеством придатков, оснащенных захватными, режущими и атакующими орудиями. Генератор энергии способен производить 12 единиц энергии (пу) за такт (единица времени скребка). Вы контролируете, как эта сила распределяется между тремя основными потребностями бота: движением, щитами и огневой мощью.

Скрепер-боты являются исключительно гибкими машинами и могут легко перемещаться, преодолевать любые препятствия, с которыми они сталкиваются. Таким образом, столкновение - это не то, что нужно учитывать вашей программе. Вы можете свободно распределять все, некоторые или ни одного из 12pu, доступных вашему боту для движения, если вы имеете дело с целыми числами. Назначение 0pu функциям движения бота сделает его неподвижным. Выделение 2pu позволит боту перемещаться на 2 единицы расстояния (du) за тик, 5pu - 5du / тик, 11pu - 11du / тик и так далее.

Генераторы щитов ваших ботов проецируют пузырь отклоняющей энергии вокруг их тела. Щит может отразить до 1 урона, прежде чем лопнуть, оставляя вашего бота незащищенным до тех пор, пока генератор щита не наберет достаточно энергии, чтобы вернуть его на место. Вы можете свободно распределить все, некоторые или ни одного из 12pu, доступных вашему боту, на его щит. Выделение 0pu на щит бота означает, что он никогда не сгенерирует щит. Выделение 2pu позволит боту генерировать новый щит 2 из 12 тиков или один раз каждые 6 тиков. 5pu приведет к регенерации щита 5 из каждых 12 тиков и так далее.

Накапливая заряд в своих сварочных лазерах, ваши боты могут стрелять повреждающими лучами на короткие расстояния с достаточной точностью. Подобно генерации щитов, скорость стрельбы ваших ботов зависит от мощности, выделяемой их лазерам. Выделение 0pu лазерам бота означает, что он никогда не сработает. Выделение 2pu позволит боту запустить 2 из каждых 12 тиков и так далее. Лазер бота будет путешествовать до тех пор, пока не натолкнется на предмет или не рассеется в бесполезности, поэтому помните о дружественном огне. Хотя ваши боты довольно точны, они не идеальны. Вы должны ожидать +/- 2,5 градуса отклонения в точности. По мере того, как лазерный луч перемещается, его частицы постепенно отклоняются атмосферой, пока луч не станет эффективно безвредным на достаточном расстоянии. Лазер наносит 1 урон в упор, и на 2,5% меньше урона на каждую длину бота.

Боты-скреперы достаточно автономны, чтобы справляться с базовыми функциями, но полагаются на вас, их программиста, чтобы они были полезны как группа. Как программист, вы можете выполнить следующие команды для каждого отдельного бота:

  • MOVE: укажите координаты, по которым бот будет двигаться.
  • ЦЕЛЬ: Определить бота, на которого можно нацелиться и выстрелить, когда позволяет распределение мощности.
  • СИЛА: перераспределить силу между движением, щитами и огневой мощью.

Технические детали игры

Есть три программы, с которыми вам нужно ознакомиться. Game Engine является тяжелым атлет и обеспечивает TCP API , что программы игрок подключения. Программа для плеера - это то, что вы напишите, и я привел здесь несколько примеров с бинарными файлами . Наконец, Renderer обрабатывает выходные данные из Game Engine для создания GIF битвы.

Игровой движок

Вы можете скачать игровой движок здесь . Когда игра запускается, она начинает прослушивать порт 50000 (в настоящее время не настраивается) для подключения игроков. Как только он получает соединения двух игроков, он отправляет сообщение ГОТОВ игрокам и начинает игру. Программы игрока посылают команды в игру через TCP API. Когда игра заканчивается, создается файл JSON с именем scrappers.json (также в настоящее время не настраиваемый). Это то, что рендерер использует для создания GIF-кода игры.

TCP API

Программы игрока и игровой движок обмениваются данными, передавая строки JSON с завершающей строкой назад и четвертый через TCP-соединение. Существует только пять различных сообщений JSON, которые можно отправлять или получать.

Готовое сообщение

Сообщение ГОТОВ отправляется из игры в программы игрока и отправляется только один раз. Это сообщение сообщает программе игрока, каков ее идентификатор игрока (PID), и предоставляет список всех ботов в игре. PID - единственный способ определить, какие боты дружественны против врага. Подробнее о полях ботов ниже.

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

Бот Сообщение

Сообщение BOT отправляется из игры в программы игрока и отправляется при изменении атрибутов бота. Например, когда проецируются щиты или меняется состояние, отправляется сообщение BOT. Идентификатор бота (BID) уникален только для конкретного игрока.

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

Переместить сообщение

Сообщение MOVE - это команда от программы игрока к игре (но воспринимайте ее как команду для бота). Просто укажите бота, которого вы хотите переместить, и координаты. Предполагается, что вы командуете своим собственным ботом, поэтому PID не требуется.

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

Целевое сообщение

Сообщение TARGET говорит одному из ваших ботов, чтобы он нацелен на другого бота.

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

Сообщение силы

Сообщение POWER перераспределяет 12pu, доступное вашему боту, между движением, огневой мощью и щитами.

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

Конкурс

Если вы достаточно смелы, чтобы исследовать необузданные земли, вы попадете в турнир с двойным выбыванием против своих наемников. Пожалуйста, создайте ответ для своей заявки и вставьте свой код или укажите ссылку на git-репо, gist и т. Д. Любой язык приемлем, но вы должны предположить, что я ничего не знаю о языке, и включать инструкции по запуску вашей программы. Создайте столько заявок, сколько захотите, и обязательно дайте им имена!

Программы- примеры игроков будут включены в турнир, поэтому я настоятельно рекомендую протестировать вашего бота на них. Турнир начнется примерно через две недели после того, как мы получим четыре уникальных представления программы. Удачи!

--- Winner's Bracket ---

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

Другая важная информация

  • Игра идет со скоростью 12 тиков в секунду, поэтому вы не будете получать сообщения чаще, чем каждые 83 миллисекунды или около того.
  • Каждый бот 60du в диаметре. Щит не занимает дополнительного места. С точностью +/- 2,5% вероятность попадания бота на определенное расстояние представлена ​​на следующем графике:

график точности

  • Распад лазерного повреждения на расстоянии представлен этим графиком:

график снижения ущерба

  • Точность бота и лазерный распад объединяются, чтобы вычислить его средний урон за выстрел. То есть средний урон, который бот будет причинять при стрельбе с определенного расстояния. Урон за выстрел представлен этим графиком:

график повреждений за выстрел

  • Лазер бота возникает на полпути между центром бота и его краем. Таким образом, сложение ваших ботов приведет к дружескому огню.
  • Вражеские боты появляются примерно на 1440du друг от друга.
  • Игра заканчивается, если пройдут 120 тиков (10 секунд) без нанесенного урона.
  • Победителем становится игрок с наибольшим количеством ботов, а затем наибольшее количество здоровья, когда игра заканчивается.

Понимание отрендеренного изображения

  • Игрок 1 представлен кругами, а игрок 2 - шестиугольниками.
  • Цвет бота представляет его распределение мощности. Больше красного означает, что больше энергии было выделено для стрельбы. Больше синего означает больше щита. Больше зеленого означает больше движения.
  • «Дыра» в теле бота представляет собой повреждение. Чем больше отверстие, тем больше урона было получено.
  • Белые круги вокруг бота - это его щит. Если у бота есть щит в конце хода, он показывается. Если щит был поврежден при получении урона, он не отображается.
  • Красные линии между ботами обозначают сделанные снимки.
  • Когда бот убит, появляется большой красный «взрыв».
Рип Лееб
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Деннис

Ответы:

4

Экстремистский (Питон 3)

Этот бот всегда будет посвящать всю свою силу одной вещи: экранированию, если он не экранирован, перемещению, если он не в своем положении, и стрельбе в противном случае. Бьет всех образцовых ботов, кроме смертельной тарелки.

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
источник
Я не знаком с python, но, похоже, у вас есть несколько проблем с вашей отправкой: 1) строки 212 120 не имеют правильного отступа и 2) target_hp не определен. Я мог бы исправить (1), но (2) мешает мне выполнить ваше представление. Но это может быть мой недостаток опыта работы с питоном.
Муги
Весь этот оператор if остался после некоторой отладки и совсем не нужен
pppery
2

Менее безрассудный ( Go )

go run main.go

Первоначально я планировал немного изменить образец программы Reckless Abandon, чтобы боты не запускались, если мешал дружественный бот. Я оказался с ботами, которые выбирают новые цели, когда друг мешает, что, я думаю, лучше. Это побьет первые две программы.

Код не идеален. Логика определения четкости выстрела использует довольно случайные догадки.

Кажется, не существует механизма, нацеленного на «никого». Это может быть хорошей особенностью, чтобы добавить.

TCP API хорош тем, что на нем может играть любой язык, но это также означает много стандартного кода. Если бы я не был знаком с языком, на котором написаны примеры ботов, у меня, вероятно, не было бы мотивов поиграть с этим. Коллекция шаблонных образцов на разных языках станет отличным дополнением к вашим другим репозиториям git.

(большая часть следующего кода - копирование / вставка из одного из примеров ботов)

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
источник
Эта программа побеждает мое экстремистское подчинение?
17
Нет @ppperry, это не так. Это пушечное мясо, но я работаю над вторым ботом.
Нарибе
2

Trigger Happy - Java 8

Trigger Happy - это простая эволюция моего оригинального, но уже не жизнеспособного бомбардного бота. Это очень простой бот, который просто стреляет по целевому противнику, если есть точный выстрел, в противном случае выполняет случайную прогулку, чтобы попытаться занять лучшую позицию. Все время пытаюсь получить щит.

Однако при всей своей простоте он очень эффективен. И с готовностью уничтожит образцы ботов.

Обратите внимание, что у бота есть несколько ошибок, таких как иногда стрельба, даже если это не точный выстрел, и он может не поддерживать щит ... но он все еще эффективен, поэтому просто отправит эту запись как есть

блюдо смерти против триггера счастливого

Блюдо смерти против Триггера Хэппи

Код следующим образом:

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

Для компиляции: javac TriggerHappy.java

Для запуска: Java TriggerHappy

Moogie
источник