Scrappers v0.1: Najemni programiści

22

W opustoszałym, rozdartym wojną świecie, w którym miasta zostały opanowane przez bandytów i złodziei, cywilizacja odkryła się na nowo w formie małych, odizolowanych spółdzielni przemysłowych, rozproszonych po wcześniej niezamieszkanym krajobrazie. Istnienie tych społeczności zależy od zespołów najemników zwanych „złomowcami”, którzy przeszukują nieokiełznane terytorium w poszukiwaniu cennych materiałów, które mogą sprzedać spółdzielni. Ponieważ tych materiałów jest coraz mniej, złomowanie staje się coraz trudniejszym i niebezpiecznym zawodem. Kruchych pracowników ludzkich zastąpiono w większości zdalnymi robotami, zwanymi „botami”, a typowy najemnik jest bardziej doświadczonym programistą niż uzbrojonym spawaczem. Wraz ze spadkiem ludzkiej obecności podczas złomowania, również szacunek między grupami najemników względem siebie. Boty są wyposażone nie tylko w zbieranie złomu, ale także w jego obronę, aw niektórych przypadkach siłą. Programiści botów niestrudzenie pracują nad nowymi strategiami, aby przechytrzyć rywali złomowcami, co skutkuje coraz bardziej agresywnymi botami i jeszcze innym niebezpieczeństwem dla ludzi, którzy zapuszczają się poza mury swoich społeczności.

Próbka gry skrobaczek

(tak, logo jest przezabawnie przycięte)

Witamy w Skrobaczkach!

Jest to wczesna wersja skrobaczek, w której nie wdrożono zbiórki złomu i fabryk. Zasadniczo jest to „strzelanie do nich”.

Jesteś programistą najemników, którego zadaniem jest tworzenie programu do zdalnego prowadzenia botów do zwycięstwa nad rywalizującymi grupami zgarniaczy. Twoje boty to pająkowate maszyny składające się z generatorów mocy i tarcz w ich rdzeniu, otoczone wieloma dodatkami wyposażonymi w narzędzia do chwytania, cięcia i atakowania. Generator mocy jest w stanie wyprodukować 12 jednostek mocy (pu) na tik (jednostka czasu zgarniacza). Masz kontrolę nad rozkładem tej mocy pomiędzy trzy podstawowe potrzeby bota: ruch, tarcze i siłę ognia.

Boty Scrapper są wyjątkowo zwinnymi maszynami i mogą łatwo przemieszczać się nad, pod i wokół przeszkód, które napotkają. Tak więc kolizja nie jest czymś, co powinien wziąć pod uwagę twój program. Możesz dowolnie przydzielić wszystkie, niektóre lub żadne z 12pu dostępnych botowi do ruchu, pod warunkiem, że zajmujesz się liczbami całkowitymi. Przypisanie 0pu funkcjom ruchu bota unieruchomiłoby go. Przydzielenie 2pu umożliwi botowi przesunięcie 2 jednostek odległości (du) na tik, 5pu spowoduje 5du / tik, 11pu spowoduje 11du / tik i tak dalej.

Generatory tarcz twoich botów emitują bąbel energii odbijającej wokół ciała. Tarcza może odbić do 1 obrażeń przed pęknięciem, pozostawiając twojego bota odsłoniętego, dopóki jego generator tarcz nie zgromadzi wystarczającej mocy, aby zatrzasnąć tarczę z powrotem na swoje miejsce. Możesz przydzielić wszystkie, niektóre lub żadne 12pu dostępne dla twojego bota w kierunku jego tarczy. Przydzielenie 0pu tarczy bota oznacza, że ​​nigdy nie wygeneruje tarczy. Przydzielenie 2pu pozwoliłoby botowi wygenerować nową tarczę 2 z 12 tyknięć lub raz na 6 tyknięć. 5pu spowoduje regenerację tarczy 5 na każde 12 tyknięć i tak dalej.

Tworząc ładunek w swoich laserach spawalniczych, boty mogą wystrzelić szkodliwe wiązki na krótkich dystansach z dość dokładną dokładnością. Podobnie jak generowanie tarcz, szybkość ostrzału twoich botów zależy od mocy przydzielonej ich laserom. Przydzielenie 0pu laserom bota oznacza, że ​​nigdy nie będzie strzelał. Przydzielenie 2pu pozwoliłoby botowi wystrzelić 2 z każdych 12 tyknięć i tak dalej. Laser bota będzie podróżował, dopóki nie napotka przedmiotu lub nie rozproszy się w bezużyteczności, więc miej na uwadze przyjazny ogień. Chociaż twoje boty są dość dokładne, nie są idealne. Powinieneś spodziewać się +/- 2,5 stopnia wariancji dokładności. W miarę przemieszczania się wiązki laserowej jej cząstki są stopniowo odginane przez atmosferę, aż wiązka stanie się skutecznie nieszkodliwa przy wystarczającej odległości. Laser zadaje 1 obrażenia w punkcie zerowym i 2,5% mniej obrażeń na każdej długości bota, którą pokonuje.

