Scriptbot Warz!


14

Scriptbot Warz!


Sonuçlar geldi ve Assassin 3 maçın 2'sini kazanan şampiyonumuz! Senaryolarını gönderen herkese teşekkürler! Özel teşekkürler boynuzları için BestOpportunityBot mükemmel pathing görüntülenen ve tüm eylem seçeneklerinin tam kullanmıştır.

Harita 1

Assassin BestOpportunityBot'u erkenden çıkardı ve maçın geri kalanı oldukça sıkıcıydı. Burada ayrıntılı play-by-play.

  1. Suikastçı: 10 HP, 10 Hasar Verilen, 3 Hasar Alınan
  2. Avoider v3: 10 HP, 0 Verilen Hasar, 0 Alınan Hasar
  3. Yemek Bitirmeli: 10 BG, Verilen Hasar, 0 Alınan Hasar
  4. En İyi Fırsat: 0 HP, 3 Hasar Verilen, 10 Hasar Alınan

Harita 2

BestOpportunityBot bu maçtaki çalışmaların çoğunu yaptı, ancak Assassin sonunda onu çıkardı. Burada ayrıntılı play-by-play.

  1. Suikastçı: 2 HP, 10 Hasar Verilen, 9 Alınan Hasar
  2. En İyi Fırsat: 0 HP, 32 Hasar Verilen, 10 Hasar Alınan
  3. Avoider v3: 0 Beygir gücü, 0 Verilen Hasar, 12 Alınan Hasar
  4. Yemek Bitirmeli: 0 Beygir gücü, 0 Verilen Hasar, 11 Alınan Hasar

Harita 3

BestOpportunityBot bu maçta herkesi tuzağa düşürdü. Çok havalı. Burada ayrıntılı play-by-play.

  1. En İyi Fırsat: 10 HP, 30 Hasar Verilen, 0 Alınan Hasar
  2. Suikastçı: 0 BG, Verilen Hasar, 0 Alınan Hasar
  3. Yemek Bitirmeli: 0 Beygir gücü, 0 Verilen Hasar, 0 Alınan Hasar
  4. Avoider v3: 0 Beygir gücü, 0 Verilen Hasar, 0 Alınan Hasar

Cevaplarınız için teşekkürler! Sadece 4 Scriptbot olduğu için, aşağıdaki haritaların her birinde birer tane olmak üzere üç ücretsiz maç için turnuva planlarını terk ediyoruz. En yüksek kazanma rekoruna sahip scriptbot kazanır. Beraberlik durumunda, aniden ölüme gireceğiz, burada bağı ilk koparan scriptbot kazanır.


Göreviniz, kabul etmeyi seçerseniz, bir ASCII haritasını geçip rakiplerini yok edebilecek bir Scriptbot'u kodlamaktır. Her savaş, her Scriptbot'un harekete geçmek için enerji puanlarını (EP) kullanma şansına sahip olduğu, rastgele başlangıç ​​sırası, sıra tabanlı bir oyun şeklinde olacaktır. GameMaster betiği, her Scriptbot'a girdi besler ve çıktıyı yorumlar.

çevre

Her Scriptbot, ve dizinlerinden okuyabileceği mapve statsdosyayı okuyabileceği / yazabileceği kendi dizininde bulunur data. dataDosya işinize yarayabilecek herhangi kalıcı bilgileri depolamak için kullanılabilir.

İstatistik Dosyası

statsDosyanın rakipler hakkında bilgi içerir ve aşağıdaki gibi biçimlendirilir. Her oyuncu ayrı bir sırada gösterilir. İlk sütun bir oyuncu kimliğidir ( @sizi ifade eder). İkinci sütun o oyuncunun sağlığıdır.

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

Harita Dosyası

mapDosya bu gibi görünebilir ...

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

... veya bu...

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

... veya bu...

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

... ya da tamamen farklı görünebilir. Her iki durumda da, kullanılan karakterler ve anlamları aynı kalır:

  • # Geçilemez ve geçilemez bir duvar.
  • 1, 2, 3... bir düşman oyuncuyu temsil eden bir sayı. Bu sayılar, statsdosyadaki oynatıcı kimliğine karşılık gelir .
  • !Bir tuzak. Bu yerlere hareket eden scriptler hemen ölecek.
  • @ Scriptbot'unuzun konumu.
  • Hareket etmekte özgür olduğunuz açık alan.

Oynanış

GameMaster betiği, Betik botlarına rastgele bir başlangıç ​​sırası atar. Daha sonra Scriptbotlar hala hayatta iken bu sırayla çağrılır. Scriptbot'larda 10 Sağlık Puanı (HP) vardır ve her turda 10 Enerji Puanı (EP) ile başlarlar; Her turun başlangıcında, bir Scriptbot bir HP için iyileşir veya zaten 10 HP'de ise ilave bir EP daha verilir (bu nedenle çalıştırma bazen uygun bir strateji olabilir).

