Scrappers v0.1 : 용병 프로그래머


22

도시가 깡패들과 도둑들에 의해 지배 된 황폐하고 전쟁이 심한 세상에서, 문명은 이전의 무인도에 흩어져있는 작고 고립 된 산업 협동 조합의 형태로 재창조되었습니다. 이 공동체의 존재는 "스크래퍼 (scrappers)"라고 불리는 용병 노동자 팀에 의존하는데, 이들은 스탬프에게 팔릴 귀중한 재료를 찾기 위해 길들여지지 않은 영토를 찾는다. 이러한 재료가 점점 부족 해짐에 따라 폐기는 점점 더 어렵고 위험한 직업이되었습니다. 연약한 인간 작업자는 대부분 "봇"이라고하는 원격 로봇 스탠드로 대체되었으며 일반적인 용병은 무장 용접기보다 숙련 된 프로그래머 일 가능성이 높습니다. 폐기에 대한 인간의 존재가 감소함에 따라, 용병 그룹들 사이의 존중도 ​​서로에 대한 존중을 가졌다. 봇은 스크랩을 수집 할뿐만 아니라이를 보호하기 위해 장착되며 경우에 따라 강제로 가져옵니다. 봇 프로그래머는 경쟁자 스크래퍼보다 앞서 나가기 위해 끊임없이 새로운 전략을 고안하여 점점 더 공격적인 봇을 만들고 커뮤니티 외부에서 모험을하는 인간에게는 또 다른 위험을 초래합니다.

Scrappers 게임 샘플

(예, 로고가 유쾌하게 잘립니다)

Scrappers에 오신 것을 환영합니다!

스크랩 수집 및 팩토리가 구현되지 않은 초기 버전의 Scrapper입니다. 기본적으로 "촬영"입니다.

귀하는 경쟁 스크레이퍼 그룹에서 승리하기 위해 봇을 원격으로 수행하는 프로그램을 작성하는 임무를 맡은 용병 프로그래머입니다. 봇은 코어에 전력 및 실드 생성기로 구성된 스파이더와 유사한 기계로, 그립, 절단 및 공격 도구가 장착 된 많은 부속 장치로 둘러싸여 있습니다. 발전기는 틱 (스크래퍼의 시간 단위) 당 12 개의 전원 장치 (pu)를 생산할 수 있습니다. 봇의 3 가지 주요 요구 (이동, 방패, 화력)에이 힘이 어떻게 분배되는지 제어 할 수 있습니다.

스크래퍼 봇은 매우 민첩한 기계이며, 발생하는 모든 장애물 위로 쉽게 이동할 수 있습니다. 따라서 충돌은 프로그램에서 고려해야 할 사항이 아닙니다. 정수를 처리하는 한 12pu의 봇에 사용할 수있는 12pu 중 일부 또는 전부를 자유롭게 할당 할 수 있습니다. 봇의 이동 기능에 0pu를 할당하면 움직이지 않게됩니다. 2pu를 할당하면 봇이 틱당 2 개의 거리 단위 (du)를 이동할 수 있으며, 5pu는 5du / tick, 11pu는 11du / tick 등이됩니다.

봇의 쉴드 생성기는 몸 주위에 반사 에너지 거품을 투사합니다. 방패는 터지기 전에 최대 1의 피해를받을 수 있으므로 방패 생성기가 방패를 다시 제자리에 끼울 수있는 충분한 힘을 얻을 때까지 봇을 노출시킵니다. 봇이 방패를 향해 사용할 수있는 12pu 중 일부 또는 전부를 자유롭게 할당 할 수 있습니다. 봇의 방패에 0pu를 할당하면 방패가 생성되지 않습니다. 2pu를 할당하면 봇이 12 틱 중 또는 6 틱마다 한 번씩 새로운 방패를 생성 할 수 있습니다. 5pu는 12 틱마다 쉴드 재생 5를 초래합니다.

