Scriptbot Warz!

14

Scriptbot Warz!


Os resultados estão chegando e Assassin é o nosso campeão, vencendo 2 de 3 partidas! Obrigado a todos que enviaram seus Scriptbots! Agradecimentos especiais aos chifres do BestOpportunityBot, que exibiram um excelente caminho e fizeram pleno uso de todas as opções de ação.

Mapa 1

O assassino eliminou o BestOpportunityBot logo no início, e o resto do jogo foi muito chato. Detalhado play-by-play aqui.

  1. Assassino: 10 PV, 10 Danos Causados, 3 Danos Recebidos
  2. The Avoider v3: 10 PV, 0 dano causado, 0 dano recebido
  3. Tem que terminar de comer: 10 PV, 0 Dano causado, 0 Dano Recebido
  4. BestOpportunityBot: 0 PV, 3 danos causados, 10 danos causados

Mapa 2

O BestOpportunityBot fez a maior parte do trabalho nesta partida, mas o Assassin conseguiu eliminá-lo no final. Detalhado play-by-play aqui.

  1. Assassino: 2 PV, 10 Danos Causados, 9 Danos Recebidos
  2. BestOpportunityBot: 0 PV, 32 danos causados, 10 danos causados
  3. The Avoider v3: 0 PV, 0 dano causado, 12 danos causados
  4. Tem que terminar de comer: 0 PV, 0 Dano causado, 11 Dano recebido

Mapa 3

BestOpportunityBot empurrou todos para as armadilhas nesta partida. Muito legal. Jogo detalhado por jogo aqui.

  1. BestOpportunityBot: 10 PV, 30 Dano causado, 0 Dano Recebido
  2. Assassino: 0 PV, 0 Dano causado, 0 Dano Recebido
  3. Tem que terminar de comer: 0 PV, 0 Dano causado, 0 Dano Recebido
  4. The Avoider v3: 0 PV, 0 dano causado, 0 dano recebido

Obrigado por suas respostas! Como existem apenas 4 Scriptbots, estamos abandonando os planos do torneio para três partidas gratuitas para todos, uma em cada um dos mapas abaixo. O scriptbot com o maior recorde de vitórias vence. Em caso de empate, entraremos em morte súbita, em que o scriptbot que quebra o empate vence primeiro.


Sua tarefa, se você optar por aceitá-la, é codificar um Scriptbot que pode atravessar um mapa ASCII e destruir seus oponentes. Cada batalha terá a forma de um jogo baseado em turnos de ordem inicial aleatória, onde cada Scriptbot tem a chance de gastar seus pontos de energia (EP) para realizar ações. O script GameMaster alimentará a entrada e interpretará a saída de cada Scriptbot.

Meio Ambiente

Cada Scriptbot está contido dentro de seu próprio diretório onde se pode ler os mape statsarquivos e leitura / gravação para o dataarquivo. O dataarquivo pode ser usado para armazenar qualquer informação persistente que você possa achar útil.

O arquivo de estatísticas

O statsarquivo contém informações sobre seus oponentes e está formatado da seguinte forma. Cada jogador é representado em uma linha separada. A primeira coluna é um ID do jogador ( @significa você). A segunda coluna é a saúde desse jogador.

1,9HP
@,10HP
3,9HP
4,2HP

O arquivo de mapa

O maparquivo pode ser algo como isto ...

####################
#   #          #   #
# 1 #          # 2 #
#                  #
###              ###
#                  #
#      #           #
#       #     !    #
#        #         #
#       !####      #
#      ####!       #
#         #        #
#    !     #       #
#           #      #
#                  #
###              ###
#                  #
# 3 #          # @ #
#   #          #   #
####################

... ou isto...

######################################
#       # 1        #   @             #
#       #          #          #!     #
#       #          #          ####   #
#  #    #  #       #         !#!     #
#  #    #  #       #      #####      #
#  ###     #    ####    #         #  #
#          #    #!      ###       #  #
#  ######  #    #       #     #####  #
#  #!      #    ######  #        !#  #
#  ###     #            #         #  #
#  #    2  #            #   4     #  #
######################################

... ou isto...

