Scriptbot Warz!


14

Scriptbot Warz!


I risultati sono arrivati e Assassin è il nostro campione, vincendo 2 partite su 3! Grazie a tutti coloro che hanno inviato i loro Scriptbot! Un ringraziamento speciale alle trombe per BestOpportunityBot che ha mostrato un percorso eccellente e ha fatto pieno uso di tutte le opzioni di azione.

Mappa 1

Assassin ha eliminato BestOpportunityBot all'inizio e il resto della partita è stato piuttosto noioso. Play-by-play dettagliato qui.

  1. Assassino: 10 HP, 10 danni inflitti, 3 danni subiti
  2. The Avoider v3: 10 HP, 0 danni inflitti, 0 danni subiti
  3. Devo finire di mangiare: 10 HP, 0 danni inflitti, 0 danni subiti
  4. BestOpportunityBot: 0 HP, 3 danni inflitti, 10 danni subiti

Mappa 2

BestOpportunityBot ha svolto gran parte del lavoro in questa partita, ma Assassin è stato in grado di eliminarlo alla fine. Play-by-play dettagliato qui.

  1. Assassino: 2 HP, 10 danni inflitti, 9 danni subiti
  2. BestOpportunityBot: 0 HP, 32 danni inflitti, 10 danni subiti
  3. The Avoider v3: 0 HP, 0 danni inflitti, 12 danni subiti
  4. Devo finire di mangiare: 0 HP, 0 danni inflitti, 11 danni subiti

Mappa 3

BestOpportunityBot ha spinto tutti in trappole in questa partita. Molto bello. Play-by-play dettagliato qui.

  1. BestOpportunityBot: 10 HP, 30 danni inflitti, 0 danni subiti
  2. Assassino: 0 HP, 0 danni inflitti, 0 danni subiti
  3. Devo finire di mangiare: 0 HP, 0 danni inflitti, 0 danni subiti
  4. The Avoider v3: 0 HP, 0 danni inflitti, 0 danni subiti

Grazie per le tue risposte! Dato che ci sono solo 4 Scriptbot, stiamo abbandonando i piani del torneo per tre partite gratuite, una su ciascuna delle mappe sottostanti. Lo scriptbot con il record di vittorie più alto vince. In caso di pareggio, entreremo nella morte improvvisa in cui vince lo scriptbot che rompe per primo il pareggio.


Il tuo compito, se scegli di accettarlo, è codificare uno Scriptbot che può attraversare una mappa ASCII e distruggere i suoi avversari. Ogni battaglia prenderà la forma di un gioco a turni di ordine casuale in cui ogni Scriptbot ha la possibilità di spendere i propri punti energia (EP) per compiere azioni. Lo script GameMaster alimenterà l'input e interpreterà l'output di ciascun Scriptbot.

Ambiente

Ogni Scriptbot è contenuto all'interno della propria directory in cui si può leggere dai mapei statsfile e di lettura / scrittura al datafile. Il datafile può essere utilizzato per archiviare qualsiasi informazione persistente che potresti trovare utile.

Il file stats

Il statsfile contiene informazioni sui tuoi avversari ed è formattato come segue. Ogni giocatore è rappresentato su una riga separata. La prima colonna è un ID giocatore ( @significa che). La seconda colonna è la salute di quel giocatore.

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

Il file della mappa

Il mapfile potrebbe assomigliare a questo ...

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

... o questo...

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

... o questo...

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

... o potrebbe apparire totalmente diverso. Ad ogni modo, i personaggi usati e il loro significato rimarranno gli stessi:

  • # Un muro, invalicabile e impenetrabile.
  • 1, 2, 3... Un numero che rappresenta un giocatore nemico. Questi numeri corrispondono all'ID giocatore nel statsfile.
  • !Una trappola. Gli scriptbot che si spostano in queste posizioni moriranno immediatamente.
  • @ La posizione del tuo Scriptbot.
  • Spazio aperto in cui sei libero di spostarti.

gameplay

Lo script GameMaster assegnerà un ordine iniziale casuale agli Scriptbot. Gli Scriptbot vengono quindi richiamati in questo ordine mentre sono ancora vivi. Gli Scriptbot hanno 10 punti vita (HP) e iniziano con 10 punti energia (EP) ogni turno, che possono usare per muovere o attaccare. All'inizio di ogni turno, uno Scriptbot guarirà per un HP, o gli verrà concesso un EP aggiuntivo se già a 10 HP (quindi correre può essere una strategia praticabile a volte).

