Immagine Battaglia di colori


33

CONGRATULAZIONI a @kuroineko per la migliore entrata e vincendo la taglia 200 da @TheBestOne (sportività eccellente!).

Scrivi un programma per colorare il più possibile un'immagine prima dei programmi di opposizione.

Regole in breve

  • Al tuo programma verranno dati un'immagine, il tuo colore e il numero intero N.
  • Ad ogni turno ti vengono inviati aggiornamenti pixel da altri programmi e ti vengono richiesti i tuoi N aggiornamenti.
  • Puoi aggiornare qualsiasi pixel bianco accanto a un pixel del tuo colore.
  • Vince il programma che ha aggiunto il maggior numero di pixel.

Regole in dettaglio

Al programma verrà assegnato un nome file immagine PNG, un colore di casa e un numero N. Il numero N è il numero massimo di pixel che il programma può colorare ogni turno.

Esempio: MyProg arena.png (255,0,0) 30

L'immagine in ingresso sarà un rettangolo con lati tra 20 e 1000 pixel di lunghezza. Sarà composto da pixel neri, bianchi e colorati. Il tuo programma può scegliere una sequenza di pixel bianchi da colorare come tua, con la condizione che ogni nuovo pixel deve avere almeno uno dei suoi quattro pixel vicini del tuo colore. L'immagine inizialmente avrà almeno un pixel del tuo colore. Può anche avere pixel di colori a cui non è assegnato nessun programma. Il canale alfa non è utilizzato.

Il tuo obiettivo è bloccare i tuoi avversari e scrivere il tuo colore nel maggior numero di pixel possibile.

Ogni turno il tuo programma accetterà 1 o più righe di messaggio su STDIN e scriverà una riga composta da coordinate pixel su STDOUT. Ricordarsi di assegnare STDOUT come senza buffer o svuotare il buffer STDOUT ogni turno.

L'ordine dei giocatori chiamati ad ogni turno verrà assegnato casualmente. Ciò significa che un avversario (o il tuo programma) può avere 2 turni di fila.

Al tuo programma verranno inviati colour (N,N,N) chose X,Y X,Y ... X,Ymessaggi informativi che descrivono i pixel compilati dai programmi del lettore. Se un giocatore non fa mosse o mosse valide, non ti verrà inviato un messaggio sulle mosse di quel giocatore. Al tuo programma verrà inoltre inviato un messaggio relativo alle tue mosse accettate (se hai specificato almeno una mossa valida). Il pixel 0,0 si trova nell'angolo in alto a sinistra dell'immagine.

Alla ricezione pick pixels, il tuo programma genererà X,Y X,Y ... X,Yfino a N pixel (è consentita una stringa vuota composta solo da '\ n'). I pixel devono essere in ordine di stampa. Se un pixel non è valido, verrà ignorato e non verrà riportato nei giocatori. Il programma ha 2 secondi per inizializzare dopo l'avvio, ma solo 0,1 secondi per rispondere con una risposta ogni turno o perderà quel turno. Un aggiornamento di pixel inviato dopo 0,1 secondi registrerà un errore. Dopo 5 errori, il programma viene sospeso e non verranno inviati aggiornamenti o pick pixelsrichieste.

Quando il programma arbitro riceve una scelta di pixel vuota o non valida da ogni programma giocatore non sospeso, l'immagine verrà considerata completa e ai programmi verrà inviato il messaggio "esci". I programmi devono terminare dopo aver ricevuto "exit".

punteggio

Il giudice segnerà punti dopo che l'immagine è stata completata. Il tuo punteggio sarà il tuo numero di pixel aggiornato diviso per l'acquisizione media dei pixel in quel round, espressa in percentuale.

Il numero di pixel aggiunti all'immagine dal tuo giocatore è A. Il numero totale di pixel aggiunti da tutti i giocatori P è T. avg = T/P score = 100*A/avg

Pubblicazione dei punteggi

