Scriptbot Warz!

14

Scriptbot Warz!


Die Ergebnisse sind da und Assassin ist unser Champion und gewinnt 2 von 3 Matches! Vielen Dank an alle, die ihre Scriptbots eingereicht haben! Besonderer Dank geht an horns für den BestOpportunityBot, der ein exzellentes Laufverhalten zeigte und alle Aktionsmöglichkeiten voll ausschöpfte .

Karte 1

Assassin hat BestOpportunityBot frühzeitig besiegt und der Rest des Spiels war ziemlich langweilig. Ausführliches Play-by-Play hier.

  1. Attentäter: 10 HP, 10 verursachter Schaden, 3 erlittener Schaden
  2. The Avoider v3: 10 HP, 0 verursachter Schaden, 0 erlittener Schaden
  3. Erhielt zu Ende zu essen: 10 HP, 0 zugefügter Schaden, 0 erlittener Schaden
  4. BestOpportunityBot: 0 HP, 3 verursachter Schaden, 10 erlittener Schaden

Karte 2

BestOpportunityBot hat die meiste Arbeit an diesem Match geleistet, aber Assassin konnte ihn am Ende ausschalten. Ausführliches Play-by-Play hier.

  1. Attentäter: 2 LP, 10 verursachter Schaden, 9 erlittener Schaden
  2. BestOpportunityBot: 0 HP, 32 verursachter Schaden, 10 erlittener Schaden
  3. The Avoider v3: 0 LP, 0 zugefügter Schaden, 12 erlittener Schaden
  4. Erhielt zu Ende zu essen: 0 LP, 0 zugefügter Schaden, 11 erlittener Schaden

Karte 3

BestOpportunityBot hat in diesem Match alle in die Falle getrieben. Sehr cool. Ausführliches Play-by-Play hier.

  1. BestOpportunityBot: 10 HP, 30 verursachter Schaden, 0 erlittener Schaden
  2. Attentäter: 0 LP, 0 zugefügter Schaden, 0 erlittener Schaden
  3. Erhielt zu Ende zu essen: 0 LP, 0 zugefügter Schaden, 0 erlittener Schaden
  4. The Avoider v3: 0 LP, 0 zugefügter Schaden, 0 erlittener Schaden

Danke für deine Antworten! Da es nur 4 Scriptbots gibt, geben wir die Turnierpläne für drei Free-for-All-Matches auf, eines auf jeder der folgenden Karten. Der Scriptbot mit dem höchsten Gewinnrekord gewinnt. Im Falle eines Unentschieden werden wir in einen plötzlichen Tod verwickelt, bei dem der Scriptbot gewinnt, der das Unentschieden zuerst bricht.


Ihre Aufgabe ist es, einen Scriptbot zu codieren, der eine ASCII-Map durchquert und deren Gegner zerstört, falls Sie dies akzeptieren möchten. Jede Schlacht wird in Form eines rundenbasierten Spiels in zufälliger Startreihenfolge ausgetragen, in dem jeder Scriptbot die Möglichkeit hat, seine Energiepunkte (EP) für Aktionen auszugeben. Das GameMaster-Skript speist Eingaben in jeden Scriptbot ein und interpretiert sie.

Umgebung

Jede Scriptbot wird in einem eigenen Verzeichnis enthält , in dem sie von den lesen mapund statsDateien und Lese- / Schreibzugriff auf die dataDatei. In der dataDatei können alle Informationen gespeichert werden, die Sie für nützlich halten.

Die Statistikdatei

Die statsDatei enthält Informationen zu Ihren Gegnern und ist wie folgt formatiert. Jeder Spieler ist in einer eigenen Reihe vertreten. Die erste Spalte ist eine Spieler-ID ( @bedeutet Sie). Die zweite Spalte ist die Gesundheit dieses Spielers.

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

Die Map-Datei

Die mapDatei könnte ungefähr so ​​aussehen ...

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

... oder dieses...

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

... oder dieses...

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