La battaglia termina quando sopravvive un solo Scriptbot o quando sono passati 100 turni. Se più Scriptbot sono vivi alla fine di una battaglia, il loro posto viene determinato in base ai seguenti criteri:

  1. La maggior parte della salute.
  2. La maggior parte dei danni inflitti.
  3. La maggior parte dei danni subiti.

Scriptbot Input

GameMaster stamperà la mappa di battaglia su un file chiamato da mapcui lo Scriptbot avrà accesso da cui leggere. La mappa potrebbe assumere qualsiasi forma, quindi è importante che Scriptbot sia in grado di interpretarla. Lo Scriptbot verrà richiamato con un parametro che indica EP. Per esempio...

:> example_scriptbot.py 3

Lo Scriptbot verrà invocato fino a quando non spende tutto il suo EP o un massimo di 10 11 volte. I file delle mappe e delle statistiche vengono aggiornati prima di ogni invocazione.

Output di Scriptbot

Gli scriptbot dovrebbero mostrare le loro azioni a stout. Un elenco di possibili azioni è il seguente:

  • MOVE <DIRECTION> <DISTANCE>

    Costa 1 PE per DISTANCE. Il MOVEcomando sposta il tuo Scriptbot sulla mappa. Se c'è qualcosa nel modo in cui un muro o un altro Scriptbot, GameMaster sposta il tuo Scriptbot il più lontano possibile. Se DISTANCEviene fornito un EP superiore al rimanente di Scriptbot, GameMaster sposta lo Scriptbot fino a quando il suo EP non si esaurisce. DIRECTIONpotranno essere qualsiasi direzione bussola di N, E, S, o W.

  • PUSH <DIRECTION> <DISTANCE>

    Costa 1 PE per DISTANCE. Il PUSHcomando abilita uno Scriptbot a spostare un altro Scriptbot. Lo Scriptbot che emette il comando deve essere direttamente accanto allo Scriptbot inviato. Entrambi gli Scriptbot si sposteranno nella direzione indicata se non c'è un oggetto che blocca lo Scriptbot che viene spinto. DIRECTIONe DISTANCEsono gli stessi del MOVEcomando.

  • ATTACK <DIRECTION>

    Costa un PE. Il ATTACKcomando infligge 1 danno a qualsiasi Scriptbot direttamente accanto allo Scriptbot di emissione e nella direzione specificata. DIRECTIONè lo stesso del MOVEcomando.

  • PASS

    Termina il tuo turno.

Lingue supportate

Per mantenere questo ragionevole per me, accetterò le seguenti lingue:

  • Giava
  • Node.js
  • Pitone
  • PHP

Sei limitato alle librerie che sono comunemente impacchettate con le tue lingue fuori dalla scatola. Per favore non farmi individuare librerie oscure per far funzionare il tuo codice.

Presentazione e valutazione

Pubblica il tuo codice sorgente Scriptbot qui sotto e dagli un bel nome! Elenca anche la versione della lingua utilizzata. Tutti gli Scriptbot saranno esaminati per i tomfoolery, quindi si prega di commentare bene e non offuscare il codice.

Puoi inviare più di una voce, ma ti preghiamo di renderle completamente uniche e non versioni della stessa voce. Ad esempio, potresti voler codificare un bot Zerg Rush e un bot Gorilla Warfare. Va bene. Non pubblicare Zerg Rush v1, Zerg Rush v2, ecc.

Il 7 novembre raccoglierò tutte le risposte e quelle che supereranno la revisione iniziale verranno aggiunte a una parentesi del torneo. Il campione ottiene la risposta accettata. La staffa ideale è mostrata di seguito. Dal momento che probabilmente non ci saranno esattamente 16 voci, alcune parentesi potrebbero finire per essere solo tre o anche due robot. Proverò a rendere la staffa il più equa possibile. Eventuali favoritismi necessari (nel caso in cui sia necessaria una settimana di arrivederci) saranno dati ai robot che sono stati presentati per primi.

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

Q & A

Sono sicuro di aver perso alcuni dettagli, quindi sentiti libero di fare domande!

Possiamo fidarci che un file di mappa è sempre circondato da # simboli? In caso contrario, cosa succede nel caso in cui un bot tenti di uscire dalla mappa? - BrainSteel

Sì, la mappa sarà sempre delimitata da # e il tuo Scriptbot inizierà all'interno di questi limiti.

Se non è presente alcun bot nella direzione specificata in un comando PUSH, come funziona il comando? - BrainSteel

Il GameMaster non farà nulla, verrà speso zero EP e lo Scriptbot verrà richiamato di nuovo.

Si accumulano EP inutilizzati? - feersum