Viene dato un avversario di riferimento "The Blob". Per ogni risposta, dai un titolo al tuo bot con un nome, una lingua e il tuo punteggio (media dell'arena da 1 a 4) rispetto all'avversario di riferimento. Anche una foto o un'animazione di una delle tue battaglie sarebbe buona. Il vincitore è il programma con il punteggio più alto rispetto al bot di riferimento.

Se il Blob si rivela troppo facile da battere, potrei aggiungere un secondo round con un avversario di riferimento più forte.

Potresti anche sperimentare 4 o più programmi giocatore. Puoi anche testare il tuo bot con altri robot pubblicati come risposte.

Il giudice

Il programma judge richiede la comune Python Imaging Library (PIL) e dovrebbe essere facile da installare dal gestore dei pacchetti del sistema operativo su Linux. Ho un rapporto secondo cui PIL non funziona con Python a 64 bit su Windows 7, quindi controlla se PIL funzionerà per te prima di iniziare questa sfida (aggiornato 29-01-2015).

#!/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')

Esempio di configurazione - 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 - l'avversario di riferimento

# 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

Una battaglia di esempio - Blob vs Blob

Questa battaglia ebbe un risultato prevedibile:

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

Esempio di battaglia


Sei sicuro che questo non dovrebbe essere un [re della collina]?
Giustino,

Ci ho pensato. I robot non si combattono direttamente. Combattono il robot di riferimento. Questo esclude KOTH?
Logic Knight il

Sì, così com'è, non è un KOTH, ti stavo chiedendo se eri sicuro di voler combattere il bot di riferimento piuttosto che a vicenda.
Justin,

1
@TheBestOne, Aggiunto il supporto Java. Non testato con il programma Java però. Fammi sapere se non funziona.
Logic Knight il

1
I 10 pixel sono posizionati in ordine, quindi i pixel successivi potrebbero fare affidamento sui precedenti posizionamenti di pixel. Possono costruire l'uno sull'altro come suggerisci.
Logic Knight,

Risposte:


17

ColorFighter - C ++ - mangia un paio di rondini per colazione

MODIFICARE

  • ripulito il codice
  • aggiunta un'ottimizzazione semplice ma efficace
  • aggiunte alcune animazioni GIF

Dio, odio i serpenti (fingi che siano ragni, Indy)

In realtà adoro Python. Vorrei essere meno un ragazzo pigro e iniziare a impararlo correttamente, tutto qui.

Detto questo, ho dovuto lottare con la versione a 64 bit di questo serpente per far funzionare il giudice. Far funzionare PIL con la versione a 64 bit di Python in Win7 richiede più pazienza di quanto non fossi pronto a dedicare a questa sfida, quindi alla fine sono passato (dolorosamente) alla versione di Win32.

Inoltre, il giudice tende a bloccarsi gravemente quando un bot è troppo lento per rispondere.
Non essendo esperto di Python, non l'ho risolto, ma ha a che fare con la lettura di una risposta vuota dopo un timeout su stdin.

Un piccolo miglioramento sarebbe quello di mettere l'output di stderr in un file per ciascun bot. Ciò faciliterebbe la traccia per il debug post mortem.

Fatta eccezione per questi piccoli problemi, ho trovato il giudice molto semplice e piacevole da usare.
Complimenti per l'ennesima sfida creativa e divertente.

Il codice

#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();
}

Costruire l'eseguibile

Ho usato LODEpng.cpp e LODEpng.h per leggere immagini png.
Circa il modo più semplice che ho trovato per insegnare a questo linguaggio C ++ ritardato come leggere un'immagine senza dover costruire una mezza dozzina di librerie.
Basta compilare e collegare LODEpng.cpp insieme al principale e Bob è tuo zio.

Ho compilato con MSVC2013, ma dal momento che ho usato solo alcuni contenitori di base STL (deque e vettori), potrebbe funzionare con gcc (se sei fortunato).
In caso contrario, potrei provare una build di MinGW, ma francamente mi sto stancando dei problemi di portabilità in C ++.

