스크립트 봇 워즈!


14

스크립트 봇 워즈!


결과가 나오고 Assassin 은 우리의 챔피언이며 2/3 경기에서 승리했습니다! Scriptbot을 제출 한 모든 분들께 감사드립니다! 특별 감사 에 대한 BestOpportunityBot 우수한 경로 지정을 표시하고 모든 작업 옵션의 전체를 사용했다.

지도 1

어 ass 신은 초기에 BestOpportunityBot을 꺼내었고 나머지 경기는 지루했습니다. 자세한 재생 방법은 여기입니다.

  1. 어 ass 신 : 10 HP, 10 데미지 Dealt, 3 데미지
  2. 회 피자 v3 : 10 HP, 0 피해 데미지, 0 피해
  3. 식사 완료 : 10 HP, 0 데미지 Dealt, 0 데미지
  4. BestOpportunityBot : 0 HP, 3 피해 데미지, 10 피해

지도 2

BestOpportunityBot은이 경기에서 대부분의 작업을 수행했지만 Assassin은 결국 그를 끌어낼 수있었습니다. 자세한 재생 방법은 여기입니다.

  1. 어 ass 신 : 2 HP, 10 데미지 Dealt, 9 데미지
  2. BestOpportunityBot : HP 0, 피해 32, 피해 10
  3. 회 피자 v3 : 0 HP, 0 피해 데미지, 12 피해
  4. 먹기 완료 : 0 HP, 0 데미지 Dealt, 11 데미지

지도 3

BestOpportunityBot은이 경기에서 모든 사람들을 함정에 빠뜨 렸습니다. 매우 시원합니다. 자세한 재생 방법은 여기입니다.

  1. 최우수 기회 봇 : HP 10, 데미지 30, 데미지 0
  2. 어 ass 신 : 0 HP, 0 데미지 Dealt, 0 데미지
  3. 식사 완료 : 0 HP, 0 피해 데미지, 0 피해
  4. 회 피자 v3 : 0 HP, 0 피해 데미지, 0 피해

답변 주셔서 감사합니다! Scriptbots는 4 개뿐이므로 아래의 각 맵에 하나씩 3 개의 무료 대전 경기에 대한 토너먼트 계획을 포기합니다. 가장 높은 승리 기록을 가진 스크립트 봇이 승리합니다. 동점 일 경우, 우리는 동점을 깨는 스크립트 봇이 먼저이기는 갑작스런 죽음에 빠질 것입니다.


당신의 임무는 당신이 그것을 받아들이기로 선택한다면, ASCII 맵을 가로 질러 상대를 파괴 할 수있는 Scriptbot을 코딩하는 것입니다. 각 전투는 각 Scriptbot이 에너지 포인트 (EP)를 소비하여 행동을 취할 수있는 무작위 시작 순서 턴제 게임의 형태를 취합니다. GameMaster 스크립트는 각 Scriptbot에 입력을 공급하고 출력을 해석합니다.

환경

가 읽을 수있는 각 Scriptbot는 자신의 디렉토리에 포함되어 mapstats/를 쓰기 파일 읽기 data파일을. 이 data파일은 유용한 영구 정보를 저장하는 데 사용할 수 있습니다.

통계 파일

stats파일에는 상대에 대한 정보가 포함되어 있으며 다음과 같이 형식이 지정됩니다. 각 플레이어는 별도의 행에 표시됩니다. 첫 번째 열은 플레이어 ID @입니다. 두 번째 열은 그 플레이어의 건강입니다.

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

지도 파일

map파일은 다음과 같이 표시 될 수 있습니다 ...

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

... 아니면 이거...

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

... 아니면 이거...

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

... 또는 완전히 다르게 보일 수 있습니다. 어느 쪽이든, 사용 된 문자와 그 의미는 동일하게 유지됩니다.

  • # 통과 할 수없고 뚫을 수없는 벽.
  • 1, 2, 3... 적 플레이어를 나타내는 숫자입니다. 이 숫자는 stats파일 의 플레이어 ID에 해당 합니다.
  • !함정. 이 위치로 이동하는 스크립트 봇은 즉시 죽습니다.
  • @ Scriptbot의 위치
  • 자유롭게 이동할 수있는 열린 공간.

게임 플레이

GameMaster 스크립트는 Scriptbot에 임의의 시작 순서를 할당합니다. 스크립트 봇은 여전히 ​​살아있는 동안이 순서대로 호출됩니다. 스크립트 봇에는 10의 체력 (HP)이 있으며, 매 턴마다 10의 에너지 포인트 (EP)로 시작하며, 이동 또는 공격에 사용할 수 있습니다. 매 턴이 시작될 때마다 Scriptbot은 한 HP를 치료하거나 10 HP에 이미 추가 EP가 주어집니다 (따라서 달리기는 실행 가능한 전략 일 수 있습니다).

