Сценарій Варц!


14

Сценарій Варц!


Результати є, і Assassin - наш чемпіон, вигравши 2 з 3 матчів! Дякуємо всім, хто подав свої сценарії! Особлива подяка рогам за BestOpportunityBot, які показали відмінний шлях та повною мірою використали всі варіанти дій.

Карта 1

Ассасін вивів BestOpportunityBot рано, а решта матчу була досить нудною. Тут ви можете ознайомитися з детальною програмою.

  1. Вбивця: 10 к.с., 10 ушкоджень, 3 ушкодження
  2. Avoider v3: 10 к.с., 0 ушкодження, 0 ушкодження
  3. Треба закінчити харчування: 10 к.с., 0 пошкоджень, 0 ушкоджень
  4. BestOpportunityBot: 0 к.с., 3 ушкодження, 10 ушкоджень

Карта 2

BestOpportunityBot зробив більшу частину роботи в цьому матчі, але в кінцевому підсумку Асассіну вдалося його вивести. Тут ви можете ознайомитись із детальною програмою.

  1. Вбивця: 2 HP, 10 ушкоджень, 9 ушкоджень
  2. BestOpportunityBot: 0 HP, 32 ушкодження, 10 ушкоджень
  3. Avoider v3: 0 к.с., 0 пошкодження шкоди, 12 ушкоджень
  4. Треба закінчити їжу: 0 к.с., 0 пошкоджень, 11 ушкоджень

Карта 3

BestOpportunityBot підштовхував усіх до пасток цього матчу. Дуже круто. Тут ви можете ознайомитись із детальною програмою.

  1. BestOpportunityBot: 10 к.с., 30 ушкоджень, 0 ушкоджень
  2. Вбивця: 0 к.с., 0 ушкодження, 0 ушкодження
  3. Треба закінчити їжу: 0 к.с., 0 ушкодження, 0 ушкодження
  4. Avoider v3: 0 к.с., 0 пошкодження шкоди, 0 пошкодження

Дякую за відповіді! Оскільки всього 4 скриптів, ми відмовляємось від турнірних планів на три матчі безкоштовно для всіх, по одному на кожній із карт нижче. Сценарій із найвищим рекордом виграє. У випадку зрівноваження ми вступимо в раптову смерть, в якій виграє сценарій, який розірве краватку спочатку.


Якщо ви вирішите його прийняти, ваше завдання - кодувати Scriptbot, який може пройти карту ASCII та знищити її супротивників. Кожен бій буде проходити у формі покрокової гри в довільному порядку, де кожен Scriptbot має шанс витратити свої енергетичні очки (ЕП) для здійснення дій. Сценарій 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файлі.
  • !Пастка. Роботи сценаріїв, які переїжджають на ці місця, загинуть негайно.
  • @ Місцезнаходження вашого скрипта
  • Відкрийте простір, по якому ви можете вільно пересуватися.

Ігровий процес

Сценарій GameMaster призначить випадковий стартовий порядок скриптам. Потім скрипти викликаються в цьому порядку, поки вони ще живі. У сценаріїв є 10 очок здоров’я (HP) і починаються з 10 енергетичних очок (ЕП) на кожному ходу, які вони можуть використовувати для переміщення або нападу. На початку кожного кроку Scriptbot оздоровиться за один HP, або отримає одне додаткове EP, якщо вже в 10 HP (таким чином, запуск може бути життєздатною стратегією часом).

Бій закінчується, коли виживає лише один скрипт або коли пройшло 100 оборотів. Якщо в кінці бою кілька ботів сценаріїв живі, їх місце визначається виходячи з таких критеріїв:

  1. Найбільше здоров’я.
  2. Більшість завданих збитків.
  3. Більшість збитків заподіяно.

Введення скриптів

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

:> example_scriptbot.py 3

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

Вихід з сценарію