Savaş sadece bir Scriptbot hayatta kaldığında veya 100 tur geçtiğinde sona erer. Bir savaşın sonunda birden fazla Scriptbot yaşıyorsa, yerleri aşağıdaki kriterlere göre belirlenir:

  1. Çoğu sağlık.
  2. En çok hasar verildi.
  3. En çok hasar alındı.

Scriptbot Girişi

GameMaster savaş haritasını mapScriptbot'un okumak için erişebileceği bir dosyaya yazdıracaktır. Harita herhangi bir biçimde olabilir, bu yüzden Scriptbot'un onu yorumlayabilmesi önemlidir. Scriptbot'unuz EP'yi gösteren bir parametre ile çağrılır. Örneğin...

:> example_scriptbot.py 3

Scriptbot tüm EP'sini veya en fazla 10 11 kez harcayana kadar çağrılır . Harita ve istatistik dosyaları her çağrıdan önce güncellenir.

Scriptbot Çıkışı

Senaryo botları eylemlerini şişmanlamak için çıkarmalıdır. Olası eylemlerin bir listesi aşağıdaki gibidir:

  • MOVE <DIRECTION> <DISTANCE>

    Başına 1 EP maliyeti DISTANCE. MOVEKomut haritası etrafında Scriptbot taşır. Bir duvar veya başka bir Scriptbot gibi bir şey varsa, GameMaster Scriptbot'unuzu mümkün olduğunca hareket ettirir. DISTANCEScriptbot'un kalan EP'sinden daha büyük bir değer verilirse, GameMaster, Scriptbot'u EP bitene kadar hareket ettirir. DIRECTIONherhangi bir pusula yön olabilir N, E, Sya da W.

  • PUSH <DIRECTION> <DISTANCE>

    Başına 1 EP maliyeti DISTANCE. PUSHKomut başka Scriptbot taşımak için bir Scriptbot sağlar. Komutu veren Scriptbot, doğrudan itilen Scriptbot'un yanında olmalıdır. Scriptbot'u iten bir nesne yoksa, her iki Scriptbot da belirtilen yönde hareket edecektir. DIRECTIONve komutla DISTANCEaynıdır MOVE.

  • ATTACK <DIRECTION>

    Bir EP maliyeti. ATTACKKomut veren Scriptbot için belirlenmiş yöne hemen yanında herhangi Scriptbot 1 damage. DIRECTION, MOVEkomutla aynıdır .

  • PASS

    Sıranızı sonlandırır.

Desteklenen Diller

Bunu benim için makul tutmak için aşağıdaki dilleri kabul edeceğim:

  • Java
  • node.js
  • piton
  • PHP

Kutusundan çıkar çıkmaz dillerinizle birlikte paketlenmiş kitaplıklarla sınırlısınız. Lütfen kodunuzun çalışması için belirsiz kütüphaneleri bulmama izin vermeyin.

Teslim ve Değerlendirme

Scriptbot kaynak kodunuzu aşağıya yazın ve havalı bir ad verin! Lütfen kullandığınız dilin sürümünü de listeleyin. Tüm Scriptbotlar tomfoolery için incelenecektir, bu yüzden lütfen iyi yorum yapın ve kodunuzu gizlemeyin.

Birden fazla giriş gönderebilirsiniz, ancak lütfen bunları aynı girişin sürümlerini değil tamamen benzersiz girişler yapın. Örneğin, bir Zerg Rush botunu ve bir Gorilla Warfare botunu kodlamak isteyebilirsiniz. Bu iyi. Zerg Rush v1, Zerg Rush v2 vb. Göndermeyin.

7 Kasım'da tüm cevapları toplayacağım ve ilk incelemeyi geçen cevaplar bir turnuva dirseğine eklenecek. Şampiyon kabul edilen cevabı alır. İdeal braket aşağıda gösterilmiştir. Tam olarak 16 giriş olmayacağından, bazı köşeli parantezler sadece üç hatta iki bot olabilir. Desteği olabildiğince adil yapmaya çalışacağım. İlk gönderilen botlara gerekli her türlü kayırmacılık (örneğin bir hoşçakal haftasının gerekli olması halinde) verilecektir.

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

S & C

Eminim bazı detayları kaçırdım, bu yüzden soru sormaktan çekinmeyin!

Bir harita dosyasının her zaman # sembolle çevrili olduğuna güvenebilir miyiz? Değilse, bir botun haritadan çıkmaya çalışması durumunda ne olur? - BrainSteel

Evet, harita her zaman # ile sınırlandırılacak ve Scriptbot'unuz bu sınırların içinde başlayacaktır.

PUSH komutunda belirtilen yönde hiç bot yoksa, komut nasıl çalışır? - BrainSteel

GameMaster hiçbir şey yapmaz, sıfır EP harcanır ve Scriptbot tekrar çağrılır.

Kullanılmayan EP birikir mi? - feersum

Hayır. Her Scriptbot turu 10 EP ile başlatacaktır. Harcanmayan EP'ler boşa gidecek.