용접 레이저에 전하를 축적함으로써 봇은 근거리에서 정확한 정확도로 해로운 광선을 발사 할 수 있습니다. 방패 생성과 마찬가지로 봇의 발사 속도는 레이저에 할당 된 전력에 따라 다릅니다. 봇의 레이저에 0pu를 할당하면 절대로 발사되지 않습니다. 2pu를 할당하면 봇이 12 틱마다 2 개를 발사 할 수 있습니다. 봇의 레이저는 물체에 부딪 치거나 쓸모가 없어 질 때까지 이동하므로 친절한 화재에주의하십시오. 봇은 매우 정확하지만 완벽하지는 않습니다. +/- 2.5 도의 정확도 차이를 기대해야합니다. 레이저 빔이 이동함에 따라 빔은 충분한 거리로 빔이 효과적으로 무해해질 때까지 대기에 의해 점차적으로 편향됩니다. 레이저는 포인트 블랭크 범위에서 1의 피해를 입히고, 이동하는 봇 길이마다 2.5 %의 피해를줍니다.

스크래퍼 봇은 기본 기능을 처리 할 수있을만큼 자율적이지만, 프로그래머 인 당신에게 의존하여 그룹으로 유용하게 만듭니다. 프로그래머는 각 개별 봇에 대해 다음 명령을 발행 할 수 있습니다.

  • 이동 : 봇이 이동할 좌표를 지정합니다.
  • 목표 : 전력 할당이 허용 될 때 조준하고 발사 할 봇을 식별합니다.
  • 전원 : 이동, 보호막 및 화력간에 전원을 재분배합니다.

기술 게임 정보

숙지해야 할 세 가지 프로그램이 있습니다. 게임 엔진은 무거운 리프터이며, 플레이어 프로그램에 연결하는 TCP API를 제공합니다. 플레이어 프로그램은 여러분이 작성할 것이며, 여기에 바이너리가 포함 된 예제가 있습니다 . 마지막으로 렌더러 는 게임 엔진의 출력을 처리하여 전투의 GIF를 만듭니다.

게임 엔진

여기에서 게임 엔진을 다운로드 할 수 있습니다 . 게임이 시작되면 플레이어 연결을 위해 포트 50000 (현재 구성 할 수 없음)에서 청취를 시작합니다. 두 개의 플레이어 연결이 수신되면 READY 메시지를 플레이어에게 보내고 게임을 시작합니다. 플레이어 프로그램은 TCP API를 통해 게임에 명령을 보냅니다. 게임이 끝나면 scrappers.json이라는 JSON 파일 (현재 구성 할 수 없음)이 생성됩니다. 렌더러가 게임의 GIF를 만드는 데 사용하는 것입니다.

TCP API

플레이어 프로그램과 게임 엔진은 줄 바꿈으로 끝나는 JSON 문자열을 TCP 연결을 통해 앞뒤로 전달하여 통신합니다. 보내거나받을 수있는 JSON 메시지는 다섯 가지뿐입니다.

준비 메시지

READY 메시지는 게임에서 플레이어 프로그램으로 전송되며 한 번만 전송됩니다. 이 메시지는 플레이어 프로그램에 플레이어 ID (PID)가 무엇인지 알려주고 게임의 모든 봇 목록을 제공합니다. PID는 친한 대 봇을 결정하는 유일한 방법입니다. 아래 봇 필드에 대한 자세한 정보.

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

봇 메시지

BOT 메시지는 게임에서 플레이어 프로그램으로 전송되며 봇의 속성이 변경되면 전송됩니다. 예를 들어, 방패가 투사되거나 상태가 변경되면 BOT 메시지가 전송됩니다. 봇 ID (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
}

대상 메시지

타겟 메시지는 봇 중 하나에게 다른 봇을 타겟팅하도록 알려줍니다.