한 대의 Scriptbot 만 살아남거나 100 턴이 지나면 전투가 끝납니다. 전투가 끝날 때 여러 대의 스크립트 봇이 살아있는 경우 그 장소는 다음 기준에 따라 결정됩니다.

  1. 대부분의 건강.
  2. 가장 많은 피해를 입었습니다.
  3. 가장 큰 피해.

스크립트 봇 입력

mapGameMaster는 Scriptbot이 읽을 수있는 파일에 전투 맵을 인쇄합니다 . 맵은 어떤 형식이든 취할 수 있으므로 Scriptbot이이를 해석 할 수 있어야합니다. EP를 나타내는 하나의 매개 변수와 함께 Scriptbot이 호출됩니다. 예를 들어 ...

:> example_scriptbot.py 3

Scriptbot은 모든 EP를 사용하거나 최대 10 11 번 을 사용할 때까지 호출됩니다 . 맵 및 통계 파일은 각 호출 전에 업데이트됩니다.

스크립트 봇 출력

스크립트 봇은 자신의 행동을 스타우트로 출력해야합니다. 가능한 조치 목록은 다음과 같습니다.

  • MOVE <DIRECTION> <DISTANCE>

    1 EP 당 비용 DISTANCE. 이 MOVE명령은 Scriptbot을지도 주위로 이동시킵니다. 벽이나 다른 Scriptbot과 같은 방해물이있는 경우 GameMaster는 가능한 한 Scriptbot을 움직입니다. 경우 DISTANCEScriptbot의 나머지 EP보다 더 큰이 주어 그 EP가 소진 될 때까지 GameMaster는 Scriptbot 이동합니다. DIRECTION나침반의 임의의 방향 일 수있다 N, E, S, 또는 W.

  • PUSH <DIRECTION> <DISTANCE>

    1 EP 당 비용 DISTANCE. 이 PUSH명령을 사용하면 Scriptbot이 다른 Scriptbot을 이동할 수 있습니다. 명령을 실행하는 Scriptbot은 푸시중인 Scriptbot 바로 옆에 있어야합니다. 스크립트 봇을 밀고있는 물체를 막지 않으면 두 스크립트 봇은 지시 된 방향으로 움직입니다. DIRECTIONDISTANCE와 동일 MOVE명령.

  • ATTACK <DIRECTION>

    하나의 EP 비용이 든다. 이 ATTACK명령은 발행하는 Scriptbot 바로 옆과 지정된 방향으로 모든 Scriptbot에 1의 피해를줍니다. 명령 DIRECTION과 동일합니다 MOVE.

  • PASS

    당신의 차례를 종료합니다.

지원되는 언어

이것을 합리적으로 유지하기 위해 다음 언어를 받아들입니다.

  • 자바
  • Node.js
  • 파이썬
  • PHP

기본적으로 언어와 함께 패키지로 제공되는 라이브러리로 제한됩니다. 코드가 작동하도록 모호한 라이브러리를 찾지 마십시오.

제출 및 심사

아래에 Scriptbot 소스 코드를 게시하고 멋진 이름을 지정하십시오! 사용한 언어의 버전도 기재하십시오. 모든 Scriptbot은 tomfoolery에 대해 검토되므로 잘 설명하고 코드를 난독 화하지 마십시오.

하나 이상의 출품작을 제출할 수 있지만 동일한 출품작의 버전이 아닌 완전히 고유 한 출품작으로 만드십시오. 예를 들어, 저그 러쉬 봇과 고릴라 전쟁 봇을 코딩 할 수 있습니다. 괜찮아. Zerg Rush v1, Zerg Rush v2 등을 게시하지 마십시오.

11 월 7 일에 모든 답변을 수집하고 최초 검토를 통과 한 답변이 토너먼트 브래킷에 추가됩니다. 챔피언은 허용 된 답변을받습니다. 이상적인 브라켓은 아래와 같습니다. 정확히 16 개의 엔트리가 없을 가능성이 있기 때문에 일부 괄호는 3 개 또는 2 개의 봇일 수 있습니다. 나는 괄호를 가능한 한 공정하게 만들려고 노력할 것이다. 필요한 편애 (예 : 1 주일이 필요한 경우)가 먼저 제출 된 봇에게 제공됩니다.

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

Q & A

세부 정보를 놓친 것 같습니다. 궁금한 점이 있으면 언제든지 문의 해주세요.

지도 파일이 항상 # 기호로 둘러싸여 있다고 믿을 수 있습니까? 그렇지 않은 경우, 봇이지도에서 벗어나려고하면 어떻게됩니까? -BrainSteel

예,지도는 항상 #로 묶여 있으며 Scriptbot은이 범위 내에서 시작합니다.

PUSH 명령에 지정된 방향으로 봇이 없으면 명령은 어떻게 작동합니까? -BrainSteel

GameMaster는 아무 것도하지 않고 EP를 전혀 사용하지 않으며 Scriptbot을 다시 호출합니다.

