Scriptbot Warz!

14

Scriptbot Warz!


Hasilnya sudah tiba dan Assassin adalah juara kami, memenangkan 2 dari 3 pertandingan! Terima kasih kepada semua orang yang mengirimkan Scriptbots mereka! Terima kasih khusus kepada klakson untuk BestOpportunityBot yang menampilkan lintasan luar biasa dan memanfaatkan sepenuhnya semua opsi tindakan.

Peta 1

Assassin mengeluarkan BestOpportunityBot sejak awal, dan sisa pertandingan itu cukup membosankan. Rinci play-by-play di sini.

  1. Pembunuh: 10 HP, 10 Damage Dealt, 3 Damage Diambil
  2. The Avoider v3: 10 HP, 0 Damage Dealt, 0 Damage Diambil
  3. Harus Selesai Makan: 10 HP, 0 Damage Dealt, 0 Damage Diambil
  4. BestOpportunityBot: 0 HP, 3 Damage Dealt, 10 Damage Diambil

Peta 2

BestOpportunityBot melakukan sebagian besar pekerjaan pada pertandingan ini, tetapi Assassin mampu membawanya keluar pada akhirnya. Rinci play-by-play di sini.

  1. Pembunuh: 2 HP, 10 Damage Dealt, 9 Damage Diambil
  2. BestOpportunityBot: 0 HP, 32 Damage Dealt, 10 Damage Diambil
  3. The Avoider v3: 0 HP, 0 Damage Dealt, 12 Damage Diambil
  4. Harus Selesai Makan: 0 HP, 0 Damage Dealt, 11 Damage Diambil

Peta 3

BestOpportunityBot mendorong semua orang ke jebakan pada pertandingan ini. Sangat keren. Rinci play-by-play di sini.

  1. BestOpportunityBot: 10 HP, 30 Damage Dealt, 0 Damage Diambil
  2. Pembunuh: 0 HP, 0 Damage Dealt, 0 Damage Diambil
  3. Harus Selesai Makan: 0 HP, 0 Damage Dealt, 0 Damage Diambil
  4. The Avoider v3: 0 HP, 0 Damage Dealt, 0 Damage Diambil

Terima kasih atas jawaban Anda! Karena hanya ada 4 Scriptbots, kami meninggalkan rencana turnamen untuk tiga pertandingan gratis untuk semua, satu di setiap peta di bawah ini. Scripbbot dengan catatan kemenangan tertinggi menang. Dalam hal seri, kita akan masuk ke dalam kematian mendadak dimana scriptbot yang memecahkan dasi pertama menang.


Tugas Anda, jika Anda memilih untuk menerimanya, adalah membuat kode Scriptbot yang dapat melintasi peta ASCII dan menghancurkan lawan-lawannya. Setiap pertempuran akan mengambil bentuk permainan berbasis giliran mulai urutan acak di mana setiap Scriptbot memiliki kesempatan untuk menghabiskan poin energi mereka (EP) untuk mengambil tindakan. Skrip GameMaster akan memasukkan input ke, dan menginterpretasikan output dari setiap Scriptbot.

Lingkungan Hidup

Setiap Scriptbot terdapat dalam direktori sendiri di mana ia dapat membaca dari mapdan statsfile dan membaca / menulis ke datafile. The datafile dapat digunakan untuk menyimpan informasi yang gigih Anda mungkin menemukan berguna.

File statistik

The statsfile berisi informasi tentang lawan Anda dan diformat sebagai berikut. Setiap pemain diwakili pada baris terpisah. Kolom pertama adalah ID pemain ( @berarti Anda). Kolom kedua adalah kesehatan pemain itu.

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

File peta

The mapFile mungkin terlihat seperti ini ...

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

... atau ini...

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

... atau ini...

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

... atau mungkin terlihat sama sekali berbeda. Bagaimanapun, karakter yang digunakan dan artinya akan tetap sama:

  • # Dinding, tidak bisa dilewati dan tidak bisa ditembus.
  • 1, 2, 3... Sebuah angka yang mewakili pemain musuh. Angka-angka ini sesuai dengan ID pemain dalam statsfile.
  • !Sebuah jebakan. Scriptbots yang pindah ke lokasi ini akan langsung mati.
  • @ Lokasi Scriptbot Anda.
  • Buka ruang tempat Anda bebas bergerak.

Gameplay