###################
###!!!!!!#!!!!!!###
##!             !##
#! 1     !     2 !#
#!       !       !#
#!               !#
#!               !#
#!      !!!      !#
## !!   !!!   !! ##
#!      !!!      !#
#!               !#
#!               !#
#!       !       !#
#! 3     !     @ !#
##!             !##
###!!!!!!#!!!!!!###
###################

... ou pode parecer totalmente diferente. De qualquer maneira, os caracteres usados ​​e seu significado permanecerão os mesmos:

  • # Uma parede intransitável e impenetrável.
  • 1, 2, 3... Um número que representa um jogador inimigo. Esses números correspondem ao ID do jogador no statsarquivo.
  • !Uma armadilha. Os scriptbots que se mudarem para esses locais morrerão imediatamente.
  • @ A localização do seu Scriptbot.
  • Espaço aberto no qual você é livre para se deslocar.

Jogabilidade

O script GameMaster atribuirá uma ordem inicial aleatória aos Scriptbots. Os Scriptbots são invocados nessa ordem enquanto ainda estão vivos. Os Scriptbots têm 10 Pontos de Vida (HP) e começam com 10 Pontos de Energia (EP) a cada turno, que podem ser usados ​​para mover ou atacar. No início de cada turno, um Scriptbot recupera um HP ou recebe um EP adicional se já tiver 10 HP (portanto, rodar pode ser uma estratégia viável às vezes).

A batalha termina quando apenas um Scriptbot sobrevive ou quando 100 turnos se passaram. Se vários Scriptbots estiverem ativos no final de uma batalha, seu lugar será determinado com base nos seguintes critérios:

  1. Mais saúde.
  2. Maior dano causado.
  3. Maior dano recebido.

Entrada Scriptbot

O GameMaster imprimirá o mapa de batalha em um arquivo chamado do mapqual o Scriptbot terá acesso para leitura. O mapa pode ter qualquer forma, por isso é importante que o Scriptbot possa interpretá-lo. Seu Scriptbot será chamado com um parâmetro indicando EP. Por exemplo...

:> example_scriptbot.py 3

O Scriptbot será invocado até que ele gaste todo o seu EP ou no máximo 10 11 vezes. Os arquivos de mapa e estatísticas são atualizados antes de cada chamada.

Saída Scriptbot

Os scriptbots devem exibir suas ações como stout. Uma lista de ações possíveis é a seguinte:

  • MOVE <DIRECTION> <DISTANCE>

    Custa 1 PE por DISTANCE. O MOVEcomando move seu Scriptbot pelo mapa. Se houver algo como um muro ou outro Scriptbot, o GameMaster moverá o seu Scriptbot o máximo possível. Se um DISTANCEEP maior que o restante do Scriptbot for fornecido, o GameMaster moverá o Scriptbot até que o EP acabe. DIRECTIONpode ser qualquer um compasso de N, E, S, ou W.

  • PUSH <DIRECTION> <DISTANCE>

    Custa 1 PE por DISTANCE. O PUSHcomando permite que um Scriptbot mova outro Scriptbot. O Scriptbot que emite o comando deve estar diretamente próximo ao Scriptbot que está sendo enviado. Os dois Scriptbots se moverão na direção indicada se não houver um objeto bloqueando o Scriptbot que está sendo enviado. DIRECTIONe DISTANCEsão os mesmos que para o MOVEcomando.

  • ATTACK <DIRECTION>

    Custa um EP. O ATTACKcomando causa 1 dano a qualquer Scriptbot diretamente ao lado do Scriptbot emissor e na direção especificada. DIRECTIONé o mesmo que para o MOVEcomando.

  • PASS

    Termina a sua vez.

Idiomas suportados

Para manter isso razoável, aceitarei os seguintes idiomas:

  • Java
  • Node.js
  • Pitão
  • PHP

Você está limitado a bibliotecas que normalmente são fornecidas com seus idiomas prontas para uso. Por favor, não me faça localizar bibliotecas obscuras para que seu código funcione.

Submissão e Julgamento

Publique seu código-fonte Scriptbot abaixo e dê um nome legal! Por favor, liste também a versão do idioma que você usou. Todos os Scriptbots serão revisados ​​para o tomfoolery, portanto, comente bem e não ofusque seu código.

