Renklerin Resim Savaşı


33

En iyi giriş için @kuroineko'ya KONGRELER ve @BestOne'dan 200 ödül kazanma şansı (mükemmel bir sportmenlik!).

Muhalefet programlarından önce görüntünün mümkün olduğu kadarını renklendirmek için bir program yazın.

Kısaca Kurallar

  • Programınıza bir resim, renginiz ve tamsayı N verilecektir.
  • Her turda, diğer programlar tarafından piksel güncellemeleri gönderilir ve N güncellemeniz istenir.
  • Renginizin pikselinin yanındaki beyaz pikseli güncelleyebilirsiniz.
  • En fazla piksel ekleyen program kazanır.

Ayrıntılı kurallar

Programınıza bir PNG resim dosyası adı, ev rengi ve bir N numarası verilecektir. N sayısı, programınızın her dönüşünde renklendirebileceği maksimum piksel sayısıdır.

Örnek: MyProg arena.png (255,0,0) 30

Giriş görüntüsü, kenarları 20 ila 1000 piksel arasında olan bir dikdörtgen olacaktır. Siyah, beyaz ve renkli piksellerden oluşacaktır. Programınız, her yeni pikselin kendi renginizdeki dört komşu pikselinden en az birine sahip olması koşuluyla, kendi renginizle renklendirmek için bir beyaz piksel dizisi seçebilir . Görüntü başlangıçta renginizin en az bir pikseline sahip olacaktır. Ayrıca hiçbir programın atanmadığı renk piksellerini de içerebilir. Alfa kanalı kullanılmıyor.

Amacınız rakiplerinizi engellemek ve renginizi olabildiğince fazla piksel içine yazmak.

Programınız her turda STDIN'de 1 veya daha fazla mesaj satırı kabul edecek ve STDOUT'da piksel koordinatlarından oluşan bir satır yazacaktır. STDOUT'u arabelleksiz olarak atamayı ya da her turda STDOUT arabelleğini temizlemeyi unutmayın.

Her turda çağrılan oyuncuların sırası rastgele atanır. Bu, bir rakibin (veya programınızın) arka arkaya 2 dönüş yapabileceği anlamına gelir.

Programınıza, colour (N,N,N) chose X,Y X,Y ... X,Yoynatıcı programları tarafından doldurulan pikselleri açıklayan bilgi mesajları gönderilecektir . Bir oyuncu hamle yapmazsa veya geçerli hamle yapmazsa, o oyuncunun hamleleri hakkında bir mesaj gönderilmez. Programınıza ayrıca kabul ettiğiniz kendi hareketleriniz hakkında bir mesaj gönderilecektir (en az bir geçerli hareket belirtmişseniz). 0,0 piksel, görüntünün sol üst köşesindedir.

Aldığınızda pick pixels, programınız X,Y X,Y ... X,YN piksele kadar çıkacaktır (yalnızca '\ n' içeren boş bir dizeye izin verilir). Piksellerin çizim sırasına göre olması gerekir. Bir piksel geçersizse, yoksayılır ve oyunculara raporda yer almaz. Programınızın başlattıktan sonra başlatması için 2 saniye var, ancak her dönüşü bir cevapla yanıtlamak için yalnızca 0.1 saniye, yoksa bu dönüşü kaçıracak. 0,1 saniye sonra gönderilen piksel güncellemesi bir hata kaydeder. 5 hatadan sonra programınız askıya alınır ve güncelleme veya pick pixelsistek gönderilmez .

Hakim program askıya alınmamış her oyuncu programından boş veya geçersiz bir piksel seçeneği aldığında, resim tamamlanmış sayılır ve programlara "çıkış" mesajı gönderilir. "Çıkış" aldıktan sonra programlar sonlandırılmalıdır.

puanlama

Hakim, resim tamamlandıktan sonra puan kazanacak. Puanınız, yüzde olarak ifade edilen, o turdaki ortalama piksel yakalama sayısına bölünerek güncellenen piksel sayınız olacaktır.

Görüntüye oyuncu tarafından eklenen piksel sayısı A'dır. Tüm P oyuncuları tarafından eklenen toplam piksel sayısı T'dir. avg = T/P score = 100*A/avg

Puanları gönderme

Bir referans rakibi "The Blob" verilir. Her cevap için botunuza isim, dil ve puanınızı (arenada ortalama 1 ila 4 arası) atıfta bulunacak şekilde isimlendirin. Savaşlarınızdan birinin resmi veya animasyonu da iyi olabilir. Kazanan, referans botuna karşı en yüksek puana sahip programdır.

Blob'u yenmek kolay olmazsa, daha güçlü bir referans rakibi ile ikinci bir raunt ekleyebilirim.

Ayrıca 4 veya daha fazla oynatıcı programı denemek isteyebilirsiniz. Botunuzu cevap olarak yayınlanan diğer botlara karşı da test edebilirsiniz.

Yargıç

Hakim program, ortak Python Görüntüleme Kütüphanesi'ni (PIL) gerektirir ve işletim sistemi paket yöneticinizden Linux'ta kurulumu kolay olmalıdır. PIL'in Windows 7'de 64 bit Python ile çalışmadığını bildiğim için, bu zorluğa başlamadan önce lütfen PIL'in sizin için çalışıp çalışmadığını kontrol edin (güncellenmiş 2015-01-29).

#!/usr/bin/env python
# Judge Program for Image Battle challenge on PPCG.
# Runs on Python 2.7 on Ubuntu Linux. May need edits for other platforms.
# V1.0 First release.
# V1.1 Added Java support
# V1.2 Added Java inner class support
# usage: judge cfg.py
import sys, re, random, os, shutil, subprocess, datetime, time, signal
from PIL import Image

ORTH = ((-1,0), (1,0), (0,-1), (0,1))
def place(loc, colour):
    # if valid, place colour at loc and return True, else False
    if pix[loc] == (255,255,255):
        plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
        if any(pix[p]==colour for p in plist if 0<=p[0]<W and 0<=p[1]<H):
            pix[loc] = colour
            return True
    return False

def updateimage(image, msg, bot):
    if not re.match(r'(\s*\d+,\d+)*\s*', msg):
        return []
    plist = [tuple(int(v) for v in pr.split(',')) for pr in msg.split()]
    plist = plist[:PIXELBATCH]
    return [p for p in plist if place(p, bot.colour)]