미사용 EP가 축적됩니까? -퇴적물

아니요. 각 Scriptbot은 10 EP로 라운드 / 턴을 시작합니다. 소비하지 않은 EP는 낭비됩니다.

봇 A와 B의 경우 이벤트 순서는 A @ 10EP-> MOVE MAP_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATTACK MAP_UPDATE B @ 9EP->입니다. 공격 ... 또는 A @ 10EP-> MOVE A @ 9EP-> 공격 ... MAP_UPDATE B @ 10EP-> PUSH B @ 9EP-> 공격 ... MAP_UPDATE? 다시 말해, 하나의 컨트롤러 봇 쿼리 루프에서 모든 동작이 원 자성입니까? 그렇다면 왜 루프입니까? 모든 작업이 완료된 단일 파일을 반환하지 않는 이유는 무엇입니까? 그렇지 않으면 봇은 멀티 액션 시퀀스를 추적하기 위해 자체 상태 파일을 작성해야합니다. map / stats 파일은 첫 번째 작업 전에 만 유효합니다. -코토

두 번째 예는 가깝지만 옳지 않습니다. 한 턴 동안, 스크립트 봇은 EP가 소비 될 때까지 또는 최대 11 번 반복해서 호출됩니다. 맵 및 통계 파일은 각 호출 전에 업데이트됩니다. 루프는 봇이 잘못된 출력을 제공하는 경우에 유용합니다. GameMaster는 잘못된 출력을 처리하고 봇을 다시 시작하여 봇에게 실수를 바로 잡을 수있는 기회를줍니다.

테스트를 위해 GameMaster 스크립트를 출시 하시겠습니까? -이치 빈킨 바움

GameMaster 스크립트는 출시되지 않습니다. 봇의 행동을 테스트하기 위해 맵 및 통계 파일을 생성하는 것이 좋습니다.

robotA가 robotB를 트랩으로 밀어 넣는 경우 robotA는 현재 로봇 B의 상태와 같은 "손상 처리 된"포인트로 인정됩니까? -마이크 스위니

예, 좋은 생각입니다. 봇은 함정에 밀어 넣는 봇의 체력과 동일한 피해 점을받습니다.


map파일이 항상 #기호로 둘러싸여 있다고 믿을 수 있습니까? 그렇지 않은 경우, 봇이지도에서 벗어나려고하면 어떻게됩니까?
BrainSteel

@BrainSteel 예, 맵은 항상 경계가 #되고 Scriptbot은이 경계 내에서 시작합니다.
Rip Leeb

3
당신이 무언가를 놓쳤다 고 확신한다면, 왜 샌드 박스에 게시하지 않습니까?
Martin Ender

2
@ MartinBüttner 나는 이것을 완전히 철저히 생각했습니다. 나는 단지 친절하고 질문을 환영한다는 것을 분명히하려고 노력했다.
Rip Leeb

1
robotA가 robotB를 트랩으로 밀어 넣는 경우 robotA는 현재 로봇 B의 상태와 같은 "손상 처리 된"포인트로 인정됩니까?
논리 기사

답변:


1

어 ass 신 (자바 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 버전으로 작성 되었습니까?
Rip Leeb

@ Nate 1.7을 사용했습니다.
CommonGuy

3

회 피자 v3

간단한 마음을 가진 봇. 다른 로봇과 함정을 두려워합니다. 공격하지 않습니다. stats 파일을 무시하고 전혀 생각하지 않습니다.

이것은 주로 규칙이 어떻게 작동하는지, 다른 경쟁자들에게는 멍청한 상대로서의 테스트입니다.

편집 : 더 나은 이동이 없을 때 이제 통과합니다.

편집 2 : 로봇은 '123'대신 '1234'일 수 있습니다

파이썬 코드 :

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
@ 네이트 감사합니다. 봇을 변경했습니다. 규칙에서이를 명확하게하고 싶을 수도 있습니다. 나는 이것을 오해 한 유일한 사람이 아닐 수도 있습니다.
Logic Knight

3

최고의 기회 봇

이것은 내가 의도했던 것보다 조금 더 길어졌습니다 ... 그리고 나는 회전 규칙을 완전히 이해했는지 잘 모르겠습니다. 그래서 우리는 이것이 어떻게되는지 볼 것입니다.

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

어떤 버전의 파이썬으로 작성 했습니까?
Rip Leeb

win32의 @Nate 3.4.1

@horns를 제출해 주셔서 감사합니다. 보는 것은 정말로 재미 있었다!
Rip Leeb

1

먹어 마무리

파이썬 :

import sys
print 'PASS'

1
나는 그것에 갔다-그리고 왜 도대체 import sys?
tomsmeding

1
@tomsmeding 글쎄, 나는 물건을 복사해야했습니다. 그리고 나는 물론 식사를 마쳤을 때 args를 읽어야한다고 생각했습니다.
Timtech
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.