Skrip GameMaster akan menetapkan urutan awal acak ke Scriptbots. Scriptbots kemudian dipanggil dalam urutan ini saat mereka masih hidup. Scriptbots memiliki 10 Health Points (HP), dan mulai dengan 10 Energy Points (EP) setiap belokan, yang dapat mereka gunakan untuk bergerak atau menyerang. Pada awal setiap belokan, Scriptbot akan sembuh untuk satu HP, atau diberikan satu EP tambahan jika sudah mencapai 10 HP (dengan demikian menjalankan mungkin merupakan strategi yang layak di kali).

Pertempuran berakhir ketika hanya satu Scriptbot yang selamat atau ketika 100 putaran telah berlalu. Jika beberapa Scriptbots hidup di akhir pertempuran, tempat mereka ditentukan berdasarkan kriteria berikut:

  1. Sebagian besar kesehatan.
  2. Sebagian besar kerusakan ditangani.
  3. Kerusakan paling besar terjadi.

Input Scriptbot

GameMaster akan mencetak peta pertempuran ke file bernama mapyang Scriptbot akan memiliki akses untuk membaca. Peta itu bisa berupa apa saja, jadi penting bahwa Scriptbot dapat menafsirkannya. Scriptbot Anda akan dipanggil dengan satu parameter yang menunjukkan EP. Sebagai contoh...

:> example_scriptbot.py 3

Scriptbot akan dipanggil sampai menghabiskan semua EP atau maksimum 10 11 kali. File peta dan statistik diperbarui sebelum setiap doa.

Output Scriptbot

Scriptbots harus menampilkan aksi mereka menjadi gagah. Daftar tindakan yang mungkin adalah sebagai berikut:

  • MOVE <DIRECTION> <DISTANCE>

    Biaya 1 EP per DISTANCE. The MOVEperintah bergerak Scriptbot Anda di sekitar peta. Jika ada sesuatu seperti tembok atau Scriptbot lain, GameMaster akan memindahkan Scriptbot Anda sejauh mungkin. Jika DISTANCEEP yang lebih besar dari sisa Scriptbot diberikan, GameMaster akan memindahkan Scriptbot sampai EP-nya habis. DIRECTIONmungkin setiap arah kompas dari N, E, S, atau W.

  • PUSH <DIRECTION> <DISTANCE>

    Biaya 1 EP per DISTANCE. The PUSHperintah memungkinkan Scriptbot untuk bergerak Scriptbot lain. Scriptbot yang mengeluarkan perintah harus langsung di sebelah Scriptbot yang sedang didorong. Kedua Scriptbot akan bergerak ke arah yang ditunjukkan jika tidak ada objek yang menghalangi Scriptbot yang didorong. DIRECTIONdan DISTANCEsama seperti untuk MOVEperintah.

  • ATTACK <DIRECTION>

    Biaya satu EP. The ATTACKperintah Penawaran 1 kerusakan Scriptbot apapun secara langsung di sebelah Scriptbot mengeluarkan dan dalam arah yang ditentukan. DIRECTIONsama seperti untuk MOVEperintah.

  • PASS

    Berakhir giliran Anda.

Bahasa yang didukung

Agar ini masuk akal bagi saya, saya akan menerima bahasa berikut:

  • Jawa
  • Node.js
  • Python
  • PHP

Anda terbatas pada perpustakaan yang biasanya dikemas dengan bahasa Anda di luar kotak. Tolong jangan membuat saya menemukan perpustakaan yang tidak jelas untuk membuat kode Anda berfungsi.

Pengajuan dan Penilaian

Posting kode sumber Scriptbot Anda di bawah ini dan berikan nama yang keren! Harap cantumkan juga versi bahasa yang Anda gunakan. Semua Scriptbots akan ditinjau untuk tindakan gila-gilaan jadi silakan komentar dengan baik dan jangan mengaburkan kode Anda.

Anda dapat mengirimkan lebih dari satu entri, tetapi harap buat entri yang benar-benar unik, dan bukan versi entri yang sama. Misalnya, Anda mungkin ingin kode bot Zerg Rush dan bot Gorilla Warfare. Tidak apa-apa. Jangan memposting Zerg Rush v1, Zerg Rush v2, dll.

Pada 7 November saya akan mengumpulkan semua jawaban dan jawaban yang lolos ulasan awal akan ditambahkan ke braket turnamen. Sang juara mendapat jawaban yang diterima. Braket ideal ditunjukkan di bawah ini. Karena kemungkinan tidak ada persis 16 entri, beberapa tanda kurung mungkin berakhir hanya tiga atau bahkan dua bot. Saya akan mencoba membuat braket seadil mungkin. Favoritisme apa pun yang diperlukan (dalam hal diperlukan waktu seminggu, misalnya) akan diberikan kepada bot yang diserahkan terlebih dahulu.

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