class Bot:
    botlist = []
    def __init__(self, name, interpreter=None, colour=None):
        self.prog = name
        self.botlist.append(self)
        callarg = re.sub(r'\.class$', '', name)  # Java fix
        self.call = [interpreter, callarg] if interpreter else [callarg]
        self.colour = colour
        self.colstr = str(colour).replace(' ', '')
        self.faults = 0
        self.env = 'env%u' % self.botlist.index(self)
        try: os.mkdir(self.env)
        except: pass
        if name.endswith('.class'): # Java inner class fix
            rootname = re.sub(r'\.class$', '', name)
            for fn in os.listdir('.'):
                if fn.startswith(rootname) and fn.endswith('.class'):
                    shutil.copy(fn, self.env)
        else:
            shutil.copy(self.prog, self.env)
        shutil.copy(imagename, self.env)
        os.chdir(self.env)
        args = self.call + [imagename, self.colstr, `PIXELBATCH`]
        self.proc = subprocess.Popen(args, stdin=subprocess.PIPE, 
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        os.chdir('..')
    def send(self, msg):
        if self.faults < FAULTLIMIT:
            self.proc.stdin.write(msg + '\n')
            self.proc.stdin.flush()
    def read(self, timelimit):
        if self.faults < FAULTLIMIT:
            start = time.time()
            inline = self.proc.stdout.readline()
            if time.time() - start > timelimit:
                self.faults += 1
                inline = ''
            return inline.strip()
    def exit(self):
        self.send('exit')

from cfg import *
for i, (prog, interp) in enumerate(botspec):
    Bot(prog, interp, colourspec[i])

image = Image.open(imagename)
pix = image.load()
W,H = image.size

time.sleep(INITTIME)
total = 0
for turn in range(1, MAXTURNS+1):
    random.shuffle(Bot.botlist)
    nullbots = 0
    for bot in Bot.botlist:
        bot.send('pick pixels')
        inmsg = bot.read(TIMELIMIT)
        newpixels = updateimage(image, inmsg, bot)
        total += len(newpixels)
        if newpixels:
            pixtext = ' '.join('%u,%u'%p for p in newpixels)
            msg = 'colour %s chose %s' % (bot.colstr, pixtext)
            for msgbot in Bot.botlist:
                msgbot.send(msg)
        else:
            nullbots += 1
    if nullbots == len(Bot.botlist):
        break
    if turn % 100 == 0: print 'Turn %s done %s pixels' % (turn, total)
for msgbot in Bot.botlist:
    msgbot.exit()

counts = dict((c,f) for f,c in image.getcolors(W*H))
avg = 1.0 * sum(counts.values()) / len(Bot.botlist)
for bot in Bot.botlist:
    score = 100 * counts[bot.colour] / avg
    print 'Bot %s with colour %s scored %s' % (bot.prog, bot.colour, score)
image.save(BATTLE+'.png')

Örnek Yapılandırma - cfg.py

BATTLE = 'Green Blob vs Red Blob'
MAXTURNS = 20000
PIXELBATCH = 10
INITTIME = 2.0
TIMELIMIT = 0.1
FAULTLIMIT = 5

imagename = 'arena1.png'

colourspec = (0,255,0), (255,0,0)

botspec = [
    ('blob.py', 'python'),
    ('blob.py', 'python'),
    ]

The Blob - referans rakibi

# Blob v1.0 - A reference opponent for the Image Battle challenge on PPCG.
import sys, os
from PIL import Image

image = Image.open(sys.argv[1])
pix = image.load()
W,H = image.size
mycolour = eval(sys.argv[2])
pixbatch = int(sys.argv[3])

ORTH = ((-1,0), (1,0), (0,-1), (0,1))
def canchoose(loc, colour):
    if pix[loc] == (255,255,255):
        plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
        if any(pix[p]==colour for p in plist if 0<=p[0]<W and 0<=p[1]<H):
            return True
    return False

def near(loc):
    plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
    pboard = [p for p in plist if 0<=p[0]<W and 0<=p[1]<H]
    return [p for p in pboard if pix[p] == (255,255,255)]

def updateimage(image, msg):
    ctext, colourtext, chose, points = msg.split(None, 3)
    colour = eval(colourtext)
    plist = [tuple(int(v) for v in pr.split(',')) for pr in points.split()]
    for p in plist:
        pix[p] = colour
        skin.discard(p)
        if colour == mycolour:
            for np in near(p):
                skin.add(np)

board = [(x,y) for x in range(W) for y in range(H)]
skin = set(p for p in board if canchoose(p, mycolour))

while 1:
    msg = sys.stdin.readline()
    if msg.startswith('colour'):
        updateimage(image, msg.strip())
    if msg.startswith('pick'):
        plen = min(pixbatch, len(skin))
        moves = [skin.pop() for i in range(plen)]
        movetext = ' '.join('%u,%u'%p for p in moves)
        sys.stdout.write(movetext + '\n')
        sys.stdout.flush()
    if msg.startswith('exit'):
        break

image.save('blob.png')

Arena 1

arena1.png

Arena 2

arena2.png

Arena 3

arena3.png

Arena 4

arena4.png

Bir Örnek Savaş - Blob vs Blob

Bu savaşın tahmin edilebilir bir sonucu vardı:

Bot blob.py with colour (255, 0, 0) scored 89.2883333333
Bot blob.py with colour (0, 255, 0) scored 89.365

Örnek Savaş


Bunun bir [tepe kralı] olmaması gerektiğinden emin misin?
Justin,

Bunun hakkında düşündüm. Robotlar birbirleriyle doğrudan savaşmıyor. Referans botuyla savaşıyorlar. Bu KOTH'u dışladı mı?
Mantık Şövalyesi

Evet, bu olduğu gibi, bir KOTH değil, referans botuyla birbirinden ziyade savaşmak istediğinden emin olup olmadığını soruyordum.
Justin,

1
@ TheBestOne, Java desteği eklendi. Yine de Java programında denenmedi. İşe yaramazsa bana haber ver.
Mantık Şövalyesi

1
10 piksel sırayla yerleştirilir, böylece sonraki pikseller önceki piksel yerleşimlerine dayanabilir. İstediğiniz gibi birbirinize dayanabilirler.
Mantık Şövalye

Yanıtlar:


17

ColorFighter - C ++ - kahvaltıda birkaç kırlangıç ​​yiyor

DÜZENLE

  • kodu temizle
  • basit ama etkili bir optimizasyon ekledi
  • bazı GIF animasyonları ekledi

Tanrı yılanlardan nefret ederim (sadece örümceklermiş gibi davranın, Indy)

Aslında Python'u seviyorum. Keşke daha az tembel bir çocuk olsaydım ve onu düzgün öğrenmeye başladım, hepsi bu.

Bütün bunlar söyleniyor, yargıcın çalışmasını sağlamak için bu yılanın 64 bit versiyonu ile mücadele etmek zorunda kaldım. PIL'in Win7 altındaki 64 bit Python sürümüyle çalışması için bu zorluğa adamaya hazır olduğumdan daha fazla sabır gerekiyor, bu yüzden sonunda (acı bir şekilde) Win32 sürümüne geçtim.

Ayrıca, bir bot yanıt veremeyecek kadar yavaş olduğunda, Hakim fena çarpma eğilimindedir.
Python meraklısı olmadığım için tamir etmedim, ama stdin zaman aşımından sonra boş bir cevap okumak zorundaydı.

Küçük bir gelişme, stderr çıktısını her bot için bir dosyaya koymak olacaktır. Bu, ölüm sonrası hata ayıklama için izlemeyi kolaylaştırır.

Bu küçük problemler dışında, Hakimi kullanması çok basit ve keyifli buldum.
Başka bir yaratıcı ve eğlenceli mücadele için Kudos.

Kod

#define _CRT_SECURE_NO_WARNINGS // prevents Microsoft from croaking about the safety of scanf. Since every rabid Russian hacker and his dog are welcome to try and overflow my buffers, I could not care less.
#include "lodepng.h"
#include <vector>
#include <deque>
#include <iostream>
#include <sstream>
#include <cassert>   // paranoid android
#include <cstdint>   // fixed size types
#include <algorithm> // min max

using namespace std;

// ============================================================================
// The less painful way I found to teach C++ how to handle png images
// ============================================================================
typedef unsigned tRGB;
#define RGB(r,g,b) (((r) << 16) | ((g) << 8) | (b))
class tRawImage {
public:
    unsigned w, h;

    tRawImage(unsigned w=0, unsigned h=0) : w(w), h(h), data(w*h * 4, 0) {}
    void read(const char* filename) { unsigned res = lodepng::decode(data, w, h, filename); assert(!res);  }
    void write(const char * filename)
    {
        std::vector<unsigned char> png;
        unsigned res = lodepng::encode(png, data, w, h, LCT_RGBA); assert(!res);
        lodepng::save_file(png, filename);
    }
    tRGB get_pixel(int x, int y) const
    {
        size_t base = raw_index(x,y);
        return RGB(data[base], data[base + 1], data[base + 2]);
    }
    void set_pixel(int x, int y, tRGB color)
    {
        size_t base = raw_index(x, y);
        data[base+0] = (color >> 16) & 0xFF;
        data[base+1] = (color >>  8) & 0xFF;
        data[base+2] = (color >> 0) & 0xFF;
        data[base+3] = 0xFF; // alpha
    }
private:
    vector<unsigned char> data;
    void bound_check(unsigned x, unsigned y) const { assert(x < w && y < h); }
    size_t raw_index(unsigned x, unsigned y) const { bound_check(x, y); return 4 * (y * w + x); }
};

// ============================================================================
// coordinates
// ============================================================================
typedef int16_t tCoord;

struct tPoint {
    tCoord x, y;
    tPoint operator+  (const tPoint & p) const { return { x + p.x, y + p.y }; }
};

typedef deque<tPoint> tPointList;

// ============================================================================
// command line and input parsing
// (in a nice airtight bag to contain the stench of C++ string handling)
// ============================================================================
enum tCommand {
    c_quit,
    c_update,
    c_play,
};

class tParser {
public:
    tRGB color;
    tPointList points;

    tRGB read_color(const char * s)
    {
        int r, g, b;
        sscanf(s, "(%d,%d,%d)", &r, &g, &b);
        return RGB(r, g, b);
    }

    tCommand command(void)
    {
        string line;
        getline(cin, line);

        string cmd = get_token(line);
        points.clear();

        if (cmd == "exit") return c_quit;
        if (cmd == "pick") return c_play;

        // even more convoluted and ugly than the LEFT$s and RIGHT$s of Apple ][ basic...
        if (cmd != "colour")
        {
            cerr << "unknown command '" << cmd << "'\n";
            exit(0);
        }
        assert(cmd == "colour");
        color = read_color(get_token(line).c_str());
        get_token(line); // skip "chose"
        while (line != "")
        {
            string coords = get_token(line);
            int x = atoi(get_token(coords, ',').c_str());
            int y = atoi(coords.c_str());
            points.push_back({ x, y });
        }
        return c_update;
    }

private:
    // even more verbose and inefficient than setting up an ADA rendezvous...
    string get_token(string& s, char delimiter = ' ')
    {
        size_t pos = 0;
        string token;
        if ((pos = s.find(delimiter)) != string::npos)
        {
            token = s.substr(0, pos);
            s.erase(0, pos + 1);
            return token;
        }
        token = s; s.clear(); return token;
    }
};

// ============================================================================
// pathing
// ============================================================================
class tPather {

public:
    tPather(tRawImage image, tRGB own_color)
        : arena(image)
        , w(image.w)
        , h(image.h)
        , own_color(own_color)
        , enemy_threat(false)
    {
        // extract colored pixels and own color areas
        tPointList own_pixels;
        color_plane[neutral].resize(w*h, false);
        color_plane[enemies].resize(w*h, false);
        for (size_t x = 0; x != w; x++)
        for (size_t y = 0; y != h; y++)
        {
            tRGB color = image.get_pixel(x, y);
            if (color == col_white) continue;
            plane_set(neutral, x, y);
            if (color == own_color) own_pixels.push_back({ x, y }); // fill the frontier with all points of our color
        }

        // compute initial frontier
        for (tPoint pixel : own_pixels)
        for (tPoint n : neighbour)
        {
            tPoint pos = pixel + n;
            if (!in_picture(pos)) continue;
            if (image.get_pixel(pos.x, pos.y) == col_white)
            {
                frontier.push_back(pixel);
                break;
            }
        }
    }

    tPointList search(size_t pixels_required)
    {
        // flood fill the arena, starting from our current frontier
        tPointList result;
        tPlane closed;
        static tCandidate pool[max_size*max_size]; // fastest possible garbage collection
        size_t alloc;
        static tCandidate* border[max_size*max_size]; // a FIFO that beats a deque anytime
        size_t head, tail;
        static vector<tDistance>distance(w*h); // distance map to be flooded
        size_t filling_pixels = 0; // end of game  optimization

    get_more_results:

        // ready the distance map for filling
        distance.assign(w*h, distance_max);

        // seed our flood fill with the frontier
        alloc = head = tail = 0;
        for (tPoint pos : frontier)
        {
            border[tail++] = new (&pool[alloc++]) tCandidate (pos);
        }

        // set already explored points
        closed = color_plane[neutral]; // that's one huge copy

        // add current result
        for (tPoint pos : result)
        {
            border[tail++] = new (&pool[alloc++]) tCandidate(pos);
            closed[raw_index(pos)] = true;
        }

        // let's floooooood!!!!
        while (tail > head && pixels_required > filling_pixels)
        {
            tCandidate& candidate = *border[head++];
            tDistance  dist = candidate.distance;
            distance[raw_index(candidate.pos)] = dist++;
            for (tPoint n : neighbour)
            {
                tPoint pos = candidate.pos + n;
                if (!in_picture (pos)) continue;
                size_t index = raw_index(pos);
                if (closed[index]) continue;
                if (color_plane[enemies][index])
                {
                    if (dist == (distance_initial + 1)) continue; // already near an enemy pixel

                    // reached the nearest enemy pixel
                    static tPoint trail[max_size * max_size / 2]; // dimensioned as a 1 pixel wide spiral across the whole map
                    size_t trail_size = 0;

                    // walk back toward the frontier
                    tPoint walker = candidate.pos;
                    tDistance cur_d = dist;
                    while (cur_d > distance_initial)
                    {
                        trail[trail_size++] = walker;
                        tPoint next_n;
                        for (tPoint n : neighbour)
                        {
                            tPoint next = walker + n;
                            if (!in_picture(next)) continue;
                            tDistance prev_d = distance[raw_index(next)];
                            if (prev_d < cur_d)
                            {
                                cur_d = prev_d;
                                next_n = n;
                            }
                        }
                        walker = walker + next_n;
                    }

                    // collect our precious new pixels
                    if (trail_size > 0)
                    {
                        while (trail_size > 0)
                        {
                            if (pixels_required-- == 0) return result;       // ;!; <-- BRUTAL EXIT
                            tPoint pos = trail[--trail_size];
                            result.push_back (pos);
                        }
                        goto get_more_results; // I could have done a loop, but I did not bother to. Booooh!!!
                    }
                    continue;
                }

                // on to the next neighbour
                closed[index] = true;
                border[tail++] = new (&pool[alloc++]) tCandidate(pos, dist);
                if (!enemy_threat) filling_pixels++;
            }
        }

        // if all enemies have been surrounded, top up result with the first points of our flood fill
        if (enemy_threat) enemy_threat = pixels_required == 0;
        tPathIndex i = frontier.size() + result.size();
        while (pixels_required--) result.push_back(pool[i++].pos);
        return result;
    }

    // tidy up our map and frontier while other bots are thinking
    void validate(tPointList moves)
    {
        // report new points
        for (tPoint pos : moves)
        {
            frontier.push_back(pos);
            color_plane[neutral][raw_index(pos)] = true;
        }

        // remove surrounded points from frontier
        for (auto it = frontier.begin(); it != frontier.end();) 
        {
            bool in_frontier = false;
            for (tPoint n : neighbour)
            {
                tPoint pos = *it + n;
                if (!in_picture(pos)) continue;
                if (!(color_plane[neutral][raw_index(pos)] || color_plane[enemies][raw_index(pos)]))
                {
                    in_frontier = true;
                    break;
                }
            }
            if (!in_frontier) it = frontier.erase(it); else ++it; // the magic way of deleting an element without wrecking your iterator
        }       
    }

    // handle enemy move notifications
    void update(tRGB color, tPointList points)
    {
        assert(color != own_color);

        // plot enemy moves
        enemy_threat = true;
        for (tPoint p : points) plane_set(enemies, p);

        // important optimization here :
        /*
         * Stop 1 pixel away from the enemy to avoid wasting moves in dogfights.
         * Better let the enemy gain a few more pixels inside the surrounded region
         * and use our precious moves to get closer to the next threat.
         */
        for (tPoint p : points) for (tPoint n : neighbour) plane_set(enemies, p+n);

        // if a new enemy is detected, gather its initial pixels
        for (tRGB enemy : known_enemies) if (color == enemy) return;
        known_enemies.push_back(color);
        tPointList start_areas = scan_color(color);
        for (tPoint p : start_areas) plane_set(enemies, p);
    }

private:
    typedef uint16_t tPathIndex;

    typedef uint16_t tDistance;
    static const tDistance distance_max     = 0xFFFF;
    static const tDistance distance_initial = 0;

    struct tCandidate {
        tPoint pos;
        tDistance distance;
        tCandidate(){} // must avoid doing anything in this constructor, or pathing will slow to a crawl
        tCandidate(tPoint pos, tDistance distance = distance_initial) : pos(pos), distance(distance) {}
    };

    // neighbourhood of a pixel
    static const tPoint neighbour[4];

    // dimensions
    tCoord w, h; 
    static const size_t max_size = 1000;

    // colors lookup
    const tRGB col_white = RGB(0xFF, 0xFF, 0xFF);
    const tRGB col_black = RGB(0x00, 0x00, 0x00);
    tRGB own_color;
    const tRawImage arena;
    tPointList scan_color(tRGB color)
    {
        tPointList res;
        for (size_t x = 0; x != w; x++)
        for (size_t y = 0; y != h; y++)
        {
            if (arena.get_pixel(x, y) == color) res.push_back({ x, y });
        }
        return res;
    }

    // color planes
    typedef vector<bool> tPlane;
    tPlane color_plane[2];
    const size_t neutral = 0;
    const size_t enemies = 1;
    bool plane_get(size_t player, tPoint p) { return plane_get(player, p.x, p.y); }
    bool plane_get(size_t player, size_t x, size_t y) { return in_picture(x, y) ? color_plane[player][raw_index(x, y)] : false; }
    void plane_set(size_t player, tPoint p) { plane_set(player, p.x, p.y); }
    void plane_set(size_t player, size_t x, size_t y) { if (in_picture(x, y)) color_plane[player][raw_index(x, y)] = true; }
    bool in_picture(tPoint p) { return in_picture(p.x, p.y); }
    bool in_picture(int x, int y) { return x >= 0 && x < w && y >= 0 && y < h; }
    size_t raw_index(tPoint p) { return raw_index(p.x, p.y); }
    size_t raw_index(size_t x, size_t y) { return y*w + x; }

    // frontier
    tPointList frontier;

    // register enemies when they show up
    vector<tRGB>known_enemies;

    // end of game optimization
    bool enemy_threat;
};

// small neighbourhood
const tPoint tPather::neighbour[4] = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };

// ============================================================================
// main class
// ============================================================================
class tGame {
public:
    tGame(tRawImage image, tRGB color, size_t num_pixels)
        : own_color(color)
        , response_len(num_pixels)
        , pather(image, color)
    {}

    void main_loop(void)
    {
        // grab an initial answer in case we're playing first
        tPointList moves = pather.search(response_len);
        for (;;)
        {
            ostringstream answer;
            size_t        num_points;
            tPointList    played;

            switch (parser.command())
            {
            case c_quit: 
                return;

            case c_play:
                // play as many pixels as possible
                if (moves.size() < response_len) moves = pather.search(response_len);
                num_points = min(moves.size(), response_len);
                for (size_t i = 0; i != num_points; i++)
                {
                    answer << moves[0].x << ',' << moves[0].y;
                    if (i != num_points - 1) answer << ' '; // STL had more important things to do these last 30 years than implement an implode/explode feature, but you can write your own custom version with exception safety and in-place construction. It's a bit of work, but thanks to C++ inherent genericity you will be able to extend it to giraffes and hippos with a very manageable amount of code refactoring. It's not anyone's language, your C++, eh. Just try to implode hippos in Python. Hah!
                    played.push_back(moves[0]);
                    moves.pop_front();
                }
                cout << answer.str() << '\n';

                // now that we managed to print a list of points to stdout, we just need to cleanup the mess
                pather.validate(played);
                break;

            case c_update:
                if (parser.color == own_color) continue; // hopefully we kept track of these already
                pather.update(parser.color, parser.points);
                moves = pather.search(response_len); // get cracking
                break;
            }
        }
    }

private:
    tParser parser;
    tRGB    own_color;
    size_t  response_len;
    tPather pather;
};

