Scriptbot Warz!

14

Scriptbot Warz!


¡Los resultados están listos y Assassin es nuestro campeón, ganando 2 de 3 partidos! ¡Gracias a todos los que enviaron sus Scriptbots! Un agradecimiento especial a los cuernos para BestOpportunityBot, que mostró una excelente ruta e hizo pleno uso de todas las opciones de acción.

Mapa 1

Assassin eliminó BestOpportunityBot desde el principio, y el resto del partido fue bastante aburrido. Juego detallado por juego aquí.

  1. Asesino: 10 HP, 10 Daño infligido, 3 Daño recibido
  2. The Avoider v3: 10 HP, 0 Daño infligido, 0 Daño recibido
  3. Tengo que terminar de comer: 10 HP, 0 Daño infligido, 0 Daño recibido
  4. BestOpportunityBot: 0 HP, 3 Daño infligido, 10 Daño recibido

Mapa 2

BestOpportunityBot hizo la mayor parte del trabajo en este partido, pero Assassin pudo eliminarlo al final. Juego detallado por juego aquí.

  1. Asesino: 2 HP, 10 Daño infligido, 9 Daño recibido
  2. BestOpportunityBot: 0 HP, 32 Daño infligido, 10 Daño recibido
  3. The Avoider v3: 0 HP, 0 Daño infligido, 12 Daño recibido
  4. Tengo que terminar de comer: 0 HP, 0 Daño infligido, 11 Daño recibido

Mapa 3

BestOpportunityBot empujó a todos a las trampas en este partido. Muy genial. Juego detallado por juego aquí.

  1. BestOpportunityBot: 10 HP, 30 Daño infligido, 0 Daño recibido
  2. Asesino: 0 HP, 0 Daño infligido, 0 Daño recibido
  3. Tengo que terminar de comer: 0 HP, 0 Daño infligido, 0 Daño recibido
  4. The Avoider v3: 0 HP, 0 Daño infligido, 0 Daño recibido

¡Gracias por tus respuestas! Dado que solo hay 4 Scriptbots, estamos abandonando los planes del torneo para tres partidos gratuitos, uno en cada uno de los mapas a continuación. El scriptbot con el mayor récord de victorias gana. En caso de empate, entraremos en una muerte súbita en la que el scriptbot que rompa el empate primero gana.


Su tarea, si elige aceptarla, es codificar un Scriptbot que pueda atravesar un mapa ASCII y destruir a sus oponentes. Cada batalla tomará la forma de un juego basado en turnos de orden aleatorio donde cada Scriptbot tiene la oportunidad de gastar sus puntos de energía (EP) para realizar acciones. La secuencia de comandos GameMaster alimentará la entrada e interpretará la salida de cada Scriptbot.

Ambiente

Cada Scriptbot está contenida dentro de su propio directorio en el que se puede leer de las mapy los statsarchivos y de lectura / escritura en el dataarchivo. El dataarchivo se puede usar para almacenar cualquier información persistente que pueda resultarle útil.

El archivo de estadísticas

El statsarchivo contiene información sobre tus oponentes y tiene el siguiente formato. Cada jugador está representado en una fila separada. La primera columna es una identificación de jugador ( @significa usted). La segunda columna es la salud de ese jugador.

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

El archivo de mapa

El maparchivo podría verse así ...

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

... o esto...

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

... o esto...

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

... o podría verse totalmente diferente. De cualquier manera, los caracteres utilizados y su significado seguirán siendo los mismos:

  • # Un muro, intransitable e impenetrable.
  • 1, 2, 3... Un número que representa a un jugador enemigo. Estos números corresponden a la ID del jugador en el statsarchivo.
  • !Una trampa. Los scriptbots que se mueven a estos lugares morirán de inmediato.
  • @ La ubicación de tu Scriptbot.
  • Espacio abierto en el que puede moverse libremente.

Como se Juega

El script GameMaster asignará un orden de inicio aleatorio a los Scriptbots. Los Scriptbots se invocan en este orden mientras todavía están vivos. Los Scriptbots tienen 10 puntos de vida (HP) y comienzan con 10 puntos de energía (EP) cada turno, que pueden usar para moverse o atacar. Al comienzo de cada turno, un Scriptbot sanará por un HP, o se le otorgará un EP adicional si ya está en 10 HP (por lo tanto, la ejecución puede ser una estrategia viable a veces).