{
    "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 등의 링크를 제공하십시오. 모든 언어는 허용되지만 언어에 대해 전혀 모른다고 가정하고 프로그램 실행 지침을 포함해야한다고 가정해야합니다. 원하는만큼 제출물을 작성하고 이름을 지정하십시오!

샘플 플레이어 프로그램은 내가보기 엔 그들에 대하여 당신의 로봇을 테스트하는 것이 좋습니다 그래서 대회에 포함됩니다. 토너먼트는 4 번의 독특한 프로그램 제출 후 대략 2 주 후에 시작됩니다. 행운을 빕니다!

--- Winner's Bracket ---

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

다른 중요한 정보

  • 게임은 12 틱 / 초로 실행되므로 83 밀리 초 정도마다 메시지를 더 자주받지 않습니다.
  • 각 봇의 직경은 60du입니다. 방패는 추가 공간을 차지하지 않습니다. +/- 2.5 % 정확도에서 특정 거리에서 봇을 칠 확률은이 그래프로 표시됩니다.

정확도 그래프

  • 거리에 따른 레이저 손상의 붕괴는 다음 그래프로 표시됩니다.

붕괴 그래프

  • 봇의 정확도와 레이저 붕괴가 결합하여 샷당 평균 손상을 계산합니다. 즉, 로봇이 특정 거리에서 발사 할 때 발생하는 평균 피해량입니다. 샷당 데미지는이 그래프로 표시됩니다.

샷 그래프 당 피해

  • 봇의 레이저는 봇의 중심과 가장자리 사이의 반쯤에서 시작됩니다. 따라서 봇을 쌓아두면 우호적 인 화재가 발생합니다.
  • 적 봇은 약 1440du 간격으로 생성됩니다.
  • 120 틱 (10 초)이 지나치지 않으면 게임이 종료됩니다.
  • 승자는 봇이 가장 많은 플레이어이며 게임이 끝날 때 가장 건강합니다.

렌더링 된 이미지 이해

  • 플레이어 1은 원으로, 플레이어 2는 육각형으로 표시됩니다.
  • 봇의 색상은 전력 할당을 나타냅니다. 빨간색이 많을수록 발사에 더 많은 전력이 할당되었음을 의미합니다. 파란색이 많을수록 더 많은 방패를 의미합니다. 더 많은 녹색은 더 많은 움직임을 의미합니다.
  • 봇 본체의 "구멍"은 손상을 나타냅니다. 구멍이 클수록 더 많은 데미지를 입었습니다.
  • 봇을 둘러싼 흰색 원은 방패입니다. 턴 끝에 봇에 방패가 있으면 표시됩니다. 방패가 데미지를 입어 튀어 나오면 보이지 않습니다.
  • 봇 사이의 빨간색 선은 촬영 된 장면을 나타냅니다.
  • 봇이 죽으면 큰 빨간색 "폭발"이 표시됩니다.

의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Dennis

답변:


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

나는 파이썬에 익숙하지 않지만 제출에 여러 가지 문제가있는 것 같습니다 : 1) 212120 줄이 올바르게 들여 쓰지 않고 2) target_hp가 정의되지 않았습니다. 나는 (1)을 고칠 수는 있지만 (2)는 제출을 실행하지 못하게합니다. 그러나 파이썬에 대한 경험이 부족할 수 있습니다.
Moogie

전체 if 문은 일부 디버깅에서
남았

2

덜 무모한 ( 가는 )

go run main.go

원래, 나는 친절한 로봇이 방해가되었을 때 로봇이 발사되지 않도록 무모한 포기 샘플 프로그램을 약간 수정하려고 계획했습니다. 나는 친구가 방해가 될 때 새로운 목표를 선택하는 봇으로 끝났습니다. 처음 두 프로그램을 이길 것입니다.

코드가 완벽하지 않습니다. 샷이 명확한 지 판단하는 논리는 꽤 임의의 추측을 사용합니다.

"아무도"를 목표로하는 메커니즘이없는 것 같습니다. 추가하기에 좋은 기능 일 수 있습니다.

TCP API는 모든 언어를 재생할 수 있다는 점에서 훌륭하지만 많은 상용구 코드를 의미합니다. 샘플 봇이 쓰여진 언어에 익숙하지 않다면 아마도 이것에 대해 다루지 않았을 것입니다. 다양한 언어로 상용구 샘플을 수집하면 다른 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는 없습니다. 대포 사료이지만 두 번째 봇에서 일하고 있습니다.
Naribe

2

행복 트리거-Java 8

Trigger Happy는 제 독창적 이었지만 더 이상 생존 할 수없는 Bombard 봇의 단순한 진화입니다. 명중률이 높으면 현재 목표로하는 적에게 발사하는 매우 간단한 봇입니다. 그렇지 않으면 더 나은 위치에 도달하기 위해 무작위 보행을 수행합니다. 항상 방패를 쓰려고합니다.

그러나 모든 단순성에 대해 매우 효과적입니다. 샘플 봇을 쉽게 파괴합니다.

봇에는 여러 가지 버그가 있습니다. 예를 들어 명확한 샷이 아니더라도 방패를 유지하지 못할 때도 때때로 발사됩니다. 그러나 여전히 효과적이므로이 항목을 그대로 제출하면됩니다.

죽음의 죽음 대 행복

죽음의 죽음 대 트리거 행복

다음과 같이 코딩하십시오.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.