... oder es könnte ganz anders aussehen. In jedem Fall bleiben die verwendeten Zeichen und ihre Bedeutung gleich:

  • # Eine Mauer, unpassierbar und undurchdringlich.
  • 1, 2, 3... Eine Reihe einen feindlichen Spieler darstellt. Diese Nummern entsprechen der Player-ID in der statsDatei.
  • !Eine Falle. Scriptbots, die an diese Orte ziehen, sterben sofort.
  • @ Der Standort Ihres Scriptbots.
  • Freiraum, in dem Sie sich frei bewegen können.

Spielweise

Das GameMaster-Skript weist den Scriptbots eine zufällige Startreihenfolge zu. Die Scriptbots werden dann in dieser Reihenfolge aufgerufen, solange sie noch aktiv sind. Scriptbots haben 10 Lebenspunkte (HP) und beginnen mit 10 Energiepunkten (EP) pro Runde, mit denen sie sich bewegen oder angreifen können. Zu Beginn jeder Runde heilt ein Scriptbot für eine HP oder erhält eine zusätzliche EP, wenn er bereits bei 10 HP ist (daher kann das Ausführen manchmal eine sinnvolle Strategie sein).

Der Kampf endet, wenn nur ein Scriptbot überlebt hat oder 100 Runden vergangen sind. Wenn am Ende eines Kampfes mehrere Scriptbots am Leben sind, wird ihr Platz anhand der folgenden Kriterien bestimmt:

  1. Die meiste Gesundheit.
  2. Der meiste verursachte Schaden.
  3. Den meisten Schaden genommen.

Scriptbot-Eingabe

Der GameMaster druckt die Kampfkarte in eine Datei mit dem Namen, aus mapder der Scriptbot lesen kann. Die Karte kann jede Form annehmen, daher ist es wichtig, dass der Scriptbot sie interpretieren kann. Ihr Scriptbot wird mit einem Parameter aufgerufen, der EP anzeigt. Beispielsweise...

:> example_scriptbot.py 3

Der Scriptbot wird solange aufgerufen, bis er seine gesamte EP oder maximal 10 bis 11 Mal ausgegeben hat. Die Karten- und Statistikdateien werden vor jedem Aufruf aktualisiert.

Scriptbot-Ausgabe

Scriptbots sollten ihre Aktionen auf stout ausgeben. Eine Liste der möglichen Aktionen lautet wie folgt:

  • MOVE <DIRECTION> <DISTANCE>

    Kostet 1 EP pro DISTANCE. Der MOVEBefehl bewegt Ihren Scriptbot auf der Karte. Wenn etwas wie eine Wand oder ein anderer Scriptbot im Weg ist, verschiebt der GameMaster Ihren Scriptbot so weit wie möglich. Wenn eine DISTANCEEP vergeben wird, die größer ist als die verbleibende EP des Scriptbots, verschiebt der GameMaster den Scriptbot, bis die EP abgelaufen ist. DIRECTIONkann jede Himmelsrichtung von N, E, Soder W.

  • PUSH <DIRECTION> <DISTANCE>

    Kostet 1 EP pro DISTANCE. Mit diesem PUSHBefehl kann ein Scriptbot einen anderen Scriptbot verschieben. Der Scriptbot, der den Befehl ausgibt, muss direkt neben dem Scriptbot stehen, der übertragen wird. Beide Scriptbots bewegen sich in die angegebene Richtung, wenn kein Objekt den Scriptbot blockiert. DIRECTIONund DISTANCEsind die gleichen wie für den MOVEBefehl.

  • ATTACK <DIRECTION>

    Kostet eine EP. Der ATTACKBefehl fügt jedem Scriptbot direkt neben dem ausstellenden Scriptbot und in der angegebenen Richtung 1 Schaden zu. DIRECTIONist das gleiche wie für den MOVEBefehl.

  • PASS

    Beendet deinen Zug.

Unterstützte Sprachen

Um dies für mich zumutbar zu machen, akzeptiere ich folgende Sprachen:

  • Java
  • Node.js
  • Python
  • PHP

