Scriptbot Warz!

14

Scriptbot Warz!


Wyniki są już dostępne, a Assassin jest naszym mistrzem, wygrywając 2 z 3 meczów! Dziękujemy wszystkim, którzy przesłali Scriptboty! Specjalne podziękowania dla rogów za BestOpportunityBot, który wyświetlał doskonałe ścieżki i w pełni wykorzystywał wszystkie opcje akcji.

Mapa 1

Assassin wyjął BestOpportunityBot wcześnie, a reszta meczu była dość nudna. Szczegółowa gra tutaj.

  1. Zabójca: 10 HP, 10 obrażeń, 3 otrzymanych obrażeń
  2. The Avoider v3: 10 PW, 0 zadanych obrażeń, 0 otrzymanych obrażeń
  3. Muszę zakończyć Jedzenie: 10 PŻ, 0 obrażeń zadanych, 0 otrzymanych obrażeń
  4. BestOpportunityBot: 0 HP, 3 obrażenia zadane, 10 otrzymanych obrażeń

Mapa 2

BestOpportunityBot wykonał większość pracy w tym meczu, ale Assassin był w stanie go wyeliminować. Szczegółowa gra tutaj.

  1. Zabójca: 2 HP, 10 obrażeń, 9 otrzymanych obrażeń
  2. BestOpportunityBot: 0 HP, 32 obrażenia zadane, 10 otrzymanych obrażeń
  3. The Avoider v3: 0 PW, 0 zadanych obrażeń, 12 odniesionych obrażeń
  4. Muszę zakończyć Jedzenie: 0 PŻ, 0 obrażeń zadanych, 11 otrzymanych obrażeń

Mapa 3

BestOpportunityBot popchnął wszystkich w pułapkę na ten mecz. Bardzo fajny. Szczegółowa gra tutaj.

  1. BestOpportunityBot: 10 HP, 30 obrażeń zadanych, 0 otrzymanych obrażeń
  2. Zabójca: 0 PŻ, 0 zadanych obrażeń, 0 otrzymanych obrażeń
  3. Muszę zakończyć Jedzenie: 0 PŻ, 0 zadanych obrażeń, 0 otrzymanych obrażeń
  4. The Avoider v3: 0 PW, 0 zadanych obrażeń, 0 otrzymanych obrażeń

Dzięki za odpowiedzi! Ponieważ są tylko 4 Scriptboty, porzucamy plany turniejowe dla trzech darmowych meczów, po jednym na każdej z poniższych map. Wygrywa skrypt z najwyższym rekordem wygranych. W przypadku remisu nastąpi nagła śmierć, w której wygrywa skrypt, który zrywa remis jako pierwszy.


Twoim zadaniem, jeśli zdecydujesz się je zaakceptować, jest kodowanie Scriptbota, który może przemierzać mapę ASCII i niszczyć przeciwników. Każda bitwa przybierze formę losowej gry turowej, w której każdy Scriptbot ma szansę wydać swoje punkty energii (EP) na działania. Skrypt GameMaster będzie przesyłać dane wejściowe i interpretować dane wyjściowe z każdego Scriptbota.

Środowisko

Każdy Scriptbot jest zawarty we własnym katalogu, w którym można go odczytać z maporaz statsplików i odczytu / zapisu do datapliku. dataPlik może być używany do przechowywania jakichkolwiek trwałych informacji można znaleźć użyteczne.

Plik statystyk

statsPlik zawiera informacje na temat swoich przeciwników i jest sformatowany w następujący sposób. Każdy gracz jest reprezentowany w osobnym rzędzie. Pierwsza kolumna to identyfikator gracza ( @oznacza ciebie). Druga kolumna to zdrowie tego gracza.

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

Plik mapy

mapPlik może wyglądać tak ...

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

... albo to...

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

... albo to...

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

... lub może wyglądać zupełnie inaczej. Tak czy inaczej, użyte znaki i ich znaczenie pozostaną takie same:

  • # Ściana nieprzejezdna i nieprzenikniona.
  • 1, 2, 3... Liczba reprezentująca odtwarzacz wroga. Te liczby odpowiadają identyfikatorowi gracza w statspliku.
  • !Pułapka. Skrypty, które poruszają się w tych lokalizacjach, umrą natychmiast.
  • @ Lokalizacja twojego Scriptbota.
  • Otwarta przestrzeń, w której możesz się poruszać.

Rozgrywka

Skrypt GameMaster przypisze losową kolejność uruchamiania do Scriptbots. Scriptboty są następnie wywoływane w tej kolejności, gdy jeszcze żyją. Skrypty mają 10 Punktów Zdrowia (HP) i zaczynają od 10 Punktów Energii (EP) w każdej turze, których mogą użyć do poruszania się lub ataku. Na początku każdej tury Scriptbot wyleczy się za jedno HP lub otrzyma jedną dodatkową EP, jeśli już ma 10 HP (dlatego uruchomienie może być czasem opłacalną strategią).

