Scriptbot Warz!


14

Scriptbot Warz!


Результаты в и Ассасин наш чемпион, выиграв 2 из 3 матчей! Спасибо всем, кто прислал свои Scriptbots! Отдельное спасибо рогам за BestOpportunityBot, который показал отличные пути и в полной мере использовал все варианты действий.

Карта 1

Ассасин забрал BestOpportunityBot на ранней стадии, а остальная часть матча была довольно скучной. Подробное описание игры здесь.

  1. Убийца: 10 HP, 10 урона, 3 урона
  2. Avoider v3: 10 HP, 0 урона, 0 урона
  3. Готово Есть: 10 HP, 0 урона, 0 урона
  4. BestOpportunityBot: 0 HP, 3 урона, 10 урона

Карта 2

BestOpportunityBot проделал большую часть работы над этим матчем, но Ассасин смог в конце концов его убить. Подробное описание игры здесь.

  1. Убийца: 2 HP, 10 урона, 9 урона
  2. BestOpportunityBot: 0 HP, 32 урона, 10 урона
  3. Avoider v3: 0 HP, 0 урона, 12 урона
  4. Готово Есть: 0 HP, 0 урона, 11 урона

Карта 3

BestOpportunityBot загнал всех в ловушку в этом матче. Очень круто. Подробное описание игры здесь.

  1. BestOpportunityBot: 10 HP, 30 урона за нанесение урона, 0 полученного урона
  2. Убийца: 0 HP, 0 урона, 0 урона
  3. Готово Есть: 0 HP, 0 урона, 0 урона
  4. Avoider v3: 0 HP, 0 урона, 0 урона

Спасибо за ваши ответы! Поскольку есть всего 4 Scriptbots, мы отказываемся от планов турниров на три бесплатных матча, по одному на каждой из карт ниже. Побеждает скрипт-бот с наибольшей победой. В случае ничьей мы вступим в внезапную смерть, в которой победит скрипт-бот, который первым разорвет ничью.


Ваша задача, если вы решите принять ее, - это написать Scriptbot, который может пересечь карту ASCII и уничтожить ее противников. Каждое сражение будет проходить в форме пошаговой игры со случайным стартовым порядком, где у каждого Scriptbot есть шанс потратить свои энергетические очки (EP) на действия. Скрипт GameMaster будет передавать входные данные и интерпретировать выходные данные каждого Scriptbot.

Окружающая обстановка

Каждый Scriptbot содержится в своем собственном каталоге , где он может считывать mapи statsфайлов и чтения / записи в dataфайл. dataФайл может быть использован для хранения любой постоянной информации вы могли бы оказаться полезными.

Файл статистики

statsФайл содержит информацию о ваших противниках и отформатирован следующим образом . Каждый игрок представлен в отдельном ряду. Первый столбец - это идентификатор игрока ( @означает вас). Второй столбец - это здоровье этого игрока.

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

Файл карты

mapФайл может выглядеть примерно так ...

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

... или это...

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

... или это...

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

... или это может выглядеть совсем иначе. В любом случае, используемые символы и их значение останутся прежними:

  • # Стена, непроходимая и непроходимая.
  • 1, 2, 3... Число , представляющее вражеский игрок. Эти номера соответствуют идентификатору игрока в statsфайле.
  • !Ловушка. Скриптботы, которые переместятся в эти локации, немедленно умрут
  • @ Местоположение вашего Scriptbot.
  • Открытое пространство, в котором вы можете свободно перемещаться.

Игровой процесс

Скрипт GameMaster назначит случайный порядок запуска для скриптовых ботов. Скриптботы затем вызываются в этом порядке, пока они еще живы. Скриптботы имеют 10 очков здоровья (HP) и начинают с 10 очков энергии (EP) каждый ход, которые они могут использовать для перемещения или атаки. В начале каждого хода, Scriptbot будет лечить один HP или получит один дополнительный EP, если он уже на 10 HP (таким образом, запуск иногда может быть жизнеспособной стратегией).

Битва заканчивается, когда выживает только один Scriptbot или когда прошло 100 ходов. Если в конце битвы несколько Scriptbots живы, их место определяется на основе следующих критериев:

  1. Больше всего здоровья.
  2. Большая часть нанесенного урона.
  3. Большая часть урона получена.

Scriptbot Input

GameMaster напечатает карту битвы в файл с именем, из mapкоторого Scriptbot будет иметь доступ для чтения. Карта может принимать любую форму, поэтому важно, чтобы Scriptbot мог ее интерпретировать. Ваш Scriptbot будет вызываться с одним параметром, обозначающим EP. Например...

:> example_scriptbot.py 3