Sie sind auf Bibliotheken beschränkt, die standardmäßig in Ihren Sprachen enthalten sind. Bitte lassen Sie mich keine undurchsichtigen Bibliotheken finden, damit Ihr Code funktioniert.

Einreichung und Beurteilung

Veröffentlichen Sie Ihren Scriptbot-Quellcode unten und geben Sie ihm einen coolen Namen! Bitte geben Sie auch die Version der von Ihnen verwendeten Sprache an. Alle Scriptbots werden auf Tomfoolery überprüft. Bitte kommentieren Sie sie gut und verschleiern Sie Ihren Code nicht.

Sie können mehr als einen Eintrag einreichen, aber machen Sie bitte völlig eindeutige Einträge und keine Versionen desselben Eintrags. Beispielsweise möchten Sie möglicherweise einen Zerg Rush-Bot und einen Gorilla Warfare-Bot codieren. Das ist gut. Posten Sie keine Zerg Rush v1, Zerg Rush v2 usw.

Am 7. November werde ich alle Antworten sammeln und diejenigen, die die erste Bewertung bestehen, werden in eine Turnier-Klammer aufgenommen. Der Champion erhält die akzeptierte Antwort. Die ideale Halterung ist unten dargestellt. Da es wahrscheinlich nicht genau 16 Einträge geben wird, sind einige Klammern möglicherweise nur drei oder sogar zwei Bots. Ich werde versuchen, die Klammer so fair wie möglich zu gestalten. Jede notwendige Bevorzugung (zum Beispiel für den Fall, dass eine Tschüss-Woche benötigt wird) wird an die Bots vergeben, die zuerst eingereicht wurden.

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

Q & A

Ich bin mir sicher, dass ich einige Details verpasst habe, also zögern Sie nicht, Fragen zu stellen!

Dürfen wir darauf vertrauen, dass eine Kartendatei immer von # Symbolen umgeben ist? Wenn nicht, was passiert, wenn ein Bot versucht, die Karte zu verlassen? - BrainSteel

Ja, die Map wird immer von # begrenzt und Ihr Scriptbot startet innerhalb dieser Grenzen.

Wenn in der in einem PUSH-Befehl angegebenen Richtung kein Bot vorhanden ist, wie funktioniert der Befehl? - BrainSteel

Der GameMaster macht nichts, es wird keine EP ausgegeben und der Scriptbot wird erneut aufgerufen.

Sammeln sich ungenutzte EPs an? - Feersum

Nein. Jeder Scriptbot startet die Runde / Runde mit 10 EP. Nicht ausgegebene EP werden verschwendet.

Ich denke, ich habe es verstanden, aber um es zu verdeutlichen: Bei Bots A und B ist die Reihenfolge der Ereignisse A @ 10EP -> MOVE MAP_UPDATE B @ 10EP -> PUSH MAP_UPDATE A @ 9EP -> ATTACK MAP_UPDATE B @ 9EP -> ATTACK ... oder A @ 10EP-> MOVE A @ 9EP-> ATTACK ... MAP_UPDATE B @ 10EP-> PUSH B @ 9EP-> ATTACK ... MAP_UPDATE? Mit anderen Worten, ist alle Aktion in einer Controller-Bot-Abfrageschleife atomar? Wenn ja, warum die Schleife? Warum nicht eine einzelne Datei mit allen auszuführenden Aktionen zurückgeben? Andernfalls müssen Bots ihre eigenen Statusdateien schreiben, um Multi-Action-Sequenzen zu verfolgen. Die Map / Statistik-Datei ist nur vor der ersten Aktion gültig. - COTO

Ihr zweites Beispiel ist nah, aber nicht ganz richtig. Während eines Spielzugs wird der Scriptbot wiederholt aufgerufen, bis die EP ausgegeben wurde, oder maximal elf Mal. Die Karten- und Statistikdateien werden vor jedem Aufruf aktualisiert. Die Schleife ist nützlich, wenn ein Bot eine ungültige Ausgabe ausgibt. Der GameMaster wird sich mit der ungültigen Ausgabe befassen und den Bot erneut einbinden, so dass der Bot die Möglichkeit hat, den Fehler zu korrigieren.