No. Ogni Scriptbot inizierà il round / turn con 10 EP. Qualsiasi EP non speso andrà sprecato.

Penso di averlo, ma solo per chiarire: con i robot A e B, è l'ordine degli eventi A @ 10EP-> SPOSTA MAPPA_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATTACK MAP_UPDATE B @ 9EP-> ATTACCO ... o A @ 10EP-> SPOSTA A @ 9EP-> ATTACCO ... MAP_UPDATE B @ 10EP-> PUSH B @ 9EP-> ATTACK ... MAP_UPDATE? In altre parole, tutte le azioni in un ciclo di query controller-bot sono atomiche? Se è così, perché il loop? Perché non restituire un singolo file con tutte le azioni da completare? Altrimenti i robot dovranno scrivere i propri file di stato per tenere traccia delle sequenze multi-azione. Il file della mappa / delle statistiche sarà valido solo prima della prima azione. - COTO

Il tuo secondo esempio è vicino, ma non del tutto giusto. Durante un turno, Scriptbot viene invocato ripetutamente fino a quando non viene speso il loro EP, o un massimo di 11 volte. I file delle mappe e delle statistiche vengono aggiornati prima di ogni invocazione. Il ciclo è utile nel caso in cui un bot fornisca un output non valido. GameMaster gestirà l'output non valido e coinvolgerà nuovamente il bot, dando al bot la possibilità di correggere l'errore.

rilascerete lo script GameMaster per il test? - IchBinKeinBaum

Lo script GameMaster non verrà rilasciato. Ti incoraggio a creare una mappa e un file delle statistiche per testare il comportamento del tuo bot.

Se robotA inserisce robotB in una trappola, a robotA vengono accreditati punti "danno inflitto" pari all'attuale salute di robotB? - Mike Sweeney

Sì, è una buona idea. Ad un bot verranno assegnati punti di danno pari alla salute di qualsiasi bot che viene inserito in una trappola.


Possiamo fidarci che un mapfile è sempre circondato da #simboli? In caso contrario, cosa succede nel caso in cui un bot tenti di uscire dalla mappa?
BrainSteel,

@BrainSteel Sì, la mappa sarà sempre delimitata da #e il tuo Scriptbot inizierà all'interno di questi limiti.
Rip Leeb,

3
Se sei sicuro di aver perso qualcosa, perché non pubblicarlo nella sandbox ?
Martin Ender,

2
@ MartinBüttner Ci ho pensato a fondo. Stavo solo cercando di essere amichevole e chiarire che le domande sono benvenute.
Rip Leeb,

1
Se robotA inserisce robotB in una trappola, a robotA vengono attribuiti punti "danno inflitto" pari all'attuale stato di robotB?
Logic Knight,

Risposte:


1

Assassin (Java 1.7)

Cerca di uccidere i nemici quando possibile, altrimenti sposta un campo. È abbastanza buono nel trovare la strada per un nemico, ma non fa nulla per nascondersi dagli altri robot.

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

In quale versione di Java è stato scritto?
Rip Leeb,

@Nate ho usato 1.7.
CommonGuy

3

The Avoider v3

Un robot dalla mentalità semplice. Ha paura di altri robot e trappole. Non attaccherà. Ignora il file delle statistiche e non pensa affatto al futuro.

Questo è principalmente un test per vedere come funzionerebbero le regole e come un stupido avversario per altri concorrenti.

Modifica: ora passerà quando nessun MOVE è migliore.

Modifica2: i robot possono essere "1234" anziché "123"

Il codice 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?

elif ch in '123':Vuoi cercare almeno 1234. Se sei il bot 3, la lista degli avversari sarebbe 124.
Rip Leeb

1
@Nate Grazie. Ho cambiato il bot. Potresti voler chiarire questo nelle regole. Potrei non essere l'unico che ha frainteso questo.
Logic Knight,

3

BestOpportunityBot

Questo ha finito per essere un po 'più lungo di quanto volessi ... e non sono sicuro di aver compreso completamente le regole per i turni, quindi vedremo come funziona.

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

in quale versione di Python hai scritto questo?
Rip Leeb,

@Nate 3.4.1 su win32

Grazie per aver inviato questo @horns. È stato davvero divertente da guardare!
Rip Leeb,

1

Devo finire di mangiare

Pitone:

import sys
print 'PASS'

1
L'ho lolato - e perché diavolo import sys?
tomsmeding

1
@tomsmeding Beh, ho dovuto copiare alcune cose. E ho pensato nel caso in cui avessi mai bisogno di leggere alcuni argomenti :) quando finisco di mangiare, ovviamente.
Timtech,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.