Scriptbot будет вызываться до тех пор, пока он не потратит все свои EP или максимум 10 11 раз. Файлы карт и статистики обновляются перед каждым вызовом.

Выход Scriptbot

Скриптботы должны выводить свои действия на полную. Список возможных действий:

  • MOVE <DIRECTION> <DISTANCE>

    Стоит 1 EP за DISTANCE. Команда MOVEперемещает вашего Scriptbot по карте. Если на пути есть что-то, например стена или другой Scriptbot, GameMaster переместит ваш Scriptbot как можно дальше. Если DISTANCEдается больше, чем оставшийся EP Scriptbot, GameMaster будет перемещать Scriptbot до тех пор, пока его EP не закончится. DIRECTIONможет быть любое направление , компас N, E, S, или W.

  • PUSH <DIRECTION> <DISTANCE>

    Стоит 1 EP за DISTANCE. Команда PUSHпозволяет Scriptbot переместить другого Scriptbot. Scriptbot, выдающий команду, должен находиться непосредственно рядом с Scriptbot, который выдвигается. Оба Scriptbots будут двигаться в указанном направлении, если не будет объекта, блокирующего Scriptbot. DIRECTIONи DISTANCEтакие же как для MOVEкоманды.

  • ATTACK <DIRECTION>

    Стоит один EP. Команда ATTACKнаносит 1 повреждение любому Scriptbot непосредственно рядом с выдающим Scriptbot и в указанном направлении. DIRECTIONтакой же, как для MOVEкоманды.

  • PASS

    Заканчивается твоя очередь.

Поддерживаемые Языки

Чтобы это было разумным для меня, я приму следующие языки:

  • Джава
  • Node.js
  • питон
  • PHP

Вы ограничены библиотеками, которые обычно упакованы с вашими языками из коробки. Пожалуйста, не заставляйте меня искать неясные библиотеки, чтобы ваш код работал.

Представление и оценка

Разместите свой исходный код Scriptbot ниже и дайте ему крутое имя! Пожалуйста, также перечислите версию языка, который вы использовали. Все Scriptbots будут проверены на tomfoolery, поэтому, пожалуйста, комментируйте хорошо и не запутывайте ваш код.

Вы можете отправить более одной записи, но, пожалуйста, сделайте их полностью уникальными, а не версиями одной и той же записи. Например, вы можете закодировать бот Zerg Rush и бот Gorilla Warfare. Все в порядке. Не публикуйте Zerg Rush v1, Zerg Rush v2 и т. Д.

7 ноября я соберу все ответы, и те, которые пройдут первоначальный обзор, будут добавлены в турнирную таблицу. Чемпион получает принятый ответ. Идеальная скобка показана ниже. Поскольку, вероятно, не будет ровно 16 записей, некоторые скобки могут оказаться только тремя или даже двумя ботами. Я постараюсь сделать кронштейн максимально справедливым. Любой необходимый фаворитизм (например, в случае необходимости прощальной недели) будет передан ботам, которые были представлены первыми.

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

Вопрос &

Я уверен, что пропустил некоторые детали, поэтому не стесняйтесь задавать вопросы!

Можем ли мы верить, что файл карты всегда окружен символами #? Если нет, что происходит в случае, если бот пытается уйти с карты? - BrainSteel

Да, карта всегда будет ограничена #, и ваш Scriptbot будет запускаться внутри этих границ.

Если нет бота в направлении, указанном в команде PUSH, как работает команда? - BrainSteel

GameMaster ничего не сделает, ноль EP будет потрачен, и Scriptbot будет вызван снова.

Накопленный неиспользованный EP? - feersum

Каждый Scriptbot начнет раунд / ход с 10 EP. Любой не потраченный EP будет пропадать.

Я думаю, что у меня есть, но просто для пояснения: с ботами A и B, порядок событий A @ 10EP-> MOVE MAP_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATTACK MAP_UPDATE B @ 9EP-> ATTACK ... или A @ 10EP-> MOVE A @ 9EP-> ATTACK ... MAP_UPDATE B @ 10EP-> PUSH B @ 9EP-> ATTACK ... MAP_UPDATE? Другими словами, все ли действия в одном цикле запросов контроллера-бота являются атомарными? Если так, то почему цикл? Почему бы не вернуть один файл со всеми действиями, которые необходимо выполнить? В противном случае боты должны будут записывать свои собственные файлы состояний, чтобы отслеживать последовательности действий. Файл карты / статистики будет действителен только до первого действия. - КОТО