Сценарії повинні виводити свої дії на товсті. Перелік можливих дій такий:

  • MOVE <DIRECTION> <DISTANCE>

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

  • PUSH <DIRECTION> <DISTANCE>

    Витрати 1 ЕП в розрахунку DISTANCE. PUSHКоманда дозволяє Scriptbot рухатися інший Scriptbot. Scriptbot, що видає команду, повинен знаходитися безпосередньо поруч із натисканням на Scriptbot. Обидві роботи скриптів рухатимуться у вказаному напрямку, якщо немає об'єкта, що блокує натискання Scriptbot. DIRECTIONі DISTANCEтакі самі, як і для MOVEкоманди.

  • ATTACK <DIRECTION>

    Коштує один EP. ATTACKКоманда завдає 1 пошкодження будь-якого Scriptbot безпосередньо поруч з видав Scriptbot і в напрямку , зазначеному. DIRECTIONте саме, що і для MOVEкоманди.

  • PASS

    Закінчується ваша черга.

Мови, що підтримуються

Щоб дотримуватися цього для мене розумним, я прийму наступні мови:

  • Java
  • Node.js
  • Пітон
  • PHP

Ви обмежені бібліотеками, які зазвичай упаковані з вашими мовами поза рамками. Будь ласка, не змушуйте мене знаходити незрозумілі бібліотеки, щоб ваш код працював.

Подання та судження

Опублікуйте свій вихідний код Scriptbot нижче і дайте йому прохолодну назву! Будь ласка, перелічіть також версію мови, яку ви використовували. Всі скрипти роботи будуть перевірені на предмет 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? - феерсум

Ні. Кожен сценарист розпочне раунд / поворот з 10 EP. Будь-який не витрачений ЕП піде на сміття.

Я думаю, що я це отримав, але просто для уточнення: з ботами A і B - це порядок подій A @ 10EP-> POVH MAP_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATTACK MAP_UPDATE B @ 9EP-> ATTACK ... або A @ 10EP-> POWE A @ 9EP-> ATTACK ... MAP_UPDATE B @ 10EP-> PUSH B @ 9EP-> ATTACK ... MAP_UPDATE? Іншими словами, чи всі дії в одному циклі запитів контролер-бот атомні? Якщо так, то чому цикл? Чому б не повернути єдиний файл із виконанням усіх дій? Інакше ботам доведеться виписувати власні файли стану, щоб відслідковувати послідовності мульти дії. Файл карти / статистики буде дійсним лише до першої дії. - КОТО

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

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

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

Якщо robotA підштовхує robotB до пастки, чи присвоєно роботові бали "завданої шкоди", що дорівнює поточному здоров'ю роботаB? - Майк Свіні

Так, це гарна ідея. Боту будуть надані окуляри пошкодження, рівні здоров’ю будь-якого бота, який він штовхає в пастку.


Чи можемо ми вірити, що mapфайл завжди оточений #символами? Якщо ні, то що станеться в тому випадку, якщо бот намагається піти з карти?
BrainSteel

@BrainSteel Так, карта завжди буде обмежена, #і ваш Scriptbot почне всередині цих меж.
Ріп Ліб

3
Якщо ви впевнені, що щось пропустили, чому б не опублікувати це в пісочниці ?
Мартін Ендер

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

1
Якщо robotA підштовхує роботB до пастки, чи присвоєно роботові бали "завданої шкоди", що дорівнює поточному стану здоров'я робота?
Логічний лицар

Відповіді:


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

Простий налаштований бот. Він боїться інших роботів і пасток. Це не нападе. Він ігнорує файл статистики і зовсім не заздалегідь роздумує.

Це в основному тест, щоб побачити, як правила працюватимуть, і як тупий противник для інших конкурентів.

Редагувати: Тепер пройде PASS, коли НЕ МУЖЕ краще.

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.
Rip Leeb

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 ви це писали?
Rip Leeb

@Nate 3.4.1 в win32

Дякуємо, що надіслали цей @horns. Це було дійсно весело спостерігати!
Ріп Ліб

1

Треба закінчити їжу

Пітон:

import sys
print 'PASS'

1
Я любив це - і чому, до біса import sys?
tomsmeding

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