void main(int argc, char * argv[])
{
    // process command line
    tRawImage raw_image; raw_image.read (argv[1]);
    tRGB my_color = tParser().read_color(argv[2]);
    int num_pixels               = atoi (argv[3]);

    // init and run
    tGame game (raw_image, my_color, num_pixels);
    game.main_loop();
}

Yürütülebilir dosyayı oluşturma

Png görüntüleri okumak için LODEpng.cpp ve LODEpng.h kullandım .
En basit yöntem hakkında, bu gerizekalı C ++ diline yarım düzine kitaplık oluşturmak zorunda kalmadan bir resmin nasıl okunacağını öğrettim.
Sadece ana ile birlikte LODEpng.cpp dosyasını derleyin ve bağlayın.

Ben MSVC2013 ile derlenmiş, ama sadece bir kaç STL temel kaplar (deque ve vektörler) kullanılan bu yana, bu olabilir (eğer şanslıysanız) gcc ile çalışır.
Öyle değil, ben belki bir MinGW yapı deneyin, ama açıkçası ben C ++ taşınabilirlik sorunları yoruldum.

Günlerimde oldukça fazla taşınabilir C / C ++ yaptım (8 - 32 bit işlemcilerin yanı sıra SunOS, Windows 3.11'den Vista'ya ve Linux'un bebeklikten Ubuntu cooing zebra ya da her neyse, egzotik derleyicilerde. Taşınabilirliğin ne anlama geldiğine dair oldukça iyi bir fikrim var), ancak o zamanlar, GNU ve STL canavarının şifreli ve şişirilmiş özelliklerinin Microsoft yorumları arasındaki sayısız tutarsızlığı ezberlemesine gerek yoktu.

Sonuçlar Swallower

arena1 Arena2 arena3 arena4

Nasıl çalışır

Çekirdek, bu basit bir kaba kuvvetli taşkın doldurma yamasıdır.

Oyuncunun renginin sınırı (yani en az bir beyaz komşusu olan pikseller) klasik mesafe taşkın algoritmasını gerçekleştirmek için bir tohum olarak kullanılır.

Bir nokta bir düşman renginin gerçekliğine ulaştığında, en yakın düşman noktasına doğru hareket eden bir dizi piksel üretmek için geriye doğru bir yol hesaplanır.

İşlem, istenen uzunlukta bir cevap için yeterli puan toplanana kadar tekrar edilir.

Bu tekrarlama, özellikle düşmanın yakınında savaşırken, çok pahalı.
Sınırdan bir düşman piksele giden bir piksel dizisi bulunduğunda (ve cevabı tamamlamak için daha fazla noktaya ihtiyacımız var), taşkın dolgusu baştan başlayarak, yeni sınıra eklenen yeni yolla yeniden yapılır. Bu, 10 piksel yanıt alabilmek için 5 taşkın dolgusu veya daha fazlasını yapmanız gerekebileceği anlamına gelir.

Daha fazla düşman pikseline ulaşılamıyorsa, sınır piksellerinin keyfi komşuları seçilir.
Algoritma, verimsiz bir taşkın dolgusuna gelişti, ancak bu, yalnızca oyunun sonucuna karar verildikten sonra olur (yani, savaşılacak tarafsız bir bölge yoktur).
Hâlihazırda yarışma yapıldıktan sonra haritayı doldurmak için yaşları harcamaması için optimize ettim. Mevcut haliyle, yürütme süresi Hakimin kendisiyle karşılaştırıldığında ihmal edilebilir düzeydedir.

Düşman renkleri başlangıçta bilinmediğinden, ilk arena görüntüsü düşmanın ilk hareketini yaptığı zaman düşmanın başlangıç ​​alanlarını kopyalamak için depoda tutulur.
İlk önce kod oynatılırsa, birkaç rastgele pikseli dolduracak şekilde taşmaya başlar.

Bu, algoritmayı keyfi sayıda rakiple ve hatta zaman zaman rastgele bir noktaya gelen yeni rakiplerle veya başlangıç ​​alanı olmadan ortaya çıkan renkleri (bunun kesinlikle pratik bir kullanımı olmasa da) mücadele edebilecek duruma getirir.

Renk başına renk temelinde düşmanın ele alınması, botun iki örneğinin birlikte çalışmasını sağlar (gizli tanıma işaretini geçmek için piksel koordinatlarını kullanarak).
Kulağa eğlenceli geliyor, muhtemelen deneyeceğim :).

Hesaplamaya yönelik ağır veri, yeni veriler mevcut olur olmaz (bir hareket bildiriminin ardından) yapılır ve bazı optimizasyonlar (sınır güncellemesi) bir cevap verildikten hemen sonra yapılır (diğer botlar dönüşlerde mümkün olduğunca fazla hesaplama yapmak için). ).

Burada yine, 1'den fazla düşman varsa (yeni veriler mevcutsa bir hesaplamayı bırakmak gibi), daha ince şeyler yapmanın yolları olabilir, fakat herhangi bir oranda, algoritma olduğu sürece, çoklu görevlerin gerekli olduğunu göremiyorum. tam yükte çalışabilir.

Performans sorunları