Ваш второй пример близок, но не совсем прав. В течение хода Scriptbot вызывается повторно до тех пор, пока их EP не будет потрачен, или максимум 11 раз. Файлы карт и статистики обновляются перед каждым вызовом. Цикл полезен в случае, если бот выдает неверный вывод. GameMaster будет иметь дело с неверным выводом и снова задействует бота, давая боту возможность исправить свою ошибку.

вы выпустите скрипт GameMaster для тестирования? - IchBinKeinBaum

Скрипт GameMaster не будет выпущен. Я бы посоветовал вам создать карту и файл статистики, чтобы проверить поведение вашего бота.

Если robotA толкает robotB в ловушку, зачисляется ли robotA баллы «нанесенный урон», равные текущему состоянию здоровья robotB? - Майк Суини

Да, это хорошая идея. Бот получает очки урона, равные здоровью любого бота, которого он помещает в ловушку.


Можем ли мы верить, что mapфайл всегда окружен #символами? Если нет, что происходит в случае, если бот пытается уйти с карты?
BrainSteel

@BrainSteel Да, карта всегда будет ограничена, #и ваш Scriptbot будет запускаться внутри этих границ.
Рип Либ

3
Если вы уверены, что что-то пропустили, почему бы не опубликовать это в песочнице ?
Мартин Эндер

2
@ MartinBüttner Я обдумал это довольно тщательно. Я просто пытался быть дружелюбным и дать понять, что вопросы приветствуются.
Рип Либ

1
Если robotA толкает robotB в ловушку, зачисляется ли robotA баллы «нанесенный урон», равные текущему состоянию здоровья robotB?
Логика Найт

Ответы:


1

Убийца (Java 1.7)

Пытается убить врагов, когда это возможно, в противном случае перемещает одно поле Он довольно хорош в поиске пути к врагу, но ничего не делает, чтобы спрятаться от других ботов.

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

В какой версии Java это было написано?
Рип Либ

@Nate я использовал 1.7.
CommonGuy

3

Avoider v3

Простой настроенный бот. Боится других роботов и ловушек. Это не будет атаковать. Он игнорирует файл статистики и вообще не думает о будущем.

Это в основном тест, чтобы увидеть, как правила будут работать, и как тупой противник для других участников.

Изменить: теперь будет проходить, когда нет движения лучше.

Edit2: роботы могут быть «1234» вместо «123»

Код Python:

import sys
maxmoves = int(sys.argv[1])

ORTH = [(-1,0), (1,0), (0,-1), (0,1)]
def orth(p):
    return [(p[0]+dx, p[1]+dy) for dx,dy in ORTH]

mapfile = open('map').readlines()[::-1]
arena = dict( ((x,y),ch) 
    for y,row in enumerate(mapfile)
    for x,ch in enumerate(row.strip()) )
loc = [loc for loc,ch in arena.items() if ch == '@'][0]

options = []
for direc in ORTH:
    for dist in range(maxmoves+1):
        newloc = (loc[0]+direc[0]*dist, loc[1]+direc[1]*dist)
        if arena.get(newloc) in '#!1234':
            break
        penalty = dist * 10  # try not to use moves too fast
        if newloc == loc:
            penalty += 32   # incentive to move
        for nextto in orth(newloc):
            ch = arena.get(nextto)
            if ch == '#':
                penalty += 17  # don't get caught againt a wall
            elif ch in '1234':
                penalty += 26  # we are afraid of other robots
            elif ch == '!':
                penalty += 38  # we are very afraid of deadly traps
        options.append( [penalty, dist, direc] )

penalty, dist, direc = min(options)
if dist > 0:
    print 'MOVE', 'WESN'[ORTH.index(direc)], dist
else:
    print 'PASS'  # stay still?

elif ch in '123':Вы хотите найти хотя бы 1234. Если вы бот 3, список оппонентов будет 124.
Рип Либ

1
@Nate Спасибо. Я изменил бот. Возможно, вы захотите прояснить это в правилах. Возможно, я не единственный, кто неправильно это понял.
Логика Найт

3

BestOpportunityBot

Это оказалось немного дольше, чем я рассчитывал ... и я не уверен, что полностью понимаю правила для поворотов, поэтому посмотрим, как это получится.

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

в какой версии Python вы написали это?
Рип Либ

@Nate 3.4.1 на win32

Спасибо за отправку этого @horns. Смотреть было действительно весело!
Рип Либ

1

Должен Закончить Еду

Python:

import sys
print 'PASS'

1
Я лол на это - и какого черта import sys?
Томсминг

1
@ tomsmeding Ну, мне пришлось скопировать кое-что. И я подумал, если мне когда-нибудь понадобится прочитать некоторые аргументы :), когда я закончу есть, конечно.
Timtech
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.