La batalla termina cuando solo un Scriptbot sobrevive o cuando han pasado 100 turnos. Si varios Scriptbots están vivos al final de una batalla, su lugar se determina según los siguientes criterios:

  1. La mayoría de la salud.
  2. La mayoría del daño infligido.
  3. La mayoría del daño recibido.

Scriptbot Input

GameMaster imprimirá el mapa de batalla en un archivo llamado desde el mapcual el Scriptbot tendrá acceso para leer. El mapa puede tomar cualquier forma, por lo que es importante que el Scriptbot pueda interpretarlo. Su Scriptbot se invocará con un parámetro que indica EP. Por ejemplo...

:> example_scriptbot.py 3

Se invocará al Scriptbot hasta que gaste todo su EP o un máximo de 10 11 veces. Los archivos de mapas y estadísticas se actualizan antes de cada invocación.

Salida de Scriptbot

Los scriptbots deberían mostrar sus acciones como robustas. Una lista de posibles acciones son las siguientes:

  • MOVE <DIRECTION> <DISTANCE>

    Cuesta 1 EP por DISTANCE. El MOVEcomando mueve su Scriptbot por el mapa. Si hay algo en el camino, como un muro u otro Scriptbot, GameMaster moverá su Scriptbot lo más lejos posible. Si se da un DISTANCEEP mayor que el restante del Scriptbot, GameMaster moverá el Scriptbot hasta que se agote su EP. DIRECTIONpuede ser cualquier dirección de la brújula de N, E, S, o W.

  • PUSH <DIRECTION> <DISTANCE>

    Cuesta 1 EP por DISTANCE. El PUSHcomando permite que un Scriptbot mueva otro Scriptbot. El Scriptbot que emite el comando debe estar directamente al lado del Scriptbot que se está empujando. Ambos Scriptbots se moverán en la dirección indicada si no hay un objeto que bloquee el Scriptbot que se está empujando. DIRECTIONy DISTANCEson los mismos que para el MOVEcomando.

  • ATTACK <DIRECTION>

    Cuesta un EP. El ATTACKcomando causa 1 daño a cualquier Scriptbot directamente al lado del Scriptbot emisor y en la dirección especificada. DIRECTIONes lo mismo que para el MOVEcomando.

  • PASS

    Termina tu turno.

Idiomas soportados

Para mantener esto razonable para mí, aceptaré los siguientes idiomas:

  • Java
  • Node.js
  • Pitón
  • PHP

Está limitado a bibliotecas que normalmente se empaquetan con sus idiomas listos para usar. No me haga localizar bibliotecas oscuras para que su código funcione.

Sumisión y Juicio

¡Publique su código fuente Scriptbot a continuación y asígnele un nombre genial! Indique también la versión del idioma que utilizó. Todos los Scriptbots serán revisados ​​por tonterías, así que por favor comente bien y no ofusque su código.

Puede enviar más de una entrada, pero hágalos totalmente únicos y no versiones de la misma entrada. Por ejemplo, es posible que desee codificar un bot Zerg Rush y un bot Gorilla Warfare. Esta bien. No publique Zerg Rush v1, Zerg Rush v2, etc.

El 7 de noviembre recogeré todas las respuestas y las que pasen la revisión inicial se agregarán a un grupo de torneos. El campeón obtiene la respuesta aceptada. El soporte ideal se muestra a continuación. Como es probable que no haya exactamente 16 entradas, algunos paréntesis pueden terminar siendo solo tres o incluso dos bots. Intentaré hacer que el soporte sea lo más justo posible. Cualquier favoritismo necesario (en el caso de que se necesite una semana de descanso, por ejemplo) se dará a los bots que se enviaron primero.

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

Q&A

Estoy seguro de que me he perdido algunos detalles, ¡así que siéntase libre de hacer preguntas!

¿Podemos confiar en que un archivo de mapa siempre está rodeado por # símbolos? Si no, ¿qué sucede en caso de que un bot intente salir del mapa? - BrainSteel

Sí, el mapa siempre estará delimitado por # y su Scriptbot comenzará dentro de estos límites.

Si no hay un bot presente en la dirección especificada en un comando PUSH, ¿cómo funciona el comando? - BrainSteel

GameMaster no hará nada, se gastará cero EP y se volverá a llamar al Scriptbot.

¿El EP no utilizado se acumula? - feersum

No. Cada Scriptbot comenzará la ronda / turno con 10 EP. Cualquier EP no gastado se desperdiciará.