Você pode enviar mais de uma entrada, mas faça com que sejam entradas totalmente exclusivas, e não versões da mesma entrada. Por exemplo, você pode codificar um bot de Zerg Rush e um bot de Gorilla Warfare. Isso é bom. Não publique Zerg Rush v1, Zerg Rush v2, etc.

No dia 7 de novembro, coletarei todas as respostas e as que passarem na revisão inicial serão adicionadas ao suporte de torneio. O campeão recebe a resposta aceita. O suporte ideal é mostrado abaixo. Como provavelmente não haverá exatamente 16 entradas, alguns colchetes podem acabar sendo apenas três ou até dois bots. Vou tentar tornar o suporte o mais justo possível. Qualquer favoritismo necessário (no caso de uma semana de despedida ser necessária, por exemplo) será dado aos bots que foram enviados primeiro.

BOT01_
BOT02_|
BOT03_|____
BOT04_|    |
           |
BOT05_     |
BOT06_|___ |
BOT07_|  | |
BOT08_|  | |_BOT ?_
         |___BOT ?_|
BOT09_    ___BOT ?_|___CHAMPION!
BOT10_|  |  _BOT ?_|
BOT11_|__| |
BOT12_|    |
           |
BOT13_     |
BOT14_|____|
BOT15_|
BOT16_|

Perguntas e Respostas

Tenho certeza de que perdi alguns detalhes, fique à vontade para fazer perguntas!

Podemos confiar que um arquivo de mapa esteja sempre cercado por # símbolos? Caso contrário, o que acontece no caso de um bot tentar sair do mapa? - BrainSteel

Sim, o mapa sempre será delimitado por # e seu Scriptbot começará dentro desses limites.

Se não houver nenhum bot presente na direção especificada em um comando PUSH, como o comando funciona? - BrainSteel

O GameMaster não fará nada, será gasto zero EP e o Scriptbot será chamado novamente.

O EP não utilizado se acumula? - feersum

Não. Cada Scriptbot começará a rodada / turno com 10 EP. Qualquer EP não gasto será desperdiçado.

Acho que entendi, mas apenas para esclarecer: com os robôs A e B, é a ordem dos eventos A @ 10EP-> MOVE MAP_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATAQUE MAP_UPDATE B @ 9EP-> ATAQUE ..., ou A @ 10EP-> MOVER A @ 9EP-> ATAQUE ... MAP_UPDATE B @ 10EP-> EMPURRAR B @ 9EP-> ATAQUE ... MAP_UPDATE? Em outras palavras, toda ação em um loop de consulta do controlador-bot é atômica? Se sim, por que o loop? Por que não retornar um único arquivo com todas as ações a serem concluídas? Caso contrário, os robôs terão que gravar seus próprios arquivos de estado para acompanhar as seqüências de várias ações. O arquivo de mapa / estatísticas só será válido antes da primeira ação. - COTO

Seu segundo exemplo está próximo, mas não totalmente certo. Durante um turno, o Scriptbot é invocado repetidamente até que o EP seja gasto, ou no máximo 11 vezes. Os arquivos de mapa e estatísticas são atualizados antes de cada chamada. O loop é útil no caso de um bot fornecer saída inválida. O GameMaster irá lidar com a saída inválida e involuirá o bot novamente, dando ao bot a chance de corrigir seu erro.

você lançará o script GameMaster para teste? - IchBinKeinBaum

O script GameMaster não será lançado. Eu o incentivaria a criar um mapa e um arquivo de estatísticas para testar o comportamento do seu bot.

Se o robô A empurra o robô B para uma armadilha, o robô A é creditado com pontos de "dano causado" iguais à saúde atual do robô B? - Mike Sweeney

Sim, é uma boa ideia. Um bot receberá pontos de dano iguais à saúde de qualquer bot que ele empurre em uma armadilha.