Ai miei tempi ho fatto un sacco di C / C ++ portatili (su compilatori esotici per vari processori da 8 a 32 bit, nonché SunOS, Windows da 3.11 fino a Vista e Linux dalla sua infanzia a Ubuntu cooing zebra o qualsiasi altra cosa, quindi penso Ho una buona idea di cosa significhi portabilità), ma al momento non era necessario memorizzare (o scoprire) le innumerevoli discrepanze tra le interpretazioni GNU e Microsoft delle specifiche criptiche e gonfie del mostro STL.

Risultati contro Swallower

arena1 arena2 arena3 arena4

Come funziona

Al centro, si tratta di un semplice percorso di inondazione a forza bruta.

La frontiera del colore del giocatore (ovvero i pixel che hanno almeno un vicino bianco) viene utilizzata come seme per eseguire il classico algoritmo di inondazione di distanza.

Quando un punto raggiunge la vincinità di un colore nemico, viene calcolato un percorso all'indietro per produrre una serie di pixel che si spostano verso il punto nemico più vicino.

Il processo viene ripetuto fino a quando non sono stati raccolti abbastanza punti per una risposta della lunghezza desiderata.

Questa ripetizione è oscenamente costosa, specialmente quando si combatte vicino al nemico.
Ogni volta che viene trovata una stringa di pixel che porta dalla frontiera a un pixel nemico (e abbiamo bisogno di più punti per completare la risposta), il riempimento dell'inondazione viene rifatto dall'inizio, con il nuovo percorso aggiunto alla frontiera. Significa che potresti dover fare 5 o più inondazioni per ottenere una risposta di 10 pixel.

Se non sono più raggiungibili pixel nemici, vengono selezionati i vicini arbitrari dei pixel di frontiera.
L'algoritmo si trasforma in un inondazione piuttosto inefficiente, ma ciò accade solo dopo che è stato deciso il risultato del gioco (cioè non c'è più un territorio neutrale per cui combattere).
L'ho ottimizzato in modo che il giudice non impieghi anni a riempire la mappa una volta che la competizione è stata affrontata. Allo stato attuale, il tempo di esecuzione è trascurabile rispetto al giudice stesso.

Poiché i colori nemici non sono noti all'inizio, l'immagine iniziale dell'arena viene conservata per copiare le aree di partenza del nemico quando fa la sua prima mossa.
Se il codice viene riprodotto per primo, riempirà semplicemente alcuni pixel arbitrari.

Questo rende l'algoritmo in grado di combattere un numero arbitrario di avversari, e forse anche nuovi avversari che arrivano in un momento casuale, o colori che appaiono senza un'area di partenza (anche se questo non ha assolutamente alcun uso pratico).

La gestione dei nemici su base colore per colore consentirebbe anche di far cooperare due istanze del bot (usando le coordinate pixel per passare un segno di riconoscimento segreto).
Sembra divertente, probabilmente lo proverò :).

Il percorso pesante di calcolo viene eseguito non appena sono disponibili nuovi dati (dopo una notifica di spostamento) e alcune ottimizzazioni (aggiornamento della frontiera) vengono eseguite subito dopo che è stata fornita una risposta (per eseguire il massimo calcolo possibile durante gli altri giri di robot ).

Anche in questo caso, potrebbero esserci modi per fare cose più sottili se ci fossero più di 1 avversario (come interrompere un calcolo se diventano disponibili nuovi dati), ma in ogni caso non riesco a vedere dove è necessario il multitasking, purché l'algoritmo sia in grado di lavorare a pieno carico.

Problemi di prestazione