T&J

Saya yakin saya melewatkan beberapa detail, jadi jangan ragu untuk bertanya!

Bolehkah kami percaya bahwa file peta selalu dikelilingi # simbol? Jika tidak, apa yang terjadi jika bot mencoba keluar dari peta? - BrainSteel

Ya peta akan selalu dibatasi oleh # dan Scriptbot Anda akan mulai di dalam batas-batas ini.

Jika tidak ada bot yang ada di arah yang ditentukan dalam perintah PUSH, bagaimana fungsinya? - BrainSteel

GameMaster tidak akan melakukan apa-apa, nol EP akan dihabiskan, dan Scriptbot akan dipanggil lagi.

Apakah EP yang tidak terpakai menumpuk? - feersum

Tidak. Setiap Scriptbot akan memulai putaran / putaran dengan 10 EP. EP mana pun yang tidak dihabiskan akan sia-sia.

Saya pikir saya sudah mendapatkannya, tetapi hanya untuk mengklarifikasi: dengan bot A dan B, adalah urutan acara A @ 10EP-> MOVE MAP_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATTACK MAP_UPDATE B @ 9EP-> ATTACK ..., atau A @ 10EP-> MOVE A @ 9EP-> ATTACK ... MAP_UPDATE B @ 10EP-> PUSH B @ 9EP-> ATTACK ... MAP_UPDATE? Dengan kata lain, apakah semua aksi dalam satu atomic loop controller-bot query? Jika demikian, mengapa loop? Mengapa tidak mengembalikan satu file dengan semua tindakan yang harus diselesaikan? Kalau tidak, bot harus menulis file negara mereka sendiri untuk melacak urutan multi-tindakan. File peta / statistik hanya akan valid sebelum tindakan pertama. - COTO

Contoh kedua Anda dekat, tetapi tidak tepat. Selama belokan, Scriptbot dipanggil berulang kali hingga EP mereka habis, atau maksimal 11 kali. File peta dan statistik diperbarui sebelum setiap doa. Loop ini berguna jika bot memberikan output yang tidak valid. GameMaster akan berurusan dengan output yang tidak valid dan melibatkan bot lagi, memberi bot kesempatan untuk memperbaiki kesalahan itu.

Apakah Anda akan merilis skrip GameMaster untuk pengujian? - IchBinKeinBaum

Skrip GameMaster tidak akan dirilis. Saya mendorong Anda untuk membuat file peta dan statistik untuk menguji perilaku bot Anda.

Jika robotA mendorong robotB ke dalam perangkap, apakah robotA dikreditkan dengan poin "Damage Dealt" sama dengan kesehatan robotB saat ini? - Mike Sweeney

Ya, itu ide yang bagus. Bot akan diberikan poin kerusakan yang setara dengan kesehatan bot apa pun yang didorongnya ke dalam perangkap.

Rip Leeb
sumber
Bolehkah kami percaya bahwa mapfile selalu dikelilingi oleh #simbol? Jika tidak, apa yang terjadi jika bot mencoba keluar dari peta?
BrainSteel
@ OtakBrain Ya peta akan selalu dibatasi oleh #dan Scriptbot Anda akan mulai di dalam batas-batas ini.
Rip Leeb
3
Jika Anda yakin telah melewatkan sesuatu, mengapa tidak mempostingnya di kotak pasir ?
Martin Ender
2
@ MartinBüttner Saya sudah memikirkan ini dengan seksama. Saya hanya berusaha bersikap ramah dan menjelaskan bahwa pertanyaan-pertanyaan itu disambut baik.
Rip Leeb
1
Jika robotA mendorong robotB ke dalam perangkap, apakah robotA dikreditkan dengan poin "Damage Dealt" sama dengan kesehatan robotB saat ini?
Logic Knight

Jawaban:

1

Assassin (Jawa 1.7)