Werden Sie das GameMaster-Skript zum Testen freigeben? - IchBinKeinBaum

Das GameMaster-Skript wird nicht veröffentlicht. Ich würde Sie ermutigen, eine Karte und eine Statistikdatei zu erstellen, um das Verhalten Ihres Bots zu testen.

Wenn Roboter A Roboter B in eine Falle stößt, werden Roboter A "Schaden" -Punkte gutgeschrieben, die dem aktuellen Gesundheitszustand von Roboter B entsprechen? - Mike Sweeney

Ja, das ist eine gute Idee. Einem Bot werden Schadenspunkte gewährt, die der Gesundheit jedes Bots entsprechen, den er in eine Falle stößt.

Rip Leeb
quelle
Dürfen wir darauf vertrauen, dass eine mapDatei immer von #Symbolen umgeben ist? Wenn nicht, was passiert, wenn ein Bot versucht, die Karte zu verlassen?
BrainSteel
@BrainSteel Ja, die Map wird immer von begrenzt #und Ihr Scriptbot startet innerhalb dieser Grenzen.
Rip Leeb
3
Wenn Sie sicher sind, dass Sie etwas verpasst haben, können Sie es in der Sandbox veröffentlichen .
Martin Ender
2
@ MartinBüttner Ich habe mir das ziemlich gründlich überlegt. Ich habe nur versucht, freundlich zu sein und klar zu machen, dass Fragen willkommen sind.
Rip Leeb
1
Wenn Roboter A Roboter B in eine Falle stößt, werden Roboter A "Schaden" -Punkte gutgeschrieben, die dem aktuellen Gesundheitszustand von Roboter B entsprechen?
Logic Knight

Antworten:

1

Assassin (Java 1.7)

Versucht, Feinde zu töten, wann immer dies möglich ist, ansonsten wird ein Feld verschoben. Es ist ziemlich gut, den Weg zu einem Feind zu finden, tut aber nichts, um sich vor anderen Bots zu verstecken.

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
quelle
In welcher Java-Version wurde das geschrieben?
Rip Leeb
@Nate Ich habe 1.7 verwendet.
CommonGuy
3

Der Avoider v3

Ein einfacher Bot. Es hat Angst vor anderen Robotern und Fallen. Es wird nicht angreifen. Es ignoriert die Statistikdatei und denkt überhaupt nicht weiter.

Dies ist hauptsächlich ein Test, um zu sehen, wie die Regeln funktionieren würden, und ein blöder Gegner für andere Konkurrenten.

Edit: Jetzt wird PASS, wenn kein MOVE besser ist.

Edit2: Roboter können '1234' anstelle von '123' sein

Der Python-Code:

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?
Logik-Ritter
quelle
elif ch in '123':Sie möchten mindestens 1234 suchen. Wenn Sie Bot 3 sind, wäre die
Rip Leeb
1
@Nate Danke. Ich habe den Bot gewechselt. Vielleicht möchten Sie dies in den Regeln klarstellen. Ich bin vielleicht nicht der Einzige, der dies missverstanden hat.
Logic Knight
3

BestOpportunityBot

Dies dauerte etwas länger als geplant ... und ich bin mir nicht sicher, ob ich die Regeln für Abbiegungen vollständig verstehe. Wir werden also sehen, wie das funktioniert.

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

quelle
In welcher Version von Python hast du das geschrieben?
Rip Leeb
@Nate 3.4.1 auf win32
Vielen Dank für die Einsendung dieses @horns. Es hat wirklich Spaß gemacht zu sehen!
Rip Leeb
1

Muss fertig essen

Python:

import sys
print 'PASS'
Timtech
quelle
1
Ich würde schon dabei sein - und warum zum Teufel import sys?
Tomsmeding
1
@tomsmeding Nun, ich musste ein paar Sachen kopieren. Und ich dachte, falls ich jemals ein paar Argumente lesen muss :) wenn ich mit dem Essen fertig bin, natürlich.
Timtech