Tutto ciò non può funzionare senza un rapido accesso ai dati (e una maggiore potenza di calcolo rispetto all'intero programma Appolo, ovvero il tuo PC medio quando viene utilizzato per fare più di pubblicare pochi tweet).

La velocità dipende fortemente dal compilatore. Di solito GNU batte Microsoft con un margine del 30% (questo è il numero magico che ho notato in altre 3 sfide di codice relative al percorso), ma questo chilometraggio può variare ovviamente.

Il codice allo stato attuale rompe a malapena il sudore nell'arena 4. Windows perfmeter riporta circa il 4 al 7% di utilizzo della CPU, quindi dovrebbe essere in grado di far fronte a una mappa 1000x1000 entro il limite di tempo di risposta di 100ms.

Al centro di ogni algoritmo di pathing c'è un FIFO (possibilmente proritizzato, anche se non in quel caso), che a sua volta richiede una rapida allocazione degli elementi.

Dato che l'OP ha imposto obbligatoriamente un limite alla dimensione dell'arena, ho fatto alcuni calcoli e ho visto che le strutture dati fisse dimensionate al massimo (cioè 1.000.000 pixel) non consumerebbero più di una dozzina di megabyte, che il tuo PC medio mangia a colazione.
Infatti sotto Win7 e compilato con MSVC 2013, il codice consuma circa 14 Mb su arena 4, mentre i due thread di Swallower utilizzano più di 20 Mb.

Ho iniziato con i contenitori STL per semplificare la prototipazione, ma STL ha reso il codice ancora meno leggibile, dal momento che non desideravo creare una classe per incapsulare ogni singolo dato per nascondere l'offuscamento (se ciò è dovuto alle mie incapacità di far fronte alla STL è lasciato all'apprezzamento del lettore).
Indipendentemente da ciò, il risultato è stato così atrocemente lento che all'inizio ho pensato di creare una versione di debug per errore.