Creo que lo tengo, pero solo para aclarar: con los bots A y B, es el orden de los eventos A @ 10EP-> MOVER MAP_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATTACK MAP_UPDATE B @ 9EP-> ATTACK ..., o A @ 10EP-> MOVE A @ 9EP-> ATTACK ... MAP_UPDATE B @ 10EP-> PUSH B @ 9EP-> ATTACK ... MAP_UPDATE? En otras palabras, ¿todas las acciones en un bucle de consulta controlador-bot son atómicas? Si es así, ¿por qué el bucle? ¿Por qué no devolver un solo archivo con todas las acciones que deben completarse? De lo contrario, los bots tendrán que escribir sus propios archivos de estado para realizar un seguimiento de las secuencias de acción múltiple. El archivo de mapa / estadísticas solo será válido antes de la primera acción. - COTO

Su segundo ejemplo está cerca, pero no del todo bien. Durante un turno, el Scriptbot se invoca repetidamente hasta que se gasta su EP, o un máximo de 11 veces. Los archivos de mapas y estadísticas se actualizan antes de cada invocación. El bucle es útil en el caso de que un bot produzca resultados no válidos. GameMaster se ocupará de la salida no válida e involucrará nuevamente al bot, dándole al bot la oportunidad de corregir su error.

¿lanzarán el script GameMaster para probar? - IchBinKeinBaum

El script GameMaster no se lanzará. Te animo a que crees un mapa y un archivo de estadísticas para probar el comportamiento de tu bot.

Si el robot A empuja al robot B hacia una trampa, ¿se le atribuye al robot A puntos de "daño infligido" igual a la salud actual del robot B? - Mike Sweeney

Si esa es una buena idea. A un bot se le otorgarán puntos de daño iguales a la salud de cualquier bot que empuje a una trampa.

Rip Leeb
fuente
¿Podemos confiar en que un maparchivo siempre está rodeado de #símbolos? Si no, ¿qué sucede en caso de que un bot intente salir del mapa?
BrainSteel
@BrainSteel Sí, el mapa siempre estará delimitado #y su Scriptbot comenzará dentro de estos límites.
Rip Leeb
3
Si estás seguro de que te has perdido algo, ¿por qué no publicarlo en el sandbox ?
Martin Ender
2
@ MartinBüttner He pensado esto detenidamente. Solo intentaba ser amigable y dejar en claro que las preguntas son bienvenidas.
Rip Leeb
1
Si el robot A empuja al robot B hacia una trampa, ¿se le atribuye al robot A puntos de "daño infligido" igual a la salud actual del robot B?
Logic Knight el

Respuestas:

1

Asesino (Java 1.7)

Intenta matar enemigos siempre que sea posible; de ​​lo contrario, se mueve un campo. Es bastante bueno para encontrar el camino hacia un enemigo, pero no hace nada para esconderse de otros 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
fuente
¿En qué versión de Java se escribió esto?
Rip Leeb
@Nate utilicé 1.7.
CommonGuy
3

El Avoider v3

Un bot de mente simple. Teme a otros robots y trampas. No atacará. Ignora el archivo de estadísticas y no piensa en el futuro.

Esto es principalmente una prueba para ver cómo funcionarían las reglas, y como un oponente tonto para otros competidores.

Editar: Ahora PASARÁ cuando ningún MOVIMIENTO sea mejor.

Edit2: los robots pueden ser '1234' en lugar de '123'

El código de 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?
Caballero Lógico
fuente
elif ch in '123':Debes buscar al menos 1234. Si eres bot 3, la lista de oponentes sería 124.
Rip Leeb
1
@Nate Gracias. He cambiado el bot. Es posible que desee aclarar esto en las reglas. Puede que no sea el único que haya entendido mal esto.
Logic Knight el
3

BestOpportunityBot

Esto terminó siendo un poco más de lo que pretendía ... y no estoy seguro si entiendo completamente las reglas para los turnos, así que veremos cómo funciona.

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

fuente
¿En qué versión de Python escribiste esto?
Rip Leeb
@Nate 3.4.1 en win32
Gracias por enviar este @horns. Fue muy divertido de ver!
Rip Leeb
1

Tengo que terminar de comer

Pitón:

import sys
print 'PASS'
Timtech
fuente
1
Me reí con eso, ¿y por qué demonios import sys?
Tomsmeding
1
@tomsmeding Bueno, tuve que copiar algunas cosas. Y pensé en caso de que alguna vez necesite leer algunos argumentos :) cuando termine de comer, por supuesto.
Timtech