Scriptbot Warz!

14

Scriptbot Warz!


Les résultats sont là et Assassin est notre champion, remportant 2 des 3 matchs! Merci à tous ceux qui ont soumis leurs Scriptbots! Un merci spécial aux klaxons pour BestOpportunityBot qui a affiché un excellent cheminement et a utilisé pleinement toutes les options d'action.

Carte 1

Assassin a éliminé BestOpportunityBot dès le début et le reste du match était assez ennuyeux. Play-by-play détaillé ici.

  1. Assassin: 10 PV, 10 dégâts infligés, 3 dégâts subis
  2. L'Avoider v3: 10 HP, 0 dégâts infligés, 0 dégâts subis
  3. Je dois finir de manger: 10 PdV, 0 Dégâts infligés, 0 Dégâts subis
  4. BestOpportunityBot: 0 HP, 3 Dégâts infligés, 10 Dégâts subis

Carte 2

BestOpportunityBot a fait la plupart du travail sur ce match, mais Assassin a finalement pu le retirer. Play-by-play détaillé ici.

  1. Assassin: 2 PV, 10 dégâts infligés, 9 dégâts subis
  2. BestOpportunityBot: 0 HP, 32 Dégâts infligés, 10 Dégâts subis
  3. L'Avoider v3: 0 HP, 0 Dégâts infligés, 12 Dégâts subis
  4. Je dois finir de manger: 0 PV, 0 dégâts infligés, 11 dégâts subis

Carte 3

BestOpportunityBot a poussé tout le monde dans des pièges sur ce match. Très cool. Play-by-play détaillé ici.

  1. BestOpportunityBot: 10 HP, 30 Dégâts infligés, 0 Dégâts subis
  2. Assassin: 0 PV, 0 Dégâts infligés, 0 Dégâts subis
  3. Je dois finir de manger: 0 PV, 0 dégâts infligés, 0 dégâts subis
  4. L'Avoider v3: 0 HP, 0 Dégâts infligés, 0 Dégâts subis

Merci pour vos réponses! Comme il n'y a que 4 Scriptbots, nous abandonnons les plans du tournoi pour trois matchs gratuits pour tous, un sur chacune des cartes ci-dessous. Le scriptbot avec le record de victoires le plus élevé gagne. En cas d'égalité, nous entrerons dans une mort subite où le scriptbot qui rompt la première égalité l'emporte.


Votre tâche, si vous l'acceptez, consiste à coder un Scriptbot qui peut traverser une carte ASCII et détruire ses adversaires. Chaque bataille prendra la forme d'un jeu au tour par tour à ordre de départ aléatoire où chaque Scriptbot aura la chance de dépenser ses points d'énergie (EP) pour effectuer des actions. Le script GameMaster alimentera les entrées et interprétera les sorties de chaque Scriptbot.

Environnement

Chaque Scriptbot est contenu dans son propre répertoire dans lequel il peut lire des mapet statsfichiers et lecture / écriture au datafichier. Le datafichier peut être utilisé pour stocker toute information persistante que vous pourriez trouver utile.

Le fichier stats

Le statsfichier contient des informations sur vos adversaires et est formaté comme suit. Chaque joueur est représenté sur une ligne distincte. La première colonne est un ID de joueur ( @signifie vous). La deuxième colonne est la santé de ce joueur.

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

Le fichier de carte

Le mapfichier pourrait ressembler à ceci ...

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

... ou ca...

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

... ou ca...

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

... ou cela pourrait être totalement différent. Quoi qu'il en soit, les caractères utilisés et leur signification resteront les mêmes:

  • # Un mur, infranchissable et impénétrable.
  • 1, 2, 3... Un nombre représentant un joueur ennemi. Ces numéros correspondent à l'ID du joueur dans le statsfichier.
  • !Un piège. Les scriptbots qui se déplacent vers ces emplacements mourront immédiatement.
  • @ L'emplacement de votre Scriptbot.
  • Espace ouvert dans lequel vous êtes libre de vous déplacer.

Gameplay

Le script GameMaster attribuera un ordre de départ aléatoire aux Scriptbots. Les Scriptbots sont ensuite invoqués dans cet ordre alors qu'ils sont encore en vie. Les scriptbots ont 10 points de vie (HP) et commencent par 10 points d'énergie (EP) à chaque tour, qu'ils peuvent utiliser pour se déplacer ou attaquer. Au début de chaque tour, un Scriptbot soignera pour un HP, ou se verra accorder un EP supplémentaire s'il est déjà à 10 HP (ainsi courir peut parfois être une stratégie viable).