Ritengo che ciò sia dovuto in parte all'implementazione incredibilmente scarsa di Microsoft dell'STL (dove, ad esempio, vettori e bitets eseguono controlli associati o altre operazioni crittografiche sull'operatore [], in diretta violazione delle specifiche), e in parte al design STL si.

Potrei far fronte alle atroci problemi di sintassi e portabilità (cioè Microsoft vs GNU) se le prestazioni fossero lì, ma questo non è certamente il caso.

Ad esempio, dequeè intrinsecamente lento, perché mescola molti dati di contabilità in attesa dell'occasione per fare il suo ridimensionamento super intelligente, di cui non me ne può fregare di meno.
Certo avrei potuto implementare un allocatore personalizzato e ridurre altri bit di template personalizzati, ma un allocatore personalizzato costa solo poche centinaia di righe di codice e la parte migliore di un giorno per testare, che cosa con la dozzina di interfacce deve implementare, mentre un la struttura equivalente fatta a mano è circa zero righe di codice (anche se più pericolosa, ma l'algoritmo non avrebbe funzionato se non avessi saputo - o penso di sapere - quello che stavo facendo comunque).

Così alla fine ho tenuto i contenitori STL in parti non critiche del codice e ho costruito il mio brutale allocatore e FIFO con due array di circa 1970 e tre cortometraggi non firmati.

Deglutizione del deglutitore

Come confermato dal suo autore, i modelli irregolari di Swallower sono causati da ritardo tra le notifiche delle mosse nemiche e gli aggiornamenti dal thread di percorso.
Il perfmeter di sistema mostra chiaramente il thread di pathing che consuma CPU al 100% in ogni momento, e gli schemi frastagliati tendono ad apparire quando il focus del combattimento si sposta su una nuova area. Questo è anche abbastanza evidente con le animazioni.

Un'ottimizzazione semplice ma efficace

Dopo aver visto gli epici combattimenti tra cani di Swallower e il mio combattente, mi sono ricordato un vecchio detto del gioco di Go: difendi da vicino, ma attacca da lontano.

C'è saggezza in questo. Se provi ad attaccarti troppo all'avversario, sprecherai mosse preziose cercando di bloccare ogni possibile percorso. Al contrario, se rimani a un solo pixel di distanza, probabilmente eviterai di riempire piccoli spazi vuoti che guadagnerebbero molto poco e utilizzerai le tue mosse per contrastare minacce più importanti.

Per implementare questa idea, ho semplicemente esteso le mosse di un nemico (contrassegnando i 4 vicini di ogni mossa come pixel nemico).
Questo ferma l'algoritmo di tracciamento a un pixel di distanza dal confine del nemico, permettendo al mio combattente di aggirare un avversario senza essere catturato in troppi combattimenti.

Puoi vedere il miglioramento
(anche se tutte le esecuzioni non hanno lo stesso successo, puoi notare i contorni molto più fluidi):

prima dopo


1
Wow. Pensavo che nulla avrebbe battuto lo Swallower. Ottima soluzione con ottima descrizione. Ricordo K&R C dei bei vecchi tempi, ma poi C è andato sul lato oscuro. Penso che ti piacerà Python .
Logic Knight,

È stato un vero piacere affrontare una sfida così ... beh ... stimolante e divertente. Questo mi ha permesso di testare questo piccolo gioiello di LODEpng in scala reale, ei risultati sono così promettenti che potrei rivisitare il pilota png, testando ancora una volta la mia relazione amore / odio con questo famigerato C. post-incrementato

1
Lo swallower è un po 'irregolare a volte per mantenere entro il limite di tempo. Questo è in parte il motivo del multi-threading. Buon lavoro!! Penso che raddoppierò il mio bonus ...
TheNumberOne

1
Pillow ha download per 64 bit. Può essere usato proprio come PIL.
TheNumberOne

@TheBestOne L'ho pensato. Il mio brutale pittore approfitta di questi momenti in cui il tuo deglutitore sgranocchia dati obsoleti :). Per quanto riguarda PIL, ho scaricato tutte le versioni Amd64 PIL e Pillow disponibili sul World Wide Web, ma non funzionavano con il mio core Python a 63,5 bit, che probabilmente era una versione bootleg e / o obsoleta. Ad ogni modo, la porta Win32 funziona altrettanto bene, e se un giorno avrò bisogno di qualcosa di più veloce dovrò passare a PyPy lo stesso.

21

Profondità Blob vs. Blob

Lingua = Python (3.2)

Punteggio = 111.475388276 153.34210035

Aggiornamento: ora utilizza una Setclasse personalizzata per ottenere il pop()metodo per produrre una sorta di griglia che migliora drasticamente l'area coperta all'inizio tagliando gran parte dell'immagine dal nemico. Nota: sto usando una griglia 12 x 12 per questo quale di un campione casuale di dimensioni della griglia sembrava dare i migliori risultati per arena3 (quello che ha ottenuto il punteggio peggiore prima dell'aggiornamento), tuttavia è molto probabile che un più ottimale la dimensione della griglia esiste per la data selezione di arene.

Ho optato per una semplice modifica al bot di riferimento per favorire la scelta di punti fattibili che sono delimitati da il minor numero di punti del proprio colore possibile. Un miglioramento potrebbe essere quello di favorire anche la raccolta di punti fattibili che sono delimitati da quanti più punti possibile di colore nemico.

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

Il giudice originale è stato leggermente modificato per funzionare con Python 3.2 (e per aggiungere una funzionalità di registrazione grezza ai robot + salva periodicamente l'immagine dell'arena per realizzare l'animazione):

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

Seguono i risultati dell'arena. Al bot dfblob è stato dato il colore rosso per tutte le arene.

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


Il tuo algoritmo è lo stesso di quello che ho implementato nel fratello più forte di Blob, Boxer. Avrei usato Boxer se Blob non fosse stata una sfida sufficiente. Animazioni anche molto belle.
Logic Knight il

Per usare PIL in Python 3, stai usando il cuscino ?
trichoplax,

@githubphagocyte Sì
SamYonnou,

Quale software hai usato per creare quelle GIF?
TheNumberOne

1
@TheBestOne Ho usato ImageMagick in particolare il comando convert -delay 5 -loop 0 result*.png animated.gifsebbene alcune delle gif
dovessero

18

swallower

Lingua = Java

Punteggio = 162,3289512601408075 169,4020975612382575

Cerca i nemici e li circonda. Potrebbe essere necessario concedergli un termine più lungo. Potrebbe essere migliorato un po '. A volte stampa pixel non validi.

Aggiornamento: circonda molto più velocemente. Utilizza un altro thread per aggiornare le priorità. Ritorna sempre entro 0,1 secondi. Il punteggio dovrebbe essere impossibile da battere senza aumentare 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;
    }

}