Bitwa kończy się, gdy tylko jeden Scriptbot przeżyje lub minie 100 tur. Jeśli pod koniec bitwy żyje wiele skryptów, ich miejsce określa się na podstawie następujących kryteriów:

  1. Większość zdrowia.
  2. Większość zadanych obrażeń.
  3. Większość otrzymanych obrażeń.

Wejście Scriptbot

GameMaster wydrukuje mapę bitwy do pliku o nazwie, z mapktórego Scriptbot będzie miał dostęp do odczytu. Mapa może przybierać dowolną formę, dlatego ważne jest, aby Scriptbot mógł ją interpretować. Twój skrypt skryptowy zostanie wywołany z jednym parametrem wskazującym EP. Na przykład...

:> example_scriptbot.py 3

Scriptbot będzie wywoływany, dopóki nie wyda całej swojej EP lub maksymalnie 10 11 razy. Pliki map i statystyk są aktualizowane przed każdym wywołaniem.

Scriptbot Output

Skrypty botowe powinny wysyłać swoje działania do grubego. Lista możliwych działań jest następująca:

  • MOVE <DIRECTION> <DISTANCE>

    Kosztuje 1 EP za DISTANCE. MOVEPolecenie przenosi swoją Scriptbot się po mapie. Jeśli coś stoi na przeszkodzie, na przykład ściana lub inny Scriptbot, GameMaster przeniesie Scriptbota tak daleko, jak to możliwe. Jeśli DISTANCEpodana zostanie wartość większa niż pozostała EP Scriptbota, GameMaster będzie przesuwał Scriptbota, aż jego EP się wyczerpie. DIRECTIONmoże być dowolny kierunek kompas N, E, Slub W.

  • PUSH <DIRECTION> <DISTANCE>

    Kosztuje 1 EP za DISTANCE. PUSHKomenda umożliwia Scriptbot aby przejść kolejną Scriptbot. Scriptbot wydający polecenie musi znajdować się bezpośrednio obok pchanego Scriptbota. Oba Scriptboty poruszają się we wskazanym kierunku, jeśli nie ma obiektu blokującego pchanie Scriptbota. DIRECTIONi DISTANCEsą takie same jak dla MOVEpolecenia.

  • ATTACK <DIRECTION>

    Kosztuje jedną EP. ATTACKKomenda zadaje 1 szkodę jakiejkolwiek Scriptbot bezpośrednio obok wydającego Scriptbot iw określonym kierunku. DIRECTIONjest taki sam jak dla MOVEpolecenia.

  • PASS

    Kończy twoją turę.

Obsługiwane języki

Aby było to dla mnie uzasadnione, przyjmuję następujące języki:

  • Jawa
  • Node.js
  • Pyton
  • PHP

Jesteś ograniczony do bibliotek, które są zwykle pakowane z Twoimi językami po wyjęciu z pudełka. Nie każ mi lokalizować niejasnych bibliotek, aby Twój kod działał.

Składanie i ocenianie

Opublikuj poniżej kod źródłowy Scriptbot i nadaj mu fajną nazwę! Podaj także wersję używanego języka. Wszystkie Scriptboty zostaną sprawdzone pod kątem wygłupów, więc prosimy o komentarz i nie zaciemniaj swojego kodu.

Możesz przesłać więcej niż jeden wpis, ale prosimy, aby były one całkowicie unikalne, a nie wersje tego samego wpisu. Na przykład możesz zakodować bota Zerg Rush i Gorilla Warfare. W porządku. Nie publikuj Zerg Rush v1, Zerg Rush v2 itp.

7 listopada zbiorę wszystkie odpowiedzi, a te, które przejdą wstępną recenzję, zostaną dodane do przedziału turniejowego. Mistrz otrzymuje zaakceptowaną odpowiedź. Idealny wspornik pokazano poniżej. Ponieważ prawdopodobnie nie będzie dokładnie 16 wpisów, niektóre nawiasy kwadratowe mogą być tylko trzema, a nawet dwoma botami. Postaram się, aby wspornik był jak najbardziej sprawiedliwy. Wszelkie niezbędne faworyzowanie (na przykład w razie potrzeby poświęcenia tygodnia) zostanie przekazane botom, które zostały przesłane jako pierwsze.

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

Pytania i odpowiedzi

Jestem pewien, że przegapiłem kilka szczegółów, więc zadawaj pytania!

Czy możemy ufać, że plik mapy jest zawsze otoczony # symbolami? Jeśli nie, co dzieje się w przypadku, gdy bot próbuje zejść z mapy? - BrainSteel

Tak, mapa będzie zawsze ograniczona przez #, a Twój skrypt skryptowy rozpocznie się w tych granicach.

Jeśli nie ma bota obecnego w kierunku określonym w poleceniu PUSH, jak działa to polecenie? - BrainSteel

GameMaster nic nie zrobi, zero PD zostanie wydane, a Scriptbot zostanie ponownie wywołany.

Czy niewykorzystana EP gromadzi się? - feersum

Nie. Każdy Scriptbot rozpocznie rundę / turę z 10 EP. Każdy niewydany EP zostanie zmarnowany.