Berusaha membunuh musuh jika memungkinkan, jika tidak bergerak satu bidang. Cukup bagus dalam menemukan jalan ke musuh, tetapi tidak melakukan apa pun untuk bersembunyi dari bot lain.

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class Assassin {
    private final Path dataPath = Paths.get("data");
    private final Path mapPath = Paths.get("map");
    private final Path statsPath = Paths.get("stats");
    private final List<Player> players = new ArrayList<>();
    private final int energy;
    private Map map = null;

    public Assassin(int energy) {
        this.energy = energy;
    }

    private void doSomething() {
        if (dataFileEmpty()) {
            calculateTurn();
        }
        printStoredOutput();
    }

    private boolean dataFileEmpty() {
        try {
            return !Files.exists(dataPath) || Files.size(dataPath)  == 0;
        } catch (IOException e) {
            return true;
        }
    }

    private void printStoredOutput() {
        try {
            List<String> lines = Files.readAllLines(dataPath, StandardCharsets.UTF_8);
            //print first line
            System.out.println(lines.get(0));           
            //delete first line
            lines.remove(0);
            Files.write(dataPath, lines, StandardCharsets.UTF_8);
        } catch (IOException e) {
            System.out.println("PASS");
        }
    }

    private void calculateTurn() {
        try {
            readStats();
            readMap();
            if (!tryKill())  {
                sneakCloser();
            }
        } catch (IOException e) {}
    }

    private void readStats() throws IOException{
        List<String> stats = Files.readAllLines(statsPath, StandardCharsets.UTF_8);
        for (String stat : stats) {
            String[] infos = stat.split(",");
            int hp = Integer.parseInt(infos[1].replace("HP", ""));
            players.add(new Player(stat.charAt(0), hp));
        }
    }

    private void readMap() throws IOException{
        List<String> lines = Files.readAllLines(mapPath, StandardCharsets.UTF_8);
        Field[][] fields = new Field[lines.size()][lines.get(0).length()];
        for (int row = 0; row < lines.size(); row++) {
            String line = lines.get(row);
            for (int col = 0; col < line.length(); col++) {
                fields[row][col] = new Field(line.charAt(col), row, col);
            }
        }
        map = new Map(fields);
    }

    private boolean tryKill() {     
        Field me = map.getMyField();
        for (Field field : map.getEnemyFields()) {
            for (Field surrField : map.surroundingFields(field)) { 
                List<Direction> dirs = map.path(me, surrField);
                if (dirs != null) {
                    for (Player player : players) {
                        //can kill this player
                        int remainderEnergy = energy - dirs.size() - player.hp;
                        if (player.id == field.content && remainderEnergy >= 0) {
                            //save future moves
                            List<String> commands = new ArrayList<>();
                            for (Direction dir : dirs) {
                                commands.add("MOVE " + dir + " 1");
                            }
                            //attacking direction
                            Direction attDir = surrField.dirsTo(field).get(0);
                            for (int i = 0; i < player.hp; i++) {
                                commands.add("ATTACK " + attDir);                               
                            }
                            if (remainderEnergy > 0) {
                                commands.add("PASS");
                            }
                            try {
                                Files.write(dataPath, commands, StandardCharsets.UTF_8);
                            } catch (IOException e) {}
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private void sneakCloser() {        
        Field me = map.getMyField();
        for (Direction dir : Direction.values()) {
            if (!map.move(me, dir).blocked()) {
                List<String> commands = new ArrayList<>();
                commands.add("MOVE " + dir + " 1");
                commands.add("PASS");
                try {
                    Files.write(dataPath, commands, StandardCharsets.UTF_8);
                } catch (IOException e) {}
                return;
            }
        }
    }

    public static void main(String[] args) {
        try {
            new Assassin(Integer.parseInt(args[0])).doSomething();
        } catch (Exception e) {
            System.out.println("PASS");
        }
    }

    class Map {
        private Field[][] fields;

        public Map(Field[][] fields) {
            this.fields = fields;
        }

        public Field getMyField() {
            for (Field[] rows : fields) {
                for (Field field : rows) {
                    if (field.isMyPos()) {
                        return field;
                    }
                }
            }
            return null; //should never happen
        }   

        public List<Field> getEnemyFields() {
            List<Field> enemyFields = new ArrayList<>();
            for (Field[] rows : fields) {
                for (Field field : rows) {
                    if (field.hasEnemy()) {
                        enemyFields.add(field);
                    }
                }
            }
            return enemyFields;
        }

        public List<Field> surroundingFields(Field field) {
            List<Field> surrFields = new ArrayList<>();
            for (Direction dir : Direction.values()) {
                surrFields.add(move(field, dir));
            }
            return surrFields;
        }

        public Field move(Field field, Direction dir) {
            return fields[field.row + dir.rowOffset][field.col + dir.colOffset];
        }

        public List<Direction> path(Field from, Field to) {
            List<Direction> dirs = new ArrayList<>();
            Field lastField = from;
            boolean changed = false;

            for (int i = 0; i < energy && lastField != to; i++) {
                List<Direction> possibleDirs = lastField.dirsTo(to);
                changed = false;
                for (Direction dir : possibleDirs) {
                    Field nextField = move(lastField, dir);
                    if (!nextField.blocked()) {
                        lastField = nextField;
                        changed = true;
                        dirs.add(dir);
                        break;
                    }
                }
                if (!changed) {
                    return null; //not possible
                }
            }
            if (lastField != to) {
                return null; //not enough energy
            }           
            return dirs;
        }
    }

    class Field {
        private char content;
        private int row;
        private int col;

        public Field(char content, int row, int col) {
            this.content = content;
            this.row = row;
            this.col = col;
        }

        public boolean hasEnemy() {
            return content == '1' || content == '2' || content == '3' || content == '4';
        }

        public List<Direction> dirsTo(Field field) {
            List<Direction> dirs = new ArrayList<>();
            int distance = Math.abs(row - field.row) + Math.abs(col - field.col);

            for (Direction dir : Direction.values()) {
                int dirDistance = Math.abs(row - field.row + dir.rowOffset) + 
                        Math.abs(col - field.col + dir.colOffset);
                if (dirDistance < distance) {
                    dirs.add(dir);
                }
            }
            if (dirs.size() == 1) { //add near directions
                for (Direction dir : dirs.get(0).nearDirections()) {
                    dirs.add(dir);
                }
            }
            return dirs;
        }

        public boolean isMyPos() {
            return content == '@';
        }

        public boolean blocked() {
            return content != ' ';
        }
    }

    class Player {
        private char id;
        private int hp;

        public Player(char id, int hp) {
            this.id = id;
            this.hp = hp;
        }
    }

    enum Direction {
        N(-1, 0),S(1, 0),E(0, 1),W(0, -1);

        private final int rowOffset;
        private final int colOffset;

        Direction(int rowOffset, int colOffset) {
            this.rowOffset = rowOffset;
            this.colOffset = colOffset;
        }

        public Direction[] nearDirections() {
            Direction[] dirs = new Direction[2];
            for (int i = 0; i < Direction.values().length; i++) {
                Direction currentDir = Direction.values()[i];
                if (Math.abs(currentDir.rowOffset) != Math.abs(this.rowOffset)) {
                    dirs[i%2] = currentDir;
                }
            }
            return dirs;
        }
    }
}
CommonGuy
sumber
Versi Java apa ini ditulis?
Rip Leeb
@Nate, saya menggunakan 1,7.
CommonGuy
3

The Avoider v3

Bot berpikiran sederhana. Ia takut dengan robot dan perangkap lainnya. Itu tidak akan menyerang. Itu mengabaikan file statistik dan tidak berpikir ke depan sama sekali.

Ini terutama merupakan ujian untuk melihat bagaimana aturan akan bekerja, dan sebagai lawan yang bodoh bagi pesaing lain.

Sunting: Sekarang akan LULUS ketika tidak ada MOVE yang lebih baik.

Sunting2: Robot bisa '1234' bukan '123'

Kode 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?
Ksatria Logika
sumber
elif ch in '123':Anda ingin mencari setidaknya 1234. Jika Anda bot 3, daftar lawannya adalah 124.
Rip Leeb
1
@Nate, terima kasih. Saya telah mengubah bot. Anda mungkin ingin memperjelas ini dalam aturan. Saya mungkin bukan satu-satunya yang salah paham tentang ini.
Logic Knight
3

BestOpportunityBot

Ini akhirnya menjadi sedikit lebih lama dari yang saya maksudkan ... dan saya tidak yakin apakah saya memahami aturan untuk belokan sepenuhnya, jadi kita akan lihat bagaimana ini terjadi.

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

sumber
versi python apa yang Anda tulis ini?
Rip Leeb
@Nate 3.4.1 pada win32
Terima kasih telah mengirimkan @horns yang satu ini. Sangat menyenangkan untuk ditonton!
Rip Leeb
1

Harus Selesai Makan

Python:

import sys
print 'PASS'
Timtech
sumber
1
Saya suka itu - dan mengapa import sys?
tommeding
1
@tommeding Ya, saya harus menyalin beberapa hal. Dan saya pikir kalau-kalau saya perlu membaca beberapa args :) ketika saya selesai makan, tentu saja.
Timtech