Boty Scrapper są wystarczająco autonomiczne, aby obsłużyć podstawowe funkcje, ale możesz polegać na tobie, ich programiście, aby uczynić je użytecznymi jako grupa. Jako programista możesz wydać następujące polecenia dla każdego bota:

  • PRZESUŃ: Określ współrzędne, w stronę których porusza się bot.
  • CEL: Zidentyfikuj bota, do którego chcesz celować i strzelać, gdy pozwala na to przydział mocy.
  • MOC: Rozdziel siłę między ruch, tarcze i siłę ognia.

Techniczne szczegóły gry

Istnieją trzy programy, które musisz znać. Gra Silnik jest ciężki podnośnik i dostarcza API TCP że programy odtwarzacz połączyć. Program odtwarzacza jest tym, co napiszesz. Podałem tutaj kilka przykładów z plikami binarnymi . Na koniec Renderer przetwarza dane wyjściowe z silnika gry w celu wygenerowania pliku GIF bitwy.

Silnik gry

Możesz pobrać silnik gry stąd . Gdy gra zostanie uruchomiona, zacznie nasłuchiwać na porcie 50000 (obecnie nie można go konfigurować) dla połączeń graczy. Po otrzymaniu połączenia z dwoma graczami wysyła wiadomość GOTOWĄ do graczy i rozpoczyna grę. Programy odtwarzające wysyłają polecenia do gry za pośrednictwem interfejsu API TCP. Po zakończeniu gry tworzony jest plik JSON o nazwie scrappers.json (również obecnie nie można go konfigurować). Tego używa renderer do stworzenia GIF-a gry.

Interfejs API TCP

Programy odtwarzacza i silnik gry komunikują się, przekazując ciągi JSON zakończone znakiem nowej linii z powrotem i czwartą przez połączenie TCP. Istnieje tylko pięć różnych komunikatów JSON, które można wysyłać lub odbierać.

Gotowa wiadomość

Komunikat GOTOWY jest wysyłany z gry do programów gracza i jest wysyłany tylko raz. Ten komunikat informuje program gracza o jego identyfikatorze (PID) i zawiera listę wszystkich botów w grze. PID to jedyny sposób na określenie, które boty są przyjazne przeciwko wrogom. Więcej informacji na temat pól botów poniżej.

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

Wiadomość Bot

Wiadomość BOT jest wysyłana z gry do programów gracza i wysyłana, gdy zmieniają się atrybuty bota. Na przykład, gdy wyświetlane są tarcze lub zmiany stanu zdrowia, wysyłany jest komunikat BOT. Bot ID (BID) jest unikalny tylko w obrębie konkretnego gracza.

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

Przenieś wiadomość

Komunikat MOVE jest poleceniem z programu gracza do gry (ale myśl o tym jako o poleceniu dla bota). Wystarczy zidentyfikować bota, który chcesz przenieść, i współrzędne. Zakłada się, że dowodzisz własnym botem, więc PID nie jest potrzebny.

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

Wiadomość docelowa

Komunikat TARGET mówi jednemu z twoich botów, aby celował w innego bota.

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

Wiadomość mocy

Komunikat POWER przenosi 12pu dostępnych dla twojego bota między ruchem, siłą ognia i tarczami.

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

Konkurencja

Jeśli masz dość odwagi, by eksplorować nieokiełznane ziemie, weźmiesz udział w turnieju podwójnej eliminacji przeciwko swoim najemnikom. Utwórz odpowiedź dla swojego zgłoszenia i wklej kod lub podaj link do repozytorium git, gist itp. Dowolny język jest akceptowalny, ale powinieneś założyć, że nic nie wiem o tym języku, i dołącz instrukcje dotyczące uruchomienia programu. Utwórz tyle zgłoszeń, ile chcesz, i nadaj im nazwy!

Programy przykładowych graczy zostaną uwzględnione w turnieju, więc bardzo polecam przetestowanie twojego bota przeciwko nim. Turniej rozpocznie się mniej więcej dwa tygodnie po otrzymaniu czterech unikalnych zgłoszeń do programu. Powodzenia!

--- Winner's Bracket ---

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

Inne ważne informacje

  • Gra działa z prędkością 12 tyknięć na sekundę, więc nie będziesz otrzymywać wiadomości częściej niż co 83 milisekundy.
  • Każdy bot ma średnicę 60du. Tarcza nie zajmuje dodatkowego miejsca. Przy dokładności +/- 2,5% szanse na trafienie bota w pewnej odległości są przedstawione na tym wykresie:

wykres dokładności

  • Rozkład uszkodzenia lasera na odległość jest reprezentowany przez ten wykres:

