Scrappers v0.1: Найманські програмісти


22

У спустошеному, розірваному війною світі, де міста були переповнені злодіями і злодіями, цивілізація відродилася у вигляді невеликих, ізольованих, промислових кооперативів, розкиданих по раніше нежилому ландшафту. Існування цих громад залежить від колективів найманців, які називаються "скрапперами", які шукають на неприйнятій території цінні матеріали для продажу кухарям. Оскільки ці матеріали стали дефіцитнішими, металобрухт стає все більш важкою та небезпечною професією. Тендітних людей в основному були замінені на віддалені робототехнічні стоянки, звані "ботами", а типовий найман, швидше за все, буде більш кваліфікованим програмістом, ніж озброєним зварником. Оскільки присутність людини у відходах зменшилась, так і повага між корисливими групами одна до одної. Боти обладнані не тільки для збору брухту, але і для його захисту, а в деяких випадках приймають його силою. Програмісти ботів невтомно розробляють нові стратегії, щоб перехитрити суперників-скраптерів, що призводить до все більш агресивних ботів і ще однієї небезпеки для людей, які ризикують за стінами своїх громад.

Скраптики Зразок гри

(так, логотип весело обрізається)

Ласкаво просимо до Scrappers!

Це рання версія Scrappers, в якій збирання брухту та фабрики не впроваджені. Це в основному "вистрілити їх".

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

Боти для скребків - надзвичайно спритні машини, і вони можуть легко переміщатися над, під і навколо будь-яких перешкод, з якими вони стикаються. Таким чином, зіткнення - це не те, що потрібно враховувати вашій програмі. Ви можете безкоштовно виділити всі, деякі або жодні з 12пу, доступних вашому боту для руху, якщо ви маєте цілу кількість. Виділення функції 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% менше шкоди за кожну довжину бота, який він подорожує.

Роботи Scrapper достатньо автономні для роботи з основними функціями, але покладайтесь на вас, їх програміста, щоб зробити їх корисними як група. Як програміст, ви можете видавати наступні команди для кожного окремого бота:

  • MOVE: Вкажіть координати, до яких рухатиметься бот.
  • МЕТА: Визначте бота, на який слід націлюватись та стріляти, коли дозволяє розподіл потужності.
  • ДІЯЛЬНІСТЬ: Перерозподіляйте силу між рухом, щитами та вогневою силою.

Технічні деталі гри

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

Ігровий двигун

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

API TCP

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

Готове повідомлення

Повідомлення READY надсилається з гри програвачам гравця і надсилається лише один раз. Це повідомлення повідомляє програмі програвача, що таке його ідентифікатор гравця (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 repo, gist тощо. Будь-яка мова є прийнятною, але ви повинні припустити, що я нічого не знаю про мову та включіть інструкції щодо запуску програми. Створіть скільки завгодно матеріалів, і обов'язково дайте їм імена!

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

--- Winner's Bracket ---

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

Інша важлива інформація

  • Гра працює зі швидкістю 12 тиків на секунду, тому ви не будете отримувати повідомлення частіше, ніж кожні 83 мілісекунди.
  • Кожен бот має діаметр 60du. Щит не займає додаткового місця. Із точністю +/- 2,5% шанси удару бота на певній відстані представлені цим графіком:

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

  • Розпад лазерного пошкодження на відстань представлений цим графіком:

графік занепаду шкоди

  • Точність бота і лазерний розпад поєднуються для обчислення середнього шкоди за постріл. Тобто, середній збиток, який бот нанесе, коли він стріляє з певної відстані. Пошкодження за постріл представлені цим графіком:

пошкодження на графік пошкоджень

  • Лазер бота бере початок на півдорозі між центром бота та його краєм. Таким чином, укладання ваших ботів призведе до доброзичливого вогню.
  • Ворожі боти породили приблизно 1440ду один від одного.
  • Гра закінчується, якщо пройде 120 тиків (10 секунд) без будь-якої шкоди.
  • Переможець - гравець з більшою кількістю ботів, то найбільше здоров'я, коли гра закінчується.

Розуміння відображеного зображення

  • Гравець 1 представлений колами, а гравець 2 - шестикутниками.
  • Колір бота представляє його розподіл потужності. Більше червоного кольору означає більше енергії для стрільби. Більше синього кольору означає більше щита. Більше зеленого кольору означає більше руху.
  • "Дірка" в тілі бота являє собою пошкодження. Чим більше отвір, тим більше шкоди було завдано.
  • Білі кола навколо бота - це щит. Якщо бот має щит в кінці повороту, він відображається. Якщо щит вискочив ушкодженням, він не показаний.
  • Червоні лінії між ботами відображають зроблені кадри.
  • Коли бота вбито, показується великий червоний «вибух».

Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
Денніс

Відповіді:


4

Екстремістський (Python 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

Я не знайомий з python, але, здається, виникає кілька проблем із вашим поданням: 1) рядки 212 120 не мають відступу правильно та 2) target_hp не визначено. Я міг би виправити (1), але (2) не дозволяє мені виконувати ваше подання. Але це мій брак досвіду роботи з пітоном.
Moogie

Це все, якщо заява залишилася від налагодження і зовсім не потрібна
pppery

2

Менш безрозсудливо ( йти )

go run main.go

Спочатку я планував трохи змінити зразок програми Reckless Abandon, щоб боти не вистрілювали, якби на шляху був дружній бот. Я закінчив ботів, які вибирають нові цілі, коли друг перешкоджає, що, мабуть, краще. Він обіграє перші дві програми.

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

Здається, не існує механізму націлювання на "нікого". Це може бути гарною особливістю додати.

API TCP приємний тим, що на будь-якій мові можна грати, але це також означає багато кодового шаблону. Якби я не був знайомий з мовою, про яку писали зразки ботів, я, певно, не був би вмотивований цим займатися. Колекція зразків котлів на різних мовах буде чудовим доповненням до інших ваших git repos.

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

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

Чи перемагає ця програма моє екстремістське уявлення?
pppery

Ні @ppperry, ні. Це гарматний корм, але я працюю над другим ботом.
Нарібе

2

Тригер щасливий - Java 8

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

Однак при всій своїй простоті це дуже ефективно. І легко знищить зразки ботів.

Зауважте, що з ботом є декілька помилок, таких як іноді вистрілить, навіть коли не буде чіткий постріл і не може підтримувати щит ... але він все ще дієвий, тому просто подайте цей запис як такий

смерть-страва проти тригера щаслива

Смерть-блюдо проти тригера 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.