Myślę, że mam, ale dla wyjaśnienia: w przypadku botów A i B kolejność zdarzeń A @ 10EP-> MOVE MAP_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATTACK MAP_UPDATE B @ 9EP-> ATTACK ... lub A @ 10EP-> MOVE A @ 9EP-> ATTACK ... MAP_UPDATE B @ 10EP-> PUSH B @ 9EP-> ATTACK ... MAP_UPDATE? Innymi słowy, czy wszystkie działania w jednej pętli zapytań kontroler-bot mają charakter atomowy? Jeśli tak, dlaczego pętla? Dlaczego nie zwrócić pojedynczego pliku ze wszystkimi czynnościami do wykonania? W przeciwnym razie boty będą musiały zapisać własne pliki stanów, aby śledzić sekwencje wielu akcji. Plik mapy / statystyk będzie ważny tylko przed pierwszą akcją. - COTO

Twój drugi przykład jest bliski, ale nie do końca słuszny. Podczas tury Scriptbot jest wywoływany wielokrotnie, aż do wydania EP lub maksymalnie 11 razy. Pliki map i statystyk są aktualizowane przed każdym wywołaniem. Pętla jest przydatna w przypadku, gdy bot podaje nieprawidłowe dane wyjściowe. GameMaster zajmie się nieprawidłowym wyjściem i ponownie uruchomi robota, dając mu szansę na naprawienie błędu.

wydasz skrypt GameMaster do testowania? - IchBinKeinBaum

Skrypt GameMaster nie zostanie wydany. Zachęcam do utworzenia pliku mapy i statystyk w celu przetestowania zachowania twojego bota.

Jeśli robot A wpycha robotB w pułapkę, czy robotowi przypisuje się punkty „zadanych obrażeń” równe aktualnemu zdrowiu robota B? - Mike Sweeney

Tak, to dobry pomysł. Bot otrzyma punkty obrażeń równe zdrowiu dowolnego bota, który wpycha go w pułapkę.

Rip Leeb
źródło
Czy możemy ufać, że mapplik jest zawsze otoczony #symbolami? Jeśli nie, co dzieje się w przypadku, gdy bot próbuje zejść z mapy?
BrainSteel
@BrainSteel Tak, mapa będzie zawsze ograniczona, #a Twój skrypt skryptowy rozpocznie się w tych granicach.
Zgrać Leeba
3
Jeśli jesteś pewien, że coś przeoczyłeś, dlaczego nie opublikować tego w piaskownicy ?
Martin Ender
2
@ MartinBüttner Dokładnie to przemyślałem. Chciałem tylko być przyjazny i wyjaśnić, że pytania są mile widziane.
Zgrać Leeba
1
Jeśli robot A wpycha robotB w pułapkę, czy robotowi przypisuje się punkty „zadanych obrażeń” równe aktualnemu zdrowiu robota B?
Logic Knight

Odpowiedzi:

1

Zabójca (Java 1.7)

Próbuje zabijać wrogów, gdy tylko jest to możliwe, w przeciwnym razie przesuwa się o jedno pole. Jest całkiem dobry w znajdowaniu drogi do wroga, ale nie robi nic, aby ukryć się przed innymi botami.

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
źródło
W jakiej wersji Java została napisana?
Rip Leeb
@Nate użyłem 1.7.
CommonGuy,
3

Avoider v3

Prosty bot. Boi się innych robotów i pułapek. Nie zaatakuje. Ignoruje plik statystyk i wcale nie myśli w przyszłość.

Jest to głównie test sprawdzający, jak działają reguły, i głupi przeciwnik dla innych konkurentów.

Edycja: Teraz przejdzie, gdy żaden ruch nie jest lepszy.

Edycja2: Roboty mogą mieć postać „1234” zamiast „123”

Kod 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?
Logic Knight
źródło
elif ch in '123':Chcesz poszukać co najmniej 1234. Jeśli masz bota 3, lista przeciwników wynosiłaby 124.
Rip Leeb
1
@Nate Thanks. Zmieniłem bota. Możesz to wyjaśnić w regulaminie. Może nie jestem jedynym, który źle to zrozumiał.
Logic Knight
3

BestOpportunityBot

Skończyło się to trochę dłużej, niż zamierzałem ... i nie jestem pewien, czy całkowicie rozumiem zasady dotyczące zwrotów, więc zobaczymy, jak to działa.

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

źródło
w jakiej wersji Pythona to napisałeś?
Rip Leeb
@Nate 3.4.1 on win32
Dziękujemy za przesłanie tego @horns. Naprawdę fajnie było oglądać!
Zgrać Leeba
1

Muszę skończyć jeść

Pyton:

import sys
print 'PASS'
Timtech
źródło
1
Lol'd na to - i dlaczego do cholery import sys?
tomsmeding
1
@tomsmeding Cóż, musiałem skopiować niektóre rzeczy. I pomyślałem, że na wypadek, gdybym kiedykolwiek musiał przeczytać kilka argumentów :) oczywiście, kiedy skończę jeść.
Timtech