wykres rozpadu uszkodzeń

  • Dokładność bota i rozkład laserowy łączą się, aby obliczyć średnie obrażenia na strzał. Oznacza to, że średnie obrażenia zadawane przez bota, gdy strzela z pewnej odległości. Obrażenia na strzał przedstawiają ten wykres:

wykres uszkodzeń na strzał

  • Laser bota zaczyna się w połowie drogi między środkiem bota a jego krawędzią. Tak więc układanie botów spowoduje przyjazny ogień.
  • Wrogie boty spawnują się mniej więcej w odległości 1440du.
  • Gra kończy się, gdy minie 120 tyknięć (10 sekund) bez zadanych obrażeń.
  • Zwycięzcą zostaje gracz, który ma najwięcej botów, a następnie najwięcej zdrowia po zakończeniu gry.

Zrozumienie renderowanego obrazu

  • Gracz 1 jest reprezentowany przez koła, a gracz 2 przez sześciokąty.
  • Kolor bota odzwierciedla jego przydział mocy. Więcej czerwieni oznacza, że ​​więcej mocy zostało przydzielone do strzelania. Więcej niebieskiego oznacza więcej tarczy. Więcej zieleni oznacza większy ruch.
  • „Dziura” w ciele bota oznacza obrażenia. Im większy otwór, tym więcej obrażeń odniesiono.
  • Białe kółka otaczające bota to jego tarcza. Jeśli bot ma tarczę na końcu tury, jest pokazywana. Jeśli tarcza została rozbita przez odniesienie obrażeń, nie jest wyświetlana.
  • Czerwone linie między botami przedstawiają wykonane zdjęcia.
  • Po zabiciu bota wyświetlana jest duża czerwona „eksplozja”.
Rip Leeb
źródło
Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Dennis,

Odpowiedzi:

4

Extremist (Python 3)

Ten bot zawsze poświęca całą swoją moc na jedną rzecz: osłonę, jeśli nie jest osłonięta, poruszanie się, jeśli jest na pozycji i strzelanie w inny sposób. Pokonuje wszystkich przykładowych botów oprócz anteny śmierci.

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
źródło
Nie jestem zaznajomiony z pythonem, ale wydaje się, że jest wiele problemów z przesłaniem: 1) wiersze 212 120 nie są poprawnie wcięte i 2) target_hp nie jest zdefiniowany. Mógłbym naprawić (1), ale (2) powstrzymuje mnie przed uruchomieniem twojego zgłoszenia. Ale może to być mój brak doświadczenia z pythonem.
Moogie,
Cała instrukcja if pozostała po jakimś debugowaniu i wcale nie jest konieczna
pppery
2

Mniej lekkomyślny ( Idź )

go run main.go

Początkowo planowałem nieznacznie zmodyfikować przykładowy program Reckless Abandon, aby boty nie strzelały, gdyby przeszkadzał mi przyjazny bot. Skończyłem z botami, które wybierają nowe cele, gdy kolega jest na drodze, co, jak sądzę, jest lepsze. Pokona pierwsze dwa programy.

Kod nie jest idealny. Logika określania, czy strzał jest wyraźny, wymaga dość losowych domysłów.

Wydaje się, że nie ma mechanizmu celowania w „nikogo”. To może być dobra funkcja do dodania.

Interfejs TCP API jest przyjemny w tym, że można go odtwarzać w dowolnym języku, ale oznacza to także wiele kodów typu „Boiler Plate”. Gdybym nie był zaznajomiony z językiem, w którym napisane były przykładowe boty, prawdopodobnie nie byłbym zmotywowany do zabawy. Zbiór próbek szablonów w różnych językach byłby świetnym dodatkiem do innych repozytoriów git.

(większość następującego kodu to kopiuj / wklej z jednego z przykładowych botów)

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
źródło
Czy ten program przewyższa moje ekstremistyczne zdanie?
pppery
Nie @ppperry, nie ma. To mięso armatnie, ale pracuję nad drugim botem.
Naribe
2

Trigger Happy - Java 8

Trigger Happy to prosta ewolucja mojego oryginalnego, ale już nieopłacalnego bota Bombard. Jest to bardzo prosty bot, który po prostu strzela do aktualnie atakowanego wroga, jeśli jest wyraźny strzał, w przeciwnym razie wykonuje losowy spacer, aby spróbować dostać się w lepszą pozycję. Cały czas próbuje mieć tarczę.

Jednak pomimo całej swojej prostoty jest bardzo skuteczny. I z łatwością zniszczy przykładowe boty.

Uwaga: bot ma wiele błędów, np. Czasami strzela, nawet jeśli nie ma wyraźnego strzału i może nie utrzymywać tarczy ... ale nadal jest skuteczny, więc po prostu prześlij ten wpis w obecnym stanie

death-dish vs trigger szczęśliwy

Death-dish vs Trigger Happy

Kod w następujący sposób:

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

Aby skompilować: javac TriggerHappy.java

Aby uruchomić: Java TriggerHappy

Moogie
źródło