La bataille se termine lorsqu'un seul Scriptbot survit ou lorsque 100 tours se sont écoulés. Si plusieurs Scriptbots sont vivants à la fin d'une bataille, leur place est déterminée en fonction des critères suivants:

  1. Plus de santé.
  2. La plupart des dégâts infligés.
  3. La plupart des dégâts subis.

Entrée Scriptbot

Le GameMaster imprimera la carte de bataille dans un fichier nommé à partir mapduquel le Scriptbot aura accès pour lire. La carte peut prendre n'importe quelle forme, il est donc important que le Scriptbot puisse l'interpréter. Votre Scriptbot sera invoqué avec un paramètre indiquant EP. Par exemple...

:> example_scriptbot.py 3

Le Scriptbot sera invoqué jusqu'à ce qu'il passe la totalité de son EP ou un maximum de 10 11 fois. Les fichiers de carte et de statistiques sont mis à jour avant chaque invocation.

Sortie Scriptbot

Les scriptbots devraient afficher leurs actions sur stout. Une liste d'actions possibles est la suivante:

  • MOVE <DIRECTION> <DISTANCE>

    Coûte 1 EP par DISTANCE. La MOVEcommande déplace votre Scriptbot sur la carte. S'il y a quelque chose comme un mur ou un autre Scriptbot, le GameMaster déplacera votre Scriptbot autant que possible. Si un DISTANCEEP supérieur au Scriptbot restant est donné, le GameMaster déplace le Scriptbot jusqu'à épuisement de son EP. DIRECTIONpeut être toute direction de la boussole de N, E, S, ou W.

  • PUSH <DIRECTION> <DISTANCE>

    Coûte 1 EP par DISTANCE. La PUSHcommande permet à un scriptbot de déplacer un autre scriptbot. Le scriptbot émettant la commande doit être directement à côté du scriptbot poussé. Les deux Scriptbots se déplaceront dans la direction indiquée s'il n'y a pas d'objet bloquant le Scriptbot poussé. DIRECTIONet DISTANCEsont les mêmes que pour la MOVEcommande.

  • ATTACK <DIRECTION>

    Coûte un EP. La ATTACKcommande inflige 1 dommage à n'importe quel Scriptbot directement à côté du Scriptbot émetteur et dans la direction spécifiée. DIRECTIONest la même que pour la MOVEcommande.

  • PASS

    Fin votre tour.

Langues prises en charge

Pour que cela reste raisonnable pour moi, j'accepterai les langues suivantes:

  • Java
  • Node.js
  • Python
  • PHP

Vous êtes limité aux bibliothèques qui sont généralement fournies avec vos langues prêtes à l'emploi. Veuillez ne pas me faire localiser des bibliothèques obscures pour faire fonctionner votre code.

Soumission et évaluation

Postez votre code source Scriptbot ci-dessous et donnez-lui un nom sympa! Veuillez également indiquer la version de la langue que vous avez utilisée. Tous les Scriptbots seront examinés pour tomfoolery, veuillez donc bien commenter et ne pas obscurcir votre code.

Vous pouvez soumettre plusieurs entrées, mais veuillez en faire des entrées totalement uniques, et non des versions de la même entrée. Par exemple, vous pouvez vouloir coder un bot Zerg Rush et un bot Gorilla Warfare. C'est très bien. Ne publiez pas Zerg Rush v1, Zerg Rush v2, etc.

Le 7 novembre, je collecterai toutes les réponses et celles qui réussiront l'examen initial seront ajoutées à une tranche de tournoi. Le champion obtient la réponse acceptée. Le support idéal est illustré ci-dessous. Puisqu'il n'y aura probablement pas exactement 16 entrées, certaines parenthèses peuvent finir par n'être que trois ou même deux robots. Je vais essayer de rendre l'équerre aussi équitable que possible. Tout favoritisme nécessaire (dans le cas où une semaine de congé est nécessaire par exemple) sera accordé aux bots qui ont été soumis en premier.

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

Q&R

Je suis sûr que j'ai raté certains détails, alors n'hésitez pas à poser des questions!

Pouvons-nous croire qu'un fichier de carte est toujours entouré de # symboles? Sinon, que se passe-t-il si un bot tente de quitter la carte? - BrainSteel

Oui, la carte sera toujours délimitée par # et votre Scriptbot démarrera à l'intérieur de ces limites.

S'il n'y a pas de bot présent dans la direction spécifiée dans une commande PUSH, comment fonctionne la commande? - BrainSteel

Le GameMaster ne fera rien, aucun EP ne sera dépensé et le Scriptbot sera rappelé.

Les EP inutilisés s'accumulent-ils? - feersum

Non. Chaque Scriptbot commencera le tour / tour avec 10 EP. Tout EP non dépensé sera perdu.