Tüm bunlar hızlı veri erişimi olmadan (ve tüm Appolo programından daha fazla bilgi işlem gücü, yani birkaç tweet göndermek için daha çok iş yaparken kullanılan ortalama PC'niz olmadan) çalışamaz.

Hız büyük ölçüde derleyiciye bağlıdır. Genelde GNU, Microsoft’u% 30 marjla yener (bu, diğer 3 kodlamayla ilgili kodda gördüğüm sihirli sayıdır), ancak bu kilometre elbette değişebilir.

Şu anki durumundaki kod, arena 4'teki terleri zorlukla kırar. Windows perfmeter yaklaşık% 4 ila 7 CPU kullanımı bildirir, bu nedenle 100ms'lik yanıt süresi içinde 1000x1000 haritayla başa çıkabilmelidir.

Yaklaşık her bir algoritma algoritmasının merkezinde bir FIFO bulunur (bu durumda olmasa da büyük olasılıkla proritized), ki bu da hızlı eleman tahsisi gerektirir.

OP, arena boyutuna mecbur bir şekilde sınır koyduğundan, bazı matematikler yaptım ve maks. Boyutta (yani 1.000.000 piksel) boyutlandırılmış sabit veri yapılarının ortalama PC'nizin kahvaltıda yediği birkaç düzine megabayttan fazla tüketmeyeceğini gördüm.
Nitekim Win7 altında ve MSVC 2013 ile derlendiğinde, kod 4 arenada yaklaşık 14Mb tüketirken, Swallower'in iki ipliği 20Mb'den fazla kullanıyor.

Daha kolay prototipleme için STL kapları ile başladım, ancak STL kodu daha az okunabilir hale getirdi, çünkü gizliliği gizlemek için her bir veri parçasını kapsüllemek için bir sınıf oluşturma arzusu yoktu (bu benim kendi engellerimden kaynaklanıyordu. STL ile başa çıkma okuyucunun takdirine bırakılmıştır).
Ne olursa olsun, sonuç o kadar acımasızca yavaştı ki, ilk başta yanlışlıkla bir hata ayıklama sürümü oluşturduğumu sanıyordum.

Bunun kısmen STL'nin inanılmaz derecede zayıf Microsoft uygulamasından kaynaklandığını düşünüyorum (örneğin, vektörler ve bitler, [[]] operatörü üzerindeki spesifik kontrolleri veya diğer şifreleme işlemlerini, spesifikasyonun doğrudan ihlaliyle yapıyor) ve kısmen STL tasarımına bağlı. kendisi.

Performanslar orada olsaydı korkunç sözdizimi ve taşınabilirlik (yani Microsoft vs GNU) sorunları ile başa çıkabilirdi, ama bu kesinlikle böyle değil.

Örneğin deque, doğası gereği yavaştır; çünkü, daha az umursayamayacağım, süper akıllı yeniden boyutlandırma işlemini yapmayı beklerken etrafta pek çok defter tutma verisi vardır.
Tabii ki özel bir tahsisatçı ve balina avcısı için diğer özel şablon bitleri uygulayabilirdim, ancak özel bir tahsisatçı tek başına birkaç yüz satırlık bir kod satırına ve test etmek için bir günün daha iyi bir kısmına mal olur, ne kadarını uygulamak zorunda olduğu bir düzine el yapımı eşdeğer yapı sıfır kod satırları hakkındadır (daha tehlikeli olsa da, fakat yine de ne yaptığımı bilmeseydim - ya da bildiğimi düşünürsem algoritma işe yaramazdı).

Sonunda STL konteynerlerini kodun kritik olmayan kısımlarında tuttum ve iki acımasız tahsisatçıyı ve FIFO'yu 1970 circa iki dizi ve üç imzasız şortla oluşturdum.

Yutkunmayı yutmak

Yazarının onayladığı gibi, Kırlangıççı'nın düzensiz kalıpları, düşman hamlelerinin bildirimleri ve yamalı iplikten gelen güncellemeler arasındaki gecikmeden kaynaklanır.
Sistem perfmetresi her zaman% 100 CPU tüketen yamalı ipliği açıkça gösterir ve pürüzlü desenler kavga odağı yeni bir alana geçtiğinde ortaya çıkma eğilimindedir. Bu aynı zamanda animasyonlarda oldukça belirgindir.

Basit ama etkili bir optimizasyon

Swallower ve benim dövüşçüm arasındaki destansı köpek dövüşlerine baktıktan sonra, Go oyunundan eski bir deyişi hatırladım: yakın savun, ama uzaktan saldırı.

Bunda bilgelik var. Rakibinize çok fazla bağlı kalmaya çalışırsanız, olası her yolu engellemeye çalışarak değerli hamleleri boşa harcarsınız. Aksine, yalnızca bir piksel uzakta kalırsanız, çok az kazanç sağlayacak küçük boşlukları doldurmaktan kaçınacak ve hamlelerinizi daha önemli tehditlere karşı koymak için kullanacaksınız.

Bu fikri uygulamak için, düşmanın hareketlerini genişlettim (her hareketin 4 komşusunu bir düşman pikseli olarak işaretledim).
Bu, dövüş algoritmasının düşmanın sınırından bir piksel uzakta durmasını sağlar ve dövüşçümün bir savaşçıda çok fazla köpek dövüşüne yakalanmadan çalışmasını sağlar.

Gelişimi görebilirsiniz
(tüm çalışmalar başarılı olmasa da, daha yumuşak ana hatları fark edebilirsiniz):

önce sonra


1
Vay. Swallower'ı yenecek bir şey olmadığını sanıyordum. Harika bir açıklama ile mükemmel bir çözüm. Eski güzel günlerden K&R C'yi hatırlıyorum, ancak C karanlık tarafa geçti. Python'u seveceğinizi düşünüyorum .
Mantık Şövalye

Zorlu bir mücadeleyle başa çıkmak gerçek bir zevkti ... zorlu ve eğlenceli. Bu, bu küçük LODEpng taşını tam ölçekli olarak test etmeme izin verdi ve sonuçlar o kadar umut verici ki, png racer'ı tekrar ziyaret edebilirim, yine de bu rezil sonrası artan C ile olan aşk / nefret ilişkimi tekrar test ettim.

1
Kırlangıççı, zaman içinde kalması için zaman zaman biraz değişken. Kısmen çok parçacıklı bunun için var. Aferin!! Sanırım ikramiyemi ikiye katlayacağım ...
TheNumberOne

1
Yastık 64-bit indirmeleri var. Sadece PIL gibi kullanılabilir.
TheNumberOne

@BestOne Öyle sanıyordum. Acımasız ressamım, kırıcınızın modası geçmiş verileri yakaladığı bu anlardan faydalanır :). PIL'ye gelince, World Wide Web'deki tüm Amd64 PIL ve Pillow sürümlerini indirdim, ancak muhtemelen bir bootleg ve / veya modası geçmiş sürüm olan çekirdek 63.5 bit Python ile çalışmazlar. Her neyse, Win32 bağlantı noktası da iyi çalışıyor ve bir gün daha hızlı bir şeye ihtiyacım olursa PyPy'ye aynı şekilde geçmek zorunda kalacağım.

21

Derinlik ilk Blob - Blob

Dil = Python (3.2)

Puan = 111.475388276 153.34210035

Güncelleme: Şimdi , görüntünün büyük bölümlerini düşmanın kesilmesinde başlangıçta kaplanan alanı büyük ölçüde iyileştiren bir çeşit ızgara deseni üretme yöntemini Setelde etmek için özel bir sınıf kullanmak pop(). Not: Bunun için rastgele bir ızgara boyutu örneğinden arena3'e (güncelleme öncesinde en kötü puanı alana) en iyi sonuçları veren gibiydi 12 x 12 ızgara kullanıyorum, ancak daha uygun bir olasılıktır. Verilen arenaların seçimi için ızgara büyüklüğü mevcuttur.

Referans botunda basit bir değişiklik yapmaya gittim, mümkün olduğu kadar az sayıda kendi renginde nokta ile sınırlanmış uygun noktaları seçmeyi tercih ettim. Bir gelişme, mümkün olduğunca çok sayıda düşman renginde nokta ile sınırlandırılmış uygun noktaları seçmeyi kolaylaştırmak için de olabilir.

dfblob.py:

import sys, os
from PIL import Image

class RoomyIntPairHashSet:
    def __init__(self, firstMax, secondMax):
        self.m1 = firstMax
        self.m2 = secondMax
        self.set = [set() for i in range((firstMax - 1) * (secondMax - 1) + 1)]
        self.len = 0

    def add(self, tup):
        subset = self.set[self.gettuphash(tup)]
        self.len -= len(subset)
        subset.add(tup)
        self.len += len(subset)

    def discard(self, tup):
        subset = self.set[self.gettuphash(tup)]
        self.len -= len(subset)
        subset.discard(tup)
        self.len += len(subset)

    def pop(self):
        for s in self.set:
            if len(s) > 0:
                self.len -= 1
                return s.pop()
        return self.set[0].pop()

    def gettuphash(self, tup):
        return (tup[0] % self.m1) * (tup[1] % self.m2)

    def __len__(self):
        return self.len

gridhashwidth = 12
gridhashheight = 12
image = Image.open(sys.argv[1])
pix = image.load()
W,H = image.size
mycolour = eval(sys.argv[2])
pixbatch = int(sys.argv[3])

ORTH = ((-1,0), (1,0), (0,-1), (0,1))
def canchoose(loc, virtualneighbors, colour, num_neighbors):
    if pix[loc] == (255,255,255):
        plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
        actual_num_neighbors = 0
        for p in plist:
            if 0<=p[0]<W and 0<=p[1]<H and pix[p]==colour or p in virtualneighbors:
                actual_num_neighbors += 1
        return num_neighbors == actual_num_neighbors
    return False

def near(loc, exclude):
    plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
    pboard = [p for p in plist if 0<=p[0]<W and 0<=p[1]<H]
    return [p for p in pboard if pix[p] == (255,255,255) and p not in exclude]

def updateimage(image, msg):
    ctext, colourtext, chose, points = msg.split(None, 3)
    colour = eval(colourtext)
    plist = [tuple(int(v) for v in pr.split(',')) for pr in points.split()]
    for p in plist:
        pix[p] = colour
        for i in range(len(skins)):
            skins[i].discard(p)
        if colour == mycolour:
            for np in near(p, []):
                for j in range(len(skins)):
                    skins[j].discard(np)
                    if canchoose(np, [], mycolour, j + 1):
                        skins[j].add(np)


board = [(x,y) for x in range(W) for y in range(H)]
skins = []
for i in range(1, 1 + len(ORTH)):
    skin = RoomyIntPairHashSet(gridhashwidth, gridhashheight)
    skins.append(skin)
    for p in board:
        if canchoose(p, [], mycolour, i):
            skin.add(p)

while 1:
    msg = sys.stdin.readline()
    print("got message "+ msg, file=sys.stderr)
    if msg.startswith('colour'):
        print("updating image", file=sys.stderr)
        updateimage(image, msg.strip())
        print("updated image", file=sys.stderr)
    if msg.startswith('pick'):
        moves = []
        print("picking moves", file=sys.stderr)
        virtualskins = [RoomyIntPairHashSet(gridhashwidth, gridhashheight) for i in range(len(skins))]
        for i in range(pixbatch):
            for j in range(len(skins)):
                if len(virtualskins[j]) > 0 or len(skins[j]) > 0:
                    move = None
                    if len(virtualskins[j]) > 0:
                        move = virtualskins[j].pop()
                    else:
                        move = skins[j].pop()
                    moves.append(move)
                    print("picking move (%u,%u) " % move, file=sys.stderr)
                    for p in near(move, moves):
                        for k in range(len(skins)):
                            virtualskins[k].discard(p)
                            if canchoose(p, moves, mycolour, k + 1):
                                virtualskins[k].add(p)
                    break
        movetext = ' '.join('%u,%u'%p for p in moves)
        print("picked %u moves" % (len(moves)), file=sys.stderr)
        sys.stdout.write(movetext + '\n')
        sys.stdout.flush()
    if msg.startswith('exit') or len(msg) < 1:
        break

image.save('dfblob.png')