Sanırım anladım, ama sadece açıklığa kavuşturmak için: A ve B botları ile, A @ 10EP-> MOVE MAP_UPDATE B @ 10EP-> PUSH MAP_UPDATE A @ 9EP-> ATTACK MAP_UPDATE B @ 9EP-> SALDIRI ... veya A @ 10EP-> HAREKET A 9EP-> SALDIRI ... HARİTA_UPDATE B @ 10EP-> PUSH B @ 9EP-> SALDIRI ... HARİTA_UPDATE? Diğer bir deyişle, tüm eylemler tek bir denetleyici-bot sorgu döngüsü atomik midir? Eğer öyleyse, neden döngü? Neden tamamlanacak tüm eylemleri içeren tek bir dosya döndürmüyorsunuz? Aksi takdirde botların çoklu eylem dizilerini takip etmek için kendi durum dosyalarını yazmaları gerekecektir. Harita / istatistik dosyası yalnızca ilk işlemden önce geçerli olacaktır. - COTO

İkinci örneğiniz yakın, ama tam olarak doğru değil. Bir dönüş sırasında Scriptbot, EP'si harcanana kadar veya maksimum 11 kez tekrar tekrar çağrılır. Harita ve istatistik dosyaları her çağrıdan önce güncellenir. Döngü, bir botun geçersiz çıktı vermesi durumunda kullanışlıdır. GameMaster geçersiz çıktı ile başa çıkacak ve botu tekrar dahil edecek ve botu hata düzeltmesi için bir şans verecektir.

Test için GameMaster betiğini yayınlayacak mısınız? - IchBinKeinBaum

GameMaster betiği yayınlanmaz. Botunuzun davranışını test etmek için bir harita ve istatistik dosyası oluşturmanızı öneririm.

RobotA, robotB'yi bir tuzağa iterse, robotA'nın "hasar verilen" puanları, robotB'nin mevcut sağlığına eşit mi? - Mike Sweeney

Evet, bu iyi bir fikir. Bir bot, bir tuzağa ittiği herhangi bir botun sağlığına eşit hasar puanları verecektir.


Bir mapdosyanın daima #sembollerle çevrili olduğuna güvenebilir miyiz ? Değilse, bir botun haritadan çıkmaya çalışması durumunda ne olur?
BrainSteel

@BrainSteel Evet harita her zaman sınırlı #olacak ve Scriptbot'unuz bu sınırların içinde başlayacaktır.
Rip Leeb

3
Bir şeyi kaçırdığınızdan eminseniz, neden onu sanal alana göndermiyorsunuz ?
Martin Ender

2
@ MartinBüttner Bunu tamamen iyice düşündüm. Sadece arkadaşça davranmaya ve soruların hoş karşılandığını açıklamaya çalışıyordum.
Rip Leeb

1
RobotA robotB'yi bir tuzağa iterse, robotA'nın "hasar verilen" puanları, robotB'nin mevcut sağlığına eşit mi?
Mantık Şövalyesi

Yanıtlar:


1

Suikastçı (Java 1.7)

Mümkün olduğunca düşmanları öldürmeye çalışır, aksi takdirde bir alanı hareket ettirir. Bir düşmanın yolunu bulmak oldukça iyidir, ancak diğer botlardan gizlemek için hiçbir şey yapmaz.

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

Bunun hangi Java sürümü yazıldı?
Rip Leeb

@Nate 1.7 kullandım.
CommonGuy

3

Avoider v3

Basit fikirli bir bot. Diğer robotlardan ve tuzaklardan korkuyor. Saldırmayacak. İstatistikler dosyasını yok sayar ve hiç ileriyi düşünmez.

Bu esas olarak kuralların nasıl çalıştığını görmek için yapılan bir testtir ve diğer rakipler için aptal bir rakip olarak.

Düzenleme: Artık MOVE daha iyi olmadığında PASS olacak.

Edit2: Robotlar '123' yerine '1234' olabilir

Python Kodu:

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':En az 1234'ü aramak istiyorsunuz. Eğer bot 3 iseniz, rakip listesi 124 olur.
Rip Leeb

1
@Doğru teşekkürler. Botu değiştirdim. Bunu kurallarda açıkça belirtmek isteyebilirsiniz. Bunu yanlış anlayan tek kişi ben olmayabilirim.
Mantık Şövalyesi

3

BestOpportunityBot

Bu, istediğimden biraz daha uzun oldu ... ve dönüş kurallarını tamamen anladığımdan emin değilim, bu yüzden bunun nasıl olduğunu göreceğiz.

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

bunu hangi python sürümüne yazdınız?
Rip Leeb

Win32'de @Nate 3.4.1

Bunu @horns gönderdiğiniz için teşekkür ederiz. İzlemek gerçekten eğlenceliydi!
Rip Leeb

1

Yemek Bitirmeliyim

Python:

import sys
print 'PASS'

1
Buna bayıldım - ve neden cehennem import sys?
15'te tomsmeding

1
@tomsmeding Bazı şeyleri kopyalamak zorunda kaldım. Tabii ki yemeğimi bitirdiğimde bazı argümanları okumam gerektiğinde düşündüm.
Timtech
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.