Je pense que je l'ai, mais juste pour clarifier: avec les bots A et B, est l'ordre des événements A @ 10EP-> MOVE MAP_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATTACK MAP_UPDATE B @ 9EP-> ATTACK ..., ou A @ 10EP-> MOVE A @ 9EP-> ATTACK ... MAP_UPDATE B @ 10EP-> PUSH B @ 9EP-> ATTACK ... MAP_UPDATE? En d'autres termes, toute action dans une boucle de requête contrôleur-bot est-elle atomique? Si oui, pourquoi la boucle? Pourquoi ne pas retourner un seul fichier avec toutes les actions à réaliser? Sinon, les bots devront écrire leurs propres fichiers d'état pour suivre les séquences multi-actions. Le fichier map / stats ne sera valide qu'avant la première action. - COTO

Votre deuxième exemple est proche, mais pas tout à fait raison. Pendant un tour, le Scriptbot est invoqué à plusieurs reprises jusqu'à ce que son EP soit dépensé, ou un maximum de 11 fois. Les fichiers de carte et de statistiques sont mis à jour avant chaque invocation. La boucle est utile dans le cas où un bot donne une sortie invalide. Le GameMaster traitera la sortie invalide et invoquera à nouveau le bot, donnant au bot une chance de corriger son erreur.

publierez-vous le script GameMaster pour le tester? - IchBinKeinBaum

Le script GameMaster ne sera pas publié. Je vous encourage à créer une carte et un fichier de statistiques pour tester le comportement de votre bot.

Si robotA pousse robotB dans un piège, robotA est-il crédité de points de "dégâts infligés" égaux à la santé actuelle de robotB? - Mike Sweeney

Oui, c'est une bonne idée. Un bot recevra des points de dégâts égaux à la santé d'un bot qu'il pousse dans un piège.

Rip Leeb
la source
Pouvons-nous croire qu'un mapfichier est toujours entouré de #symboles? Sinon, que se passe-t-il si un bot tente de quitter la carte?
BrainSteel
@BrainSteel Oui, la carte sera toujours délimitée #et votre Scriptbot commencera à l'intérieur de ces limites.
Rip Leeb
3
Si vous êtes sûr d'avoir raté quelque chose, pourquoi ne pas l'afficher dans le bac à sable ?
Martin Ender
2
@ MartinBüttner J'ai bien réfléchi à cela. J'essayais simplement d'être amical et de préciser que les questions sont les bienvenues.
Rip Leeb
1
Si robotA pousse robotB dans un piège, robotA est-il crédité de points de "dégâts infligés" égaux à la santé actuelle de robotB?
Logic Knight

Réponses:

1

Assassin (Java 1.7)

Essaie de tuer les ennemis autant que possible, sinon déplace un champ. Il est assez bon pour trouver le chemin vers un ennemi, mais ne fait rien pour se cacher des autres robots.

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
la source
Dans quelle version de Java cela a-t-il été écrit?
Rip Leeb
@Nate, j'ai utilisé 1.7.
CommonGuy
3

L'Avoider v3

Un bot simple d'esprit. Il a peur des autres robots et pièges. Il n'attaquera pas. Il ignore le fichier de statistiques et ne pense pas du tout à l'avance.

Il s'agit principalement d'un test pour voir comment les règles fonctionneraient, et comme un adversaire stupide pour les autres concurrents.

Edit: passe maintenant quand aucun mouvement n'est meilleur.

Edit2: Les robots peuvent être «1234» au lieu de «123»

Le code 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
la source
elif ch in '123':Vous voulez chercher au moins 1234. Si vous êtes le bot 3, la liste des adversaires serait 124.
Rip Leeb
1
@Nate Merci. J'ai changé le bot. Vous voudrez peut-être le préciser dans les règles. Je ne suis peut-être pas le seul à avoir mal compris cela.
Logic Knight
3

BestOpportunityBot

Cela a fini par être un peu plus long que prévu ... et je ne suis pas sûr de bien comprendre les règles pour les virages, alors nous verrons comment cela se passe.

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

la source
dans quelle version de python avez-vous écrit ceci?
Rip Leeb
@Nate 3.4.1 sur win32
Merci d'avoir soumis celui-ci @horns. C'était vraiment amusant à regarder!
Rip Leeb
1

Je dois finir de manger

Python:

import sys
print 'PASS'
Timtech
la source
1
Je lol'd à cela - et pourquoi diable import sys?
tomsmeding
1
@tomsmeding Eh bien, j'ai dû copier des trucs. Et j'ai pensé au cas où j'aurais besoin de lire quelques arguments :) quand j'ai fini de manger, bien sûr.
Timtech