Asıl hakem Python 3.2 ile çalışmak üzere hafifçe değiştirildi (ve botlara kaba bir kayıt işlevi ekleyerek + arenaların resmini kaydet, animasyon yapmak için periyodik olarak):

import sys, re, random, os, shutil, subprocess, datetime, time, signal, io
from PIL import Image

ORTH = ((-1,0), (1,0), (0,-1), (0,1))
def place(loc, colour):
    # if valid, place colour at loc and return True, else False
    if pix[loc] == (255,255,255):
        plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
        if any(pix[p]==colour for p in plist if 0<=p[0]<W and 0<=p[1]<H):
            pix[loc] = colour
            return True
    return False

def updateimage(image, msg, bot):
    if not re.match(r'(\s*\d+,\d+)*\s*', msg):
        return []
    plist = [tuple(int(v) for v in pr.split(',')) for pr in msg.split()]
    plist = plist[:PIXELBATCH]
    return [p for p in plist if place(p, bot.colour)]

class Bot:
    botlist = []
    def __init__(self, name, interpreter=None, colour=None):
        self.prog = name
        self.botlist.append(self)
        callarg = re.sub(r'\.class$', '', name)
        self.call = [interpreter, callarg] if interpreter else [callarg]
        self.colour = colour
        self.colstr = str(colour).replace(' ', '')
        self.faults = 0
        self.env = 'env%u' % self.botlist.index(self)
        try: os.mkdir(self.env)
        except: pass
        shutil.copy(self.prog, self.env)
        shutil.copy(imagename, self.env)
        os.chdir(self.env)
        args = self.call + [imagename, self.colstr, str(PIXELBATCH)]
        errorfile = 'err.log'
        with io.open(errorfile, 'wb') as errorlog:
            self.proc = subprocess.Popen(args, stdin=subprocess.PIPE, 
                stdout=subprocess.PIPE, stderr=errorlog)
        os.chdir('..')
    def send(self, msg):
        if self.faults < FAULTLIMIT:
            self.proc.stdin.write((msg+'\n').encode('utf-8'))
            self.proc.stdin.flush()
    def read(self, timelimit):
        if self.faults < FAULTLIMIT:
            start = time.time()
            inline = self.proc.stdout.readline().decode('utf-8')
            if time.time() - start > timelimit:
                self.faults += 1
                inline = ''
            return inline.strip()
    def exit(self):
        self.send('exit')

from cfg import *
for i, (prog, interp) in enumerate(botspec):
    Bot(prog, interp, colourspec[i])

image = Image.open(imagename)
pix = image.load()
W,H = image.size
os.mkdir('results')

time.sleep(INITTIME)
total = 0
for turn in range(1, MAXTURNS+1):
    random.shuffle(Bot.botlist)
    nullbots = 0
    for bot in Bot.botlist:
        bot.send('pick pixels')
        inmsg = bot.read(TIMELIMIT)
        newpixels = updateimage(image, inmsg, bot)
        total += len(newpixels)
        if newpixels:
            pixtext = ' '.join('%u,%u'%p for p in newpixels)
            msg = 'colour %s chose %s' % (bot.colstr, pixtext)
            for msgbot in Bot.botlist:
                msgbot.send(msg)
        else:
            nullbots += 1
    if nullbots == len(Bot.botlist):
        break
    if turn % 100 == 0:
        print('Turn %s done %s pixels' % (turn, total))
        image.save("results/"+BATTLE+str(turn//100).zfill(3)+'.png')
for msgbot in Bot.botlist:
    msgbot.exit()

counts = dict((c,f) for f,c in image.getcolors(W*H))
avg = 1.0 * sum(counts.values()) / len(Bot.botlist)
for bot in Bot.botlist:
    score = 100 * counts[bot.colour] / avg
    print('Bot %s with colour %s scored %s' % (bot.prog, bot.colour, score))
image.save(BATTLE+'.png')

Arena sonuçları geldi. Dfblob botuna tüm arenalar için kırmızı renk verildi.

Arena 1:

Bot dfblob.py with colour (255, 0, 0) scored 163.75666666666666
Bot blob.py with colour (0, 255, 0) scored 14.896666666666667

1

Arena 2:

Bot blob.py with colour (0, 255, 0) scored 17.65563547726219
Bot dfblob.py with colour (255, 0, 0) scored 149.57006774236964

2

Arena 3:

Bot blob.py with colour (0, 255, 0) scored 21.09758208782965
Bot dfblob.py with colour (255, 0, 0) scored 142.9732433108277

3

Arena 4:

Bot blob.py with colour (0, 255, 0) scored 34.443810082244205
Bot dfblob.py with colour (255, 0, 0) scored 157.0684236785121

4


Algoritmanız, Blob'un güçlü kardeşi Boxer'da uyguladığımla aynı. Blob yeterince zor olmasaydı Boxer'ı kullanacaktım. Çok güzel animasyonlar.
Mantık Şövalyesi,

Python 3'te PIL kullanmak için, yastık kullanıyor musunuz?
trichoplax 16

@githubphagocyte Evet
SamYonnou

Bu GIF'leri yapmak için hangi yazılımı kullandınız?
TheNumberOne

1
@TheBestOne ImageMagick kullandımconvert -delay 5 -loop 0 result*.png animated.gif Bazı daha sonra buraya yüklenmek üzere manuel olarak kesilmesi gerektiğine rağmen özellikle komutu
SamYonnou

18

swallower

Dil = Java

Puan = 162,3289512601408075 169,4020975612382575

Düşmanları arar ve çevreler. Daha uzun bir süre vermek zorunda kalabilirsiniz. Oldukça biraz geliştirilebilir. Bazen geçersiz pikselleri yazdırır.

Güncelleme: Çok daha hızlı çevreler. Öncelikleri güncellemek için başka bir konu kullanır. Her zaman 0,1 saniye içinde geri döner. Skoru arttırmadan yenmek imkansız olmalıdır MAX_TURNS.

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

public class Swallower {

    static final byte MY_TYPE = 1;
    static final byte BLANK_TYPE = 0;
    static final byte NEUTRAL_TYPE = 2;
    static final byte ENEMY_TYPE = 3;
    private static final int WHITE = Color.WHITE.getRGB();
    private static final int MAX_TIME = 50;
    private final int color;
    private final int N;
    private final int width;
    private final int height;
    private final BufferedReader in;
    Lock borderLock;
    private final PriorityBlockingQueue<Pixel> border;
    private final Set<Pixel> borderSet;
    private final Thread updater;

    Lock imageLock;
    volatile byte[][] image;
    Lock priorityLock;
    volatile int[][] priority;
    volatile boolean updating;
    volatile private boolean exit;

    class Pixel implements Comparable<Pixel> {

        int x;
        int y;

        public Pixel(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public int compareTo(Pixel o) {
            return priority() - o.priority();
        }

        private int priority() {
            priorityLock.lock();
            int p = priority[x][y];
            priorityLock.unlock();
            return p;
        }

        public byte type() {
            imageLock.lock();
            byte i = image[x][y];
            imageLock.unlock();
            return i;
        }

        public boolean isBorder() {
            if (type() != BLANK_TYPE){
                return false;
            }
            for (Pixel p : pixelsAround()){
                if (p.type() == MY_TYPE){
                    return true;
                }
            }
            return false;
        }

        public void setType(byte newType) {
            imageLock.lock();
            image[x][y] = newType;
            imageLock.unlock();
        }

        public void setPriority(int newPriority) {
            borderLock.lock();
            boolean contains = borderSet.remove(this);
            if (contains){
                border.remove(this);
            }
            priorityLock.lock();
            priority[x][y] = newPriority;
            priorityLock.unlock();
            if (contains){
                border.add(this);
                borderSet.add(this);
            }
            borderLock.unlock();
        }

        public List<Pixel> pixelsAround() {
            List<Pixel> pixels = new ArrayList<>(4);
            if (x > 0){
                pixels.add(new Pixel(x - 1, y));
            }
            if (x < width - 1){
                pixels.add(new Pixel(x + 1, y));
            }
            if (y > 0){
                pixels.add(new Pixel(x, y - 1));
            }
            if (y < height - 1){
                pixels.add(new Pixel(x, y + 1));
            }
            return pixels;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Pixel pixel = (Pixel) o;

            return x == pixel.x && y == pixel.y;

        }

        @Override
        public int hashCode() {
            int result = x;
            result = 31 * result + y;
            return result;
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedImage image = ImageIO.read(new File(args[0]));
        int color = parseColorString(args[1]);
        int N = Integer.parseInt(args[2]);
        new Swallower(image, color, N).start();
    }

    private void start() throws IOException {
        updater.start();
        try {
            while (true) {
                String input = in.readLine();
                if (input.equals("exit")) {
                    exit = true;
                    if (!updating) {
                        updater.interrupt();
                    }
                    return;
                } else if (input.startsWith("colour")) {
                    updateImage(input);
                } else if (input.equals("pick pixels")) {
                    if (updating) {
                        try {
                            synchronized (Thread.currentThread()){
                                Thread.currentThread().wait(MAX_TIME);
                            }
                        } catch (InterruptedException ignored) {
                        }
                    }
                    for (int i = 0; i < N && !border.isEmpty(); i++) {
                        borderLock.lock();
                        Pixel p = border.poll();
                        borderSet.remove(p);
                        borderLock.unlock();
                        if (!p.isBorder()){
                            i--;
                            continue;
                        }
                        updateImage(MY_TYPE, p);
                        System.out.print(p.x + "," + p.y + " ");
                    }
                    System.out.println();
                }
            }
        } catch (Throwable e){
            exit = true;
            if (!updating){
                updater.interrupt();
            }
            throw e;
        }
    }

    private void updateImage(byte type, Pixel... pixels) {
        for (Pixel pixel : pixels){
            pixel.setType(type);
            if (type == MY_TYPE){
                pixel.setPriority(Integer.MAX_VALUE);
            } else {
                pixel.setPriority(0);
            }
        }
        for (Pixel pixel : pixels){
            for (Pixel p : pixel.pixelsAround()){
                if (p.type() == BLANK_TYPE){
                    addPixelToUpdate(p);
                }
                if (type == MY_TYPE && p.isBorder()){
                    borderLock.lock();
                    if (borderSet.add(p)){
                        border.add(p);
                    }
                    borderLock.unlock();
                }
            }
        }
    }

    private synchronized void addPixelToUpdate(Pixel p) {
        if (pixelsToUpdateSet.add(p)) {
            pixelsToUpdate.add(p);
            if (!updating){
                updater.interrupt();
            }
        }
    }

    Queue<Pixel> pixelsToUpdate;
    Set<Pixel> pixelsToUpdateSet;

    private void update(){
        while (true){
            if (exit){
                return;
            }
            if (pixelsToUpdate.isEmpty()){
                try {
                    updating = false;
                    while (!exit) {
                        synchronized (Thread.currentThread()) {
                            Thread.currentThread().wait();
                        }
                    }
                } catch (InterruptedException ignored){}
                continue;
            }
            updating = true;
            Pixel pixel = pixelsToUpdate.poll();
            if (pixel.type() != BLANK_TYPE){
                continue;
            }
            pixelsToUpdateSet.remove(pixel);
            updatePixel(pixel);
        }
    }

    private void updatePixel(Pixel pixel) {
        int originalPriority = pixel.priority();
        int minPriority = Integer.MAX_VALUE;
        List<Pixel> pixelsAround = pixel.pixelsAround();
        for (Pixel p : pixelsAround){
            int priority = p.priority();
            if (priority < minPriority){
                minPriority = priority;
            }
        }
        if (minPriority >= originalPriority){
            pixel.setPriority(Integer.MAX_VALUE);
            pixelsToUpdate.addAll(pixelsAround.stream().filter(p -> p.type() == 0 && p.priority() != Integer.MAX_VALUE).filter(pixelsToUpdateSet::add).collect(Collectors.toList()));
        } else {
            pixel.setPriority(minPriority + 1);
            for (Pixel p : pixelsAround){
                if (p.type() == 0 && p.priority() > minPriority + 2){
                    if (pixelsToUpdateSet.add(p)){
                        pixelsToUpdate.add(p);
                    }
                }
            }
        }

    }

    private void updateImage(String input) {
        String[] inputs = input.split("\\s");
        int color = parseColorString(inputs[1]);
        byte type;
        if (color == this.color){
            return;
        } else {
            type = ENEMY_TYPE;
        }
        Pixel[] pixels = new Pixel[inputs.length - 3];
        for (int i = 0; i < inputs.length - 3; i++){
            String[] coords = inputs[i + 3].split(",");
            pixels[i] = new Pixel(Integer.parseInt(coords[0]), Integer.parseInt(coords[1]));
        }
        updateImage(type, pixels);
    }

    private static int parseColorString(String input) {
        String[] colorString = input.split("[\\(\\),]");
        return new Color(Integer.parseInt(colorString[1]), Integer.parseInt(colorString[2]), Integer.parseInt(colorString[3])).getRGB();
    }

    private Swallower(BufferedImage image, int color, int N){
        this.color = color;
        this.N = N;
        this.width = image.getWidth();
        this.height = image.getHeight();
        this.image = new byte[width][height];
        this.priority = new int[width][height];
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                int pixelColor = image.getRGB(x,y);
                priority[x][y] = Integer.MAX_VALUE;
                if (pixelColor == WHITE){
                    this.image[x][y] = BLANK_TYPE;
                } else if (pixelColor == this.color){
                    this.image[x][y] = MY_TYPE;
                } else {
                    this.image[x][y] = NEUTRAL_TYPE;
                }
            }
        }
        border = new PriorityBlockingQueue<>();
        borderSet = Collections.synchronizedSet(new HashSet<>());
        borderLock = new ReentrantLock();
        priorityLock = new ReentrantLock();
        imageLock = new ReentrantLock();
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                Pixel pixel = new Pixel(x,y);
                if (pixel.type() == BLANK_TYPE){
                    if (pixel.isBorder()){
                        if (borderSet.add(pixel)){
                            border.add(pixel);
                        }
                    }
                }
            }
        }
        in = new BufferedReader(new InputStreamReader(System.in));
        updating = false;
        updater = new Thread(this::update);
        pixelsToUpdate = new ConcurrentLinkedQueue<>();
        pixelsToUpdateSet = Collections.synchronizedSet(new HashSet<>());
        exit = false;
    }

}

Nasıl çalışır:

Bu bot ekleyebileceği öncelik sırasını koruyor. Bir düşman pikselinin önceliği 0'dır. Boş bir pikselin önceliği, etrafındaki en düşük önceliğin 1'den büyük olmasıdır. Diğer tüm piksellerin bir Integer.MAX_VALUE önceliği vardır. Güncelleyici iş parçacığı sürekli piksellerin önceliklerini güncelliyor. Her dönüşte, en düşük N pikselleri öncelik sırasından çıkarılır.

Green Blob vs Red Swallower

Blob'un Puanı = 1.680553372583887225

Yutkunma Puanı = 169.4020975612382575

Arena 1:

Bot Blob.py with colour (0, 255, 0) scored 1.2183333333333333
Bot Swallower.class with colour (255, 0, 0) scored 177.435

görüntü tanımını buraya girin

Arena 2:

Bot Swallower.class with colour (255, 0, 0) scored 149.57829253338517
Bot Blob.py with colour (0, 255, 0) scored 0.5159187091564356

görüntü tanımını buraya girin

Arena 3:

Bot Blob.py with colour (0, 255, 0) scored 0.727104853136361
Bot Swallower.class with colour (255, 0, 0) scored 163.343720545521

görüntü tanımını buraya girin

Arena 4:

Bot Swallower.class with colour (255, 0, 0) scored 187.25137716604686
Bot Blob.py with colour (0, 255, 0) scored 4.260856594709419

görüntü tanımını buraya girin

Yeşil Swallower vs. Red Blob

Blob'un Puanı = 1.6852943642218457375

Kırlangıç ​​Gücü = 169.3923095387498625

Arena 1:

Bot Blob.py with colour (255, 0, 0) scored 1.3166666666666667
Bot Swallower.class with colour (0, 255, 0) scored 177.33666666666667

görüntü tanımını buraya girin

Arena 2:

Bot Swallower.class with colour (0, 255, 0) scored 149.57829253338517
Bot Blob.py with colour (255, 0, 0) scored 0.49573058575466195

görüntü tanımını buraya girin

Arena 3:

Bot Swallower.class with colour (0, 255, 0) scored 163.14367053301788
Bot Blob.py with colour (255, 0, 0) scored 0.9271548656394868

görüntü tanımını buraya girin

Arena 4:

Bot Swallower.class with colour (0, 255, 0) scored 187.51060842192973
Bot Blob.py with colour (255, 0, 0) scored 4.0016253388265675

görüntü tanımını buraya girin

Kırmızı Kırlangıç ​​vs Yeşil Derinlik İlk Blob

Yutkunma Puanı = 157.0749775233111925

Derinlik İlk Blob Puanı = 18.192783547939744

Arena 1:

Bot Swallower.class with colour (255, 0, 0) scored 173.52166666666668
Bot dfblob.py with colour (0, 255, 0) scored 5.131666666666667

görüntü tanımını buraya girin

Arena 2:

Bot dfblob.py with colour (0, 255, 0) scored 17.25635925887156
Bot Swallower.class with colour (255, 0, 0) scored 149.57829253338517

görüntü tanımını buraya girin

Arena 3:

Bot Swallower.class with colour (255, 0, 0) scored 153.59801488833747
Bot dfblob.py with colour (0, 255, 0) scored 10.472810510319889

görüntü tanımını buraya girin

Arena 4:

Bot dfblob.py with colour (0, 255, 0) scored 39.91029775590086
Bot Swallower.class with colour (255, 0, 0) scored 151.60193600485545

görüntü tanımını buraya girin

Yeşil Kırlangıç ​​vs Kırmızı Derinlik İlk Blob

Yutkunma Puanı = 154.3368355651281075

Derinlik İlk Blob'un Puanı = 18.84463249420435425

Arena 1:

Bot Swallower.class with colour (0, 255, 0) scored 165.295
Bot dfblob.py with colour (255, 0, 0) scored 13.358333333333333

görüntü tanımını buraya girin

Arena 2:

Bot dfblob.py with colour (255, 0, 0) scored 8.91118721119768
Bot Swallower.class with colour (0, 255, 0) scored 149.57829253338517

görüntü tanımını buraya girin

Arena 3:

Bot Swallower.class with colour (0, 255, 0) scored 157.01136822667206
Bot dfblob.py with colour (255, 0, 0) scored 7.059457171985304

görüntü tanımını buraya girin

Arena 4:

Bot dfblob.py with colour (255, 0, 0) scored 46.0495522603011
Bot Swallower.class with colour (0, 255, 0) scored 145.4626815004552

görüntü tanımını buraya girin

Green Blob - Kırmızı Derinlik First Blob - Blue Swallower:

Blob'un Puanı = 6.347962032393275525

Derinlik İlk Blob'un Puanı = 27.34842554331698275

Kırlangıç ​​Gücü = 227.720728953415375

Arena 1:

Bot Swallower.class with colour (0, 0, 255) scored 242.54
Bot Blob.py with colour (0, 255, 0) scored 1.21
Bot dfblob.py with colour (255, 0, 0) scored 24.3525

görüntü tanımını buraya girin

Arena 2:

Bot dfblob.py with colour (255, 0, 0) scored 17.828356088588478
Bot Blob.py with colour (0, 255, 0) scored 0.9252889892479551
Bot Swallower.class with colour (0, 0, 255) scored 224.36743880007776

görüntü tanımını buraya girin

Arena 3:

Bot dfblob.py with colour (255, 0, 0) scored 7.105141670032893
Bot Swallower.class with colour (0, 0, 255) scored 226.52057245080502
Bot Blob.py with colour (0, 255, 0) scored 12.621905476369092

görüntü tanımını buraya girin

Arena 4:

Bot dfblob.py with colour (255, 0, 0) scored 60.10770441464656
Bot Blob.py with colour (0, 255, 0) scored 10.634653663956055
Bot Swallower.class with colour (0, 0, 255) scored 217.45490456277872

görüntü tanımını buraya girin

İşte Sam Yonnou'nun birkaç değişiklikle hakimi, böylece dosyaları ve komutları ayrı ayrı belirlemelisiniz:

import sys, re, random, os, shutil, subprocess, datetime, time, signal, io
from PIL import Image

ORTH = ((-1,0), (1,0), (0,-1), (0,1))
def place(loc, colour):
    # if valid, place colour at loc and return True, else False
    if pix[loc] == (255,255,255):
        plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
        if any(pix[p]==colour for p in plist if 0<=p[0]<W and 0<=p[1]<H):
            pix[loc] = colour
            return True
    return False

def updateimage(image, msg, bot):
    if not re.match(r'(\s*\d+,\d+)*\s*', msg):
        return []
    plist = [tuple(int(v) for v in pr.split(',')) for pr in msg.split()]
    plist = plist[:PIXELBATCH]
    return [p for p in plist if place(p, bot.colour)]

class Bot:
    botlist = []
    def __init__(self, progs, command=None, colour=None):
        self.prog = progs[0]
        self.botlist.append(self)
        self.colour = colour
        self.colstr = str(colour).replace(' ', '')
        self.faults = 0
        self.env = 'env%u' % self.botlist.index(self)
        try: os.mkdir(self.env)
        except: pass
        for prog in progs:
            shutil.copy(prog, self.env)
        shutil.copy(imagename, self.env)
        os.chdir(self.env)
        args = command + [imagename, self.colstr, str(PIXELBATCH)]
        errorfile = 'err.log'
        with io.open(errorfile, 'wb') as errorlog:
            self.proc = subprocess.Popen(args, stdin=subprocess.PIPE, 
                stdout=subprocess.PIPE, stderr=errorlog)
        os.chdir('..')
    def send(self, msg):
        if self.faults < FAULTLIMIT:
            self.proc.stdin.write((msg+'\n').encode('utf-8'))
            self.proc.stdin.flush()
    def read(self, timelimit):
        if self.faults < FAULTLIMIT:
            start = time.time()
            inline = self.proc.stdout.readline().decode('utf-8')
            if time.time() - start > timelimit:
                self.faults += 1
                inline = ''
            return inline.strip()
    def exit(self):
        self.send('exit')

from cfg import *
for i, (progs, command) in enumerate(botspec):
    Bot(progs, command, colourspec[i])

image = Image.open(imagename)
pix = image.load()
W,H = image.size
resultdirectory = 'results of ' + BATTLE
os.mkdir(resultdirectory)

time.sleep(INITTIME)
total = 0
image.save(resultdirectory+'/'+'result000.png')
for turn in range(1, MAXTURNS+1):
    random.shuffle(Bot.botlist)
    nullbots = 0
    for bot in Bot.botlist:
        bot.send('pick pixels')
        inmsg = bot.read(TIMELIMIT)
        newpixels = updateimage(image, inmsg, bot)
        total += len(newpixels)
        if newpixels:
            pixtext = ' '.join('%u,%u'%p for p in newpixels)
            msg = 'colour %s chose %s' % (bot.colstr, pixtext)
            for msgbot in Bot.botlist:
                msgbot.send(msg)
        else:
            nullbots += 1
    if nullbots == len(Bot.botlist):
        break
    if turn % 100 == 0:
        print('Turn %s done %s pixels' % (turn, total))
        image.save(resultdirectory+'/result'+str(turn//100).zfill(3)+'.png')
image.save(resultdirectory+'/result999.png')
for msgbot in Bot.botlist:
    msgbot.exit()

resultfile = io.open(resultdirectory+'/result.txt','w')
counts = dict((c,f) for f,c in image.getcolors(W*H))
avg = 1.0 * sum(counts.values()) / len(Bot.botlist)
for bot in Bot.botlist:
    score = 100 * counts[bot.colour] / avg
    print('Bot %s with colour %s scored %s' % (bot.prog, bot.colour, score))
    print('Bot %s with colour %s scored %s' % (bot.prog, bot.colour, score), file=resultfile)
image.save(BATTLE+'.png')

Örnek cfg:

BATTLE = 'Green DepthFirstBlob vs Red Swallower @ arena1'
MAXTURNS = 20000
PIXELBATCH = 10
INITTIME = 2.0
TIMELIMIT = .1
FAULTLIMIT = 5

imagename = 'arena1.png'

colourspec = (0,255,0), (255,0,0)

botspec = [
    (['DepthFirstBlob.py'], ['python', 'DepthFirstBlob.py']),
    (['Swallower.class','Swallower$Pixel.class'], ['java', 'Swallower']),
    ]

Not: Yutkuyu yutmayı başarabilen herkes 100 itibar kazanır. Bu konuda başarılı olursanız lütfen aşağıdaki yorumlarda gönderin.


2
@githubphagocte İstendiği gibi.
TheNumberOne

1
Hakim değişiklikleriyle iyi iş çıkardın. Ayrı dosya kopyalama ve komut iyi bir fikirdir ve hata günlüğü çok gerekliydi.
Mantık Şövalye

1
Eğer MAXTURNS'i kastediyorsanız, değiştirmekten çekinmeyin. Bu kuralların bir parçası değil. Hakimin sonsuza dek sürmesini engelliyor (ancak fesih koşullarının yine de bunu önlediğini düşünüyorum).
Mantık Şövalye

1
@githubphagocyte Fixed
TheNumberOne

1
Animasyonlu savaşlarınıza baktıktan sonra, bir Swallower vs Swallower savaşının nasıl olacağını merak etmeye başladım. Biri hızlı bir şekilde diğerini tuzağa düşürür mü yoksa mekanın egemenliği için sürekli bir mücadele olur mu?
Mantık Şövalye

6

Rastgele, Dil = java, Puan = 0,43012126100275

Bu program rastgele ekranda pikselleri koyar. Piksellerin bazıları (tümü değilse) geçerli olmayacaktır. Bir yandan notta, bundan daha hızlı bir program yapmak zor olmalı.

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

public class Random {

    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    static int n;

    static int height;

    static int width;

    public static void main(String[] args) throws Exception{
        BufferedImage image = ImageIO.read(new File(args[0]));
        height = image.getHeight();
        width = image.getWidth();
        n = Integer.parseInt(args[2]);
        while (true){
            String input = in.readLine();
            if (input.equals("exit")){
                return;
            }
            if (!input.equals("pick pixels")){
                continue;
            }
            for (int i = 0; i < n; i++){
                System.out.print((int) (Math.random() * width) + ",");
                System.out.print((int) (Math.random() * height) + " ");
            }
            System.out.println();
        }
    }
}

Arena 1:

1

Arena 2:

2

Arena 3:

3

Arena 4:

4


7
Erken optimizasyon tuzağına düşmediğini görüyorum .
Mantık Şövalyesi
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.