Come funziona:

Questo bot mantiene una coda prioritaria di pixel che può aggiungere. La priorità di un pixel nemico è 0. La priorità di un pixel vuoto è 1 maggiore della priorità più bassa attorno ad esso. Tutti gli altri pixel hanno una priorità di Integer.MAX_VALUE. Il thread di aggiornamento aggiorna costantemente le priorità dei pixel. Ad ogni giro gli N pixel più bassi vengono eliminati dalla coda di priorità.

Green Blob vs Red Swallower

Punteggio di Blob = 1.680553372583887225

Punteggio di Swallower = 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

inserisci qui la descrizione dell'immagine

Arena 2:

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

inserisci qui la descrizione dell'immagine

Arena 3:

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

inserisci qui la descrizione dell'immagine

Arena 4:

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

inserisci qui la descrizione dell'immagine

Green Swallower vs. Red Blob

Punteggio di Blob = 1.6852943642218457375

Punteggio di Swallower = 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

inserisci qui la descrizione dell'immagine

Arena 2:

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

inserisci qui la descrizione dell'immagine

Arena 3:

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

inserisci qui la descrizione dell'immagine

Arena 4:

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

inserisci qui la descrizione dell'immagine

Red Swallower vs Green Depth First Blob

Punteggio di Swallower = 157.0749775233111925

Profondità Primo punteggio BLOB = 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

inserisci qui la descrizione dell'immagine

Arena 2:

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

inserisci qui la descrizione dell'immagine

Arena 3:

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

inserisci qui la descrizione dell'immagine

Arena 4:

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

inserisci qui la descrizione dell'immagine

Green Swallower vs Red Depth First Blob

Punteggio di Swallower = 154.3368355651281075

Profondità Primo punteggio BLOB = 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

inserisci qui la descrizione dell'immagine

Arena 2:

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

inserisci qui la descrizione dell'immagine

Arena 3:

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

inserisci qui la descrizione dell'immagine

Arena 4:

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

inserisci qui la descrizione dell'immagine

Green Blob vs Red Depth First Blob vs Blue Swallower:

Punteggio di Blob = 6.347962032393275525

Profondità Primo punteggio BLOB = 27.34842554331698275

Punteggio di Swallower = 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

inserisci qui la descrizione dell'immagine

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

inserisci qui la descrizione dell'immagine

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

inserisci qui la descrizione dell'immagine

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

inserisci qui la descrizione dell'immagine

Ecco il giudice di Sam Yonnou con alcune modifiche in modo da specificare i file e il comando separatamente:

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

Esempio 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']),
    ]

Nota: chiunque riesca a ingoiare il Rondine ottiene una generosità di 100 reputazione. Si prega di inviare commenti qui sotto se ci riuscite.


2
@githubphagocyte Come richiesto.
TheNumberOne

1
Bel lavoro con il giudice cambia. La copia e il comando di file separati sono una buona idea e la registrazione degli errori era estremamente necessaria.
Logic Knight,

1
Se intendevi MAXTURNS, sentiti libero di cambiarlo. Non fa parte delle regole. Impedisce al giudice di correre per sempre (ma immagino che le condizioni di risoluzione lo impediscano comunque).
Logic Knight,

1
@githubphagocyte Fixed
TheNumberOne

1
Dopo aver visto le tue battaglie animate, ho iniziato a chiedermi come sarebbe una battaglia tra Swallower e Swallower. L'uno intrappolerebbe rapidamente l'altro o sarebbe una lotta costante per il dominio dello spazio?
Logic Knight,

6

Casuale, Lingua = java, Punteggio = 0.43012126100275

Questo programma posiziona casualmente i pixel sullo schermo. Alcuni (se non tutti) dei pixel non saranno validi. Da un lato, dovrebbe essere difficile creare un programma più veloce di questo.

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
Vedo che non sei caduto nella trappola dell'ottimizzazione prematura .
Logic Knight il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.