Rip Leeb
fonte
Podemos confiar que um maparquivo está sempre cercado por #símbolos? Caso contrário, o que acontece no caso de um bot tentar sair do mapa?
precisa saber é o seguinte
@BrainSteel Sim, o mapa sempre será limitado #e seu Scriptbot começará dentro desses limites.
Rip Leeb
3
Se você tem certeza de que perdeu algo, por que não publicá-lo na sandbox ?
Martin Ender
2
@ MartinBüttner Eu pensei sobre isso completamente. Eu estava apenas tentando ser amigável e deixar claro que as perguntas são bem-vindas.
Rip Leeb
1
Se o robô A empurra o robô B para uma armadilha, o robô A é creditado com pontos de "dano causado" iguais à saúde atual do robô B?
Logic Knight

Respostas:

1

Assassino (Java 1.7)

Tenta matar inimigos sempre que possível, caso contrário, move um campo. É muito bom encontrar o caminho para um inimigo, mas não faz nada para esconder de outros bots.

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class Assassin {
    private final Path dataPath = Paths.get("data");
    private final Path mapPath = Paths.get("map");
    private final Path statsPath = Paths.get("stats");
    private final List<Player> players = new ArrayList<>();
    private final int energy;
    private Map map = null;

    public Assassin(int energy) {
        this.energy = energy;
    }

    private void doSomething() {
        if (dataFileEmpty()) {
            calculateTurn();
        }
        printStoredOutput();
    }

    private boolean dataFileEmpty() {
        try {
            return !Files.exists(dataPath) || Files.size(dataPath)  == 0;
        } catch (IOException e) {
            return true;
        }
    }

    private void printStoredOutput() {
        try {
            List<String> lines = Files.readAllLines(dataPath, StandardCharsets.UTF_8);
            //print first line
            System.out.println(lines.get(0));           
            //delete first line
            lines.remove(0);
            Files.write(dataPath, lines, StandardCharsets.UTF_8);
        } catch (IOException e) {
            System.out.println("PASS");
        }
    }

    private void calculateTurn() {
        try {
            readStats();
            readMap();
            if (!tryKill())  {
                sneakCloser();
            }
        } catch (IOException e) {}
    }

    private void readStats() throws IOException{
        List<String> stats = Files.readAllLines(statsPath, StandardCharsets.UTF_8);
        for (String stat : stats) {
            String[] infos = stat.split(",");
            int hp = Integer.parseInt(infos[1].replace("HP", ""));
            players.add(new Player(stat.charAt(0), hp));
        }
    }

    private void readMap() throws IOException{
        List<String> lines = Files.readAllLines(mapPath, StandardCharsets.UTF_8);
        Field[][] fields = new Field[lines.size()][lines.get(0).length()];
        for (int row = 0; row < lines.size(); row++) {
            String line = lines.get(row);
            for (int col = 0; col < line.length(); col++) {
                fields[row][col] = new Field(line.charAt(col), row, col);
            }
        }
        map = new Map(fields);
    }

    private boolean tryKill() {     
        Field me = map.getMyField();
        for (Field field : map.getEnemyFields()) {
            for (Field surrField : map.surroundingFields(field)) { 
                List<Direction> dirs = map.path(me, surrField);
                if (dirs != null) {
                    for (Player player : players) {
                        //can kill this player
                        int remainderEnergy = energy - dirs.size() - player.hp;
                        if (player.id == field.content && remainderEnergy >= 0) {
                            //save future moves
                            List<String> commands = new ArrayList<>();
                            for (Direction dir : dirs) {
                                commands.add("MOVE " + dir + " 1");
                            }
                            //attacking direction
                            Direction attDir = surrField.dirsTo(field).get(0);
                            for (int i = 0; i < player.hp; i++) {
                                commands.add("ATTACK " + attDir);                               
                            }
                            if (remainderEnergy > 0) {
                                commands.add("PASS");
                            }
                            try {
                                Files.write(dataPath, commands, StandardCharsets.UTF_8);
                            } catch (IOException e) {}
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private void sneakCloser() {        
        Field me = map.getMyField();
        for (Direction dir : Direction.values()) {
            if (!map.move(me, dir).blocked()) {
                List<String> commands = new ArrayList<>();
                commands.add("MOVE " + dir + " 1");
                commands.add("PASS");
                try {
                    Files.write(dataPath, commands, StandardCharsets.UTF_8);
                } catch (IOException e) {}
                return;
            }
        }
    }

    public static void main(String[] args) {
        try {
            new Assassin(Integer.parseInt(args[0])).doSomething();
        } catch (Exception e) {
            System.out.println("PASS");
        }
    }

    class Map {
        private Field[][] fields;

        public Map(Field[][] fields) {
            this.fields = fields;
        }

        public Field getMyField() {
            for (Field[] rows : fields) {
                for (Field field : rows) {
                    if (field.isMyPos()) {
                        return field;
                    }
                }
            }
            return null; //should never happen
        }   

        public List<Field> getEnemyFields() {
            List<Field> enemyFields = new ArrayList<>();
            for (Field[] rows : fields) {
                for (Field field : rows) {
                    if (field.hasEnemy()) {
                        enemyFields.add(field);
                    }
                }
            }
            return enemyFields;
        }

        public List<Field> surroundingFields(Field field) {
            List<Field> surrFields = new ArrayList<>();
            for (Direction dir : Direction.values()) {
                surrFields.add(move(field, dir));
            }
            return surrFields;
        }

        public Field move(Field field, Direction dir) {
            return fields[field.row + dir.rowOffset][field.col + dir.colOffset];
        }

        public List<Direction> path(Field from, Field to) {
            List<Direction> dirs = new ArrayList<>();
            Field lastField = from;
            boolean changed = false;

            for (int i = 0; i < energy && lastField != to; i++) {
                List<Direction> possibleDirs = lastField.dirsTo(to);
                changed = false;
                for (Direction dir : possibleDirs) {
                    Field nextField = move(lastField, dir);
                    if (!nextField.blocked()) {
                        lastField = nextField;
                        changed = true;
                        dirs.add(dir);
                        break;
                    }
                }
                if (!changed) {
                    return null; //not possible
                }
            }
            if (lastField != to) {
                return null; //not enough energy
            }           
            return dirs;
        }
    }

    class Field {
        private char content;
        private int row;
        private int col;

        public Field(char content, int row, int col) {
            this.content = content;
            this.row = row;
            this.col = col;
        }

        public boolean hasEnemy() {
            return content == '1' || content == '2' || content == '3' || content == '4';
        }

        public List<Direction> dirsTo(Field field) {
            List<Direction> dirs = new ArrayList<>();
            int distance = Math.abs(row - field.row) + Math.abs(col - field.col);

            for (Direction dir : Direction.values()) {
                int dirDistance = Math.abs(row - field.row + dir.rowOffset) + 
                        Math.abs(col - field.col + dir.colOffset);
                if (dirDistance < distance) {
                    dirs.add(dir);
                }
            }
            if (dirs.size() == 1) { //add near directions
                for (Direction dir : dirs.get(0).nearDirections()) {
                    dirs.add(dir);
                }
            }
            return dirs;
        }

        public boolean isMyPos() {
            return content == '@';
        }

        public boolean blocked() {
            return content != ' ';
        }
    }

    class Player {
        private char id;
        private int hp;

        public Player(char id, int hp) {
            this.id = id;
            this.hp = hp;
        }
    }

    enum Direction {
        N(-1, 0),S(1, 0),E(0, 1),W(0, -1);

        private final int rowOffset;
        private final int colOffset;

        Direction(int rowOffset, int colOffset) {
            this.rowOffset = rowOffset;
            this.colOffset = colOffset;
        }

        public Direction[] nearDirections() {
            Direction[] dirs = new Direction[2];
            for (int i = 0; i < Direction.values().length; i++) {
                Direction currentDir = Direction.values()[i];
                if (Math.abs(currentDir.rowOffset) != Math.abs(this.rowOffset)) {
                    dirs[i%2] = currentDir;
                }
            }
            return dirs;
        }
    }
}
CommonGuy
fonte
Em qual versão do Java isso foi escrito?
usar o seguinte
@Nate eu usei 1.7.
precisa saber é o seguinte
3

O Avoider v3

Um bot de mente simples. Tem medo de outros robôs e armadilhas. Não vai atacar. Ele ignora o arquivo de estatísticas e não pensa no futuro.

Isso é principalmente um teste para ver como as regras funcionariam e como um oponente idiota para outros concorrentes.

Edit: Agora passará quando nenhum movimento é melhor.

Edit2: robôs podem ser '1234' em vez de '123'

O código Python:

import sys
maxmoves = int(sys.argv[1])

ORTH = [(-1,0), (1,0), (0,-1), (0,1)]
def orth(p):
    return [(p[0]+dx, p[1]+dy) for dx,dy in ORTH]

mapfile = open('map').readlines()[::-1]
arena = dict( ((x,y),ch) 
    for y,row in enumerate(mapfile)
    for x,ch in enumerate(row.strip()) )
loc = [loc for loc,ch in arena.items() if ch == '@'][0]

options = []
for direc in ORTH:
    for dist in range(maxmoves+1):
        newloc = (loc[0]+direc[0]*dist, loc[1]+direc[1]*dist)
        if arena.get(newloc) in '#!1234':
            break
        penalty = dist * 10  # try not to use moves too fast
        if newloc == loc:
            penalty += 32   # incentive to move
        for nextto in orth(newloc):
            ch = arena.get(nextto)
            if ch == '#':
                penalty += 17  # don't get caught againt a wall
            elif ch in '1234':
                penalty += 26  # we are afraid of other robots
            elif ch == '!':
                penalty += 38  # we are very afraid of deadly traps
        options.append( [penalty, dist, direc] )

penalty, dist, direc = min(options)
if dist > 0:
    print 'MOVE', 'WESN'[ORTH.index(direc)], dist
else:
    print 'PASS'  # stay still?
Cavaleiro Lógico
fonte
elif ch in '123':Você quer olhar para pelo menos 1234. Se você é bot 3, a lista oponente seria 124.
Rip Leeb
1
@Nate Obrigado. Eu mudei o bot. Você pode deixar isso claro nas regras. Eu posso não ser o único que não entendeu isso.
Logic Knight
3

BestOpportunityBot

Isso acabou sendo um pouco mais longo do que eu pretendia ... e não tenho certeza se entendi completamente as regras para as curvas, então veremos como isso acontece.

from sys import argv
from enum import IntEnum

with open("map") as map_file:
    map_lines = map_file.read().splitlines()

with open("stats") as stats_file:
    stats_data = stats_file.read().splitlines()

ep = argv[1]

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(lhs, rhs):
        if (type(rhs) == tuple or type(rhs) == list):
            return Point(lhs.x + rhs[0], lhs.y + rhs[1])
        return Point(lhs.x + rhs.x, lhs.y + rhs.y)

    def __sub__(lhs, rhs):
        if (type(rhs) == tuple or type(rhs) == list):
            return Point(lhs.x - rhs[0], lhs.y - rhs[1])
        return Point(lhs.x - rhs.x, lhs.y - rhs.y)

    def __mul__(lhs, rhs):
        return Point(lhs.x * rhs, lhs.y * rhs)

    def __str__(self):
        return "(" + str(self.x) + ", " + str(self.y) + ")"

    def __repr__(self):
        return "(" + str(self.x) + ", " + str(self.y) + ")"

    def __eq__(lhs, rhs):
        return lhs.x == rhs.x and lhs.y == rhs.y

    def __hash__(self):
        return hash(self.x) * 2**32 + hash(self.y)

    def reverse(self):
        return Point(self.y, self.x)

dirs = (Point(0, 1), Point(1, 0), Point(0, -1), Point(-1, 0))

class Bot:
    def __init__(self, pos, ch, hp):
        self.pos = pos
        self.ch = ch
        self.hp = hp

    def __str__(self):
        return str(self.pos) + " " + str(self.ch) + " " + str(self.hp)

    def __repr__(self):
        return str(self.pos) + " " + str(self.ch) + " " + str(self.hp)

class MyBot(Bot):
    def __init__(self, pos, ch, hp, ep):
        self.ep = ep
        super().__init__(pos, ch, hp)

    def __str__(self):
        return super().__str__() + " " + self.ep

    def __repr__(self):
        return super().__repr__() + " " + self.ep

class Arena:
    def __init__(self, orig_map):
        self._map = list(zip(*orig_map[::-1]))
        self.bots = []
        self.me = None

    def __getitem__(self, indexes):
        if (type(indexes) == Point):
            return self._map[indexes.x][indexes.y]
        return self._map[indexes[0]][indexes[1]]

    def __str__(self):
        output = ""
        for i in range(len(self._map[0]) - 1, -1, -1):
            for j in range(len(self._map)):
                output += self._map[j][i]
            output += "\n"
        output = output[:-1]
        return output

    def set_bot_loc(self, bot):
        for x in range(len(self._map)):
            for y in range(len(self._map[x])):
                if self._map[x][y] == bot.ch:
                    bot.pos = Point(x, y)

    def set_bots_locs(self):
        self.set_bot_loc(self.me)
        for bot in self.bots:
            self.set_bot_loc(bot)

    def adjacent_bots(self, pos=None):
        if type(pos) == None:
            pos = self.me.pos
        output = []
        for bot in self.bots:
            for d in dirs:
                if bot.pos == pos + d:
                    output.append(bot)
                    break
        return output

    def adjacent_bots_and_dirs(self):
        output = {}
        for bot in self.bots:
            for d in dirs:
                if bot.pos == self.me.pos + d:
                    yield bot, d
                    break
        return output

    def look(self, position, direction, distance, ignore='', stopchars='#1234'):
        current = position + direction
        output = []
        for i in range(distance):
            if (0 <= current.x < len(self._map) and
                0 <= current.y < len(self._map[current.x]) and
                (self[current] not in stopchars or self[current] in ignore)):
                output += self[current]
                current = current + direction
            else:
                break
        return output

    def moveable(self, position, direction, distance):
        current = position + direction
        output = []
        for i in range(distance):
            if (0 <= current.x < len(self._map) and
                0 <= current.y < len(self._map[current.x]) and
                self[current] == ' '):
                output += self[current]
            else:
                break
        return output


    def danger(self, pos, ignore=None): # damage that can be inflicted on me
        output = 0
        adjacents = self.adjacent_bots(pos)
        hps = [bot.hp for bot in adjacents if bot != ignore]
        if len(hps) == 0:
            return output

        while max(hps) > 0:
            if 0 in hps:
                hps.remove(0)

            for i in range(len(hps)):
                if hps[i] == min(hps):
                    hps[i] -= 1
                output += 1
        return output

    def path(self, pos): # Dijkstra's algorithm adapted from https://gist.github.com/econchick/4666413
        visited = {pos: 0}
        path = {}

        nodes = set()

        for i in range(len(self._map)):
            for j in range(len(self._map[0])):
                nodes.add(Point(i, j))

        while nodes:
            min_node = None
            for node in nodes:
                if node in visited:
                    if min_node is None:
                        min_node = node
                    elif visited[node] < visited[min_node]:
                        min_node = node

            if min_node is None:
                break

            nodes.remove(min_node)
            current_weight = visited[min_node]

            for _dir in dirs:
                new_node = min_node + _dir
                if self[new_node] in ' 1234':
                    weight = current_weight + 1
                    if new_node not in visited or weight < visited[new_node]:
                        visited[new_node] = weight
                        path[new_node] = min_node

        return visited, path

class MoveEnum(IntEnum):
    Null = 0
    Pass = 1
    Attack = 2
    Move = 3
    Push = 4

class Move:
    def __init__(self, move=MoveEnum.Null, direction=Point(0, 0), distance=0):
        self.move = move
        self.dir = direction
        self.dis = distance

    def __repr__(self):
        if self.move == MoveEnum.Null:
            return "NULL"
        elif self.move == MoveEnum.Pass:
            return "PASS"
        elif self.move == MoveEnum.Attack:
            return "ATTACK " + "NESW"[dirs.index(self.dir)]
        elif self.move == MoveEnum.Move:
            return "MOVE " + "NESW"[dirs.index(self.dir)] + " " + str(self.dis)
        elif self.move == MoveEnum.Push:
            return "PUSH " + "NESW"[dirs.index(self.dir)] + " " + str(self.dis)

arena = Arena(map_lines)
arena.me = MyBot(Point(0, 0), '@', 0, ep)

for line in stats_data:
    if line[0] == '@':
        arena.me.hp = int(line[2:-2])
    else:
        arena.bots.append(Bot(Point(0, 0), line[0], int(line[2:-2])))

arena.set_bots_locs()

current_danger = arena.danger(arena.me.pos)

moves = [] # format (move, damage done, danger, energy)

if arena.me.ep == 0:
    print(Move(MoveEnum.Pass))
    exit()

for bot, direction in arena.adjacent_bots_and_dirs():
    # Push to damage
    pushable_to = arena.look(arena.me.pos, direction,
                             arena.me.ep + 1, ignore=bot.ch)
    if '!' in pushable_to:
        distance = pushable_to.index('!')
        danger = arena.danger(arena.me.pos + (direction * distance), bot)
        danger -= current_danger
        moves.append((Move(MoveEnum.Push, direction, distance),
                      bot.hp, danger, distance))

    # Push to escape
    pushable_to = arena.look(arena.me.pos, direction,
                             arena.me.ep + 1, ignore=bot.ch, stopchars='#1234!')
    for distance in range(1, len(pushable_to)):
        danger = arena.danger(arena.me.pos + (direction * distance), bot)
        danger += bot.hp
        danger -= current_danger
        moves.append((Move(MoveEnum.Push, direction, distance),
                      0, danger, distance))

    # Attack
    bot.hp -= 1
    danger = arena.danger(arena.me.pos) - current_danger
    moves.append((Move(MoveEnum.Attack, direction), 1, danger, 1))
    bot.hp += 1

culled_moves = []

for move in moves: # Cull out attacks and pushes that result in certain death
    if current_danger + move[2] < arena.me.hp:
        culled_moves.append(move)

if len(culled_moves) == 1:
    print(culled_moves[0][0])
    exit()
elif len(culled_moves) > 1:
    best_move = culled_moves[0]

    for move in culled_moves:
        if move[1] - move[2] > best_move[1] - best_move[2]:
            best_move = move
        if move[1] - move[2] == best_move[1] - best_move[2] and move[3] < best_move[3]:
            best_move = move

    print (best_move[0])
    exit()

# Can't attack or push without dying, time to move

moves = []

if current_danger > 0: # Try to escape
    for direction in dirs:
        moveable_to = arena.moveable(arena.me.pos, direction, ep)

        i = 1

        for space in moveable_to:
            danger = arena.danger(arena.me.pos + (direction * i))
            danger -= current_danger
            moves.append((Move(MoveEnum.Move, direction, i), 0, danger, i))
            i += 1

    if len(moves) == 0: # Trapped and in mortal danger, attack biggest guy
        adjacents = arena.adjacent_bots()
        biggest = adjacents[0]
        for bot in adjacents:
            if biggest.hp < bot.hp:
                biggest = bot
        print (Move(MoveEnum.Attack, biggest.pos - arena.me.pos))
        exit()

    best_move = moves[0]
    for move in moves:
        if ((move[2] < best_move[2] and best_move[2] >= arena.me.hp) or
            (move[2] == best_move[2] and move[3] < best_move[3])):
            best_move = move

    print(best_move[0])
    exit()

else: # Seek out closest target with lower health
    distances, path = arena.path(arena.me.pos)

    bot_dists = list((bot, distances[bot.pos]) for bot in arena.bots)

    bot_dists.sort(key=lambda x: x[1])

    target = bot_dists[0]

    for i in range(len(bot_dists)):
        if bot_dists[i][0].hp <= arena.me.hp:
            target = bot_dists[i]
            break

    pos = target[0].pos
    for i in range(target[1] - 1):
        pos = path[pos]

    print (Move(MoveEnum.Move, pos - arena.me.pos, 1))
    exit()

# Shouldn't get here, but I might as well do something
print (Move(MoveEnum.Pass))

fonte
em qual versão do python você escreveu isso?
Rip Leeb
@Nate 3.4.1 no win32
Obrigado por enviar este @horns. Foi muito divertido de assistir!
Rip Leeb
1

Tem que terminar de comer

Pitão:

import sys
print 'PASS'
Timtech
fonte
1
Eu ri disso - e por que diabos import sys?
tomsmeding
1
@tomsmeding Bem, eu tive que copiar algumas coisas. E eu pensei que, caso eu precisasse ler alguns argumentos :) quando terminar de comer, é claro.
Timtech