Зображення Битва кольорів


33

ВІТАємо @kuroineko за найкращий запис та виграш 200 баунтів від @TheBestOne (відмінне спортивне майстерність!).

Напишіть програму, щоб розфарбувати якомога більше зображення, перш ніж це робити програми опозиції.

Правила коротко

  • Вашій програмі буде надано зображення, колір та ціле число N.
  • Кожного кроку вам надсилаються оновлення пікселів іншими програмами та запитуються ваші N оновлення.
  • Ви можете оновити будь-який білий піксель, який знаходиться поруч із пікселем вашого кольору.
  • Перемагає програма, яка додала найбільше пікселів.

Правила докладно

Вашій програмі буде надано ім'я файлу зображення PNG, домашній колір та число N. Число N - це максимальна кількість пікселів, яку ваша програма може зафарбувати кожного кроку.

Приклад: MyProg arena.png (255,0,0) 30

Вхідним зображенням буде прямокутник зі сторонами довжиною від 20 до 1000 пікселів. Він буде складатися з чорних, білих та кольорових пікселів. Ваша програма може вибрати послідовність білого кольору пікселів, які слід забарвити як власну, за умови, що кожен новий піксель повинен мати принаймні один із чотирьох сусідніх пікселів власного кольору. Зображення спочатку матиме принаймні один піксель вашого кольору. Він також може мати пікселі кольорів, яким не призначена жодна програма. Альфа-канал не використовується.

Ваша мета - блокувати опонентів і записати свій колір у стільки пікселів, скільки ви можете.

Кожен поворот ваша програма прийме 1 або більше рядків повідомлень на STDIN і запише рядок, що складається з піксельних координат на STDOUT. Не забудьте призначити STDOUT як небуферизований або промити буфер STDOUT кожного витку.

Порядок гравців, що викликають кожен виток, буде призначений випадковим чином. Це означає, що опонент (або ваша програма) може мати 2 витки поспіль.

Вашій програмі будуть надіслані colour (N,N,N) chose X,Y X,Y ... X,Yінформаційні повідомлення, що описують пікселі, заповнені програмами програвача. Якщо гравець не робить жодних рухів або не має дійсних кроків, вам не буде надіслано повідомлення про ходи цього гравця. Вашій програмі також буде надіслано повідомлення про ваші власні прийняті кроки (якщо ви вказали принаймні один дійсний хід). Піксель 0,0 знаходиться у лівому верхньому куті зображення.

Після отримання pick pixelsпрограма видасть X,Y X,Y ... X,Yдо N пікселів (пуста рядок, що складається з просто \ \ n ', дозволено). Пікселі повинні бути в порядку побудови. Якщо піксель недійсний, він буде ігнорований і не буде в звіті гравцям. Ваша програма має 2 секунди для ініціалізації після запуску, але лише 0,1 секунди, щоб відповісти кожним кроком, або вона пропустить цю чергу. Оновлення пікселів, надіслане через 0,1 секунди, запише помилку. Після 5 помилок ваша програма буде призупинена, і не надсилатимуться оновлення чи pick pixelsзапити.

Коли програма судді отримає порожній або недійсний вибір пікселя від кожної програми, що не припиняється, програвач буде вважатися завершеним, а програмам буде надіслано повідомлення "вихід". Програми мають припинитись після отримання "виходу".

Оцінка балів

Суддя набере очки після завершення зображення. Вашим балом буде кількість оновлених пікселів, поділене на середнє захоплення пікселів цього раунду, виражене у відсотках.

Кількість пікселів, доданих до зображення програвачем, становить A. Загальна кількість пікселів, доданих усіма P-плеєрами, становить T. avg = T/P score = 100*A/avg

Опублікування балів

Дано опорний опонент "The Blob". Для кожної відповіді присвойте боту ім'я, мову та бал (середній показник на арені від 1 до 4) проти опорного опонента. Зображення або анімація однієї з ваших битв також буде добре. Переможець - програма з найвищим балом проти опорного бота.

Якщо Blob виявиться занадто легким для перемоги, я можу додати другий раунд з сильнішим опорним опонентом.

Можливо, ви також хочете експериментувати з чотирма або більше програмами програвача. Ви також можете протестувати свого бота на інших ботах, розміщених як відповіді.

Суддя

Програма судді вимагає загальної бібліотеки зображень Python Imaging (PIL) і її слід легко встановити з вашого менеджера пакунків ОС в Linux. У мене є звіт про те, що PIL не працює з 64-бітним Python в Windows 7, тому, будь ласка, перевірте, чи PIL буде працювати для вас, перш ніж розпочати цю проблему (оновлено 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')

Приклад конфігурації - 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'),
    ]

Blob - опорний опонент

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

Арена 1

arena1.png

Арена 2

arena2.png

Арена 3

arena3.png

Арена 4

arena4.png

Приклад бою - Blob vs Blob

Цей бій мав передбачуваний результат:

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

Example Battle


Ви впевнені, що це не повинно бути [царем горбика]?
Джастін

Я про це думав. Боти не воюють між собою безпосередньо. Вони б'ються з еталонним ботом. Це виключає КОТУ?
Логічний лицар

Так, це так, це не КОТІ, я питав, чи ти впевнений, що ти хочеш битися з контрольним ботом, а не один з одним.
Джастін

1
@TheBestOne, Додана підтримка Java. Проте не перевірено з програмою Java. Повідомте мене, якщо це не працює.
Логічний лицар

1
10 пікселів розміщуються в порядку, тому пізні пікселі можуть спиратися на попередні місця розташування пікселів. Вони можуть будувати один одного, як ви пропонуєте.
Логічний лицар

Відповіді:


17

ColorFighter - C ++ - з'їдає пару ковтальників на сніданок

EDIT

  • очистили код
  • додали просту, але ефективну оптимізацію
  • додано кілька анімацій GIF

Боже, я ненавиджу змій (просто роблю вигляд, що вони павуки, Інді)

Насправді я люблю Python. Мені б хотілося, щоб я був менш ледачим хлопчиком і почав це правильно навчати, ось і все.

Враховуючи це, мені довелося боротися з 64-бітовою версією цієї змії, щоб суддя працював. Здійснення роботи PIL з 64-бітної версією Python під Win7 вимагає більше терпіння, ніж я був готовий присвятити цьому виклику, тому врешті-решт я перейшов (болісно) на версію Win32.

Крім того, Суддя схильний до краху, коли бот занадто повільний, щоб реагувати.
Не будучи кмітливим Python, я цього не виправив, але це стосується читання порожньої відповіді після тайм-ауту на stdin.

Невеликим поліпшенням буде поставити більш жорсткий вихід у файл для кожного бота. Це полегшило б відстежувати налагодження після смерті.

За винятком цих незначних проблем, я вважав Суддя дуже простим і приємним у використанні.
Кудо ще один винахідливий і веселий виклик.

Кодекс

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

Побудова виконуваного файлу

Я використовував LODEpng.cpp і LODEpng.h для читання PNG-зображень.
Про найпростіший спосіб я дізнався навчити цю відсталу мову C ++ як читати картинку, не будуючи півдесятка бібліотек.
Просто складіть і зв’яжіть LODEpng.cpp разом з головним та вашим дядьком Бобом.

Я компілював з MSVC2013, але оскільки я використав лише декілька базових контейнерів STL (deque та vectors), він може працювати з gcc (якщо пощастить).
Якщо це не так, я можу спробувати збільшити MinGW, але, чесно кажучи, я втомився від проблем з портативністю на C ++.

Я робив досить багато портативних C / C ++ у свої дні (на екзотичних компіляторах для різних 8 - 32 бітових процесорів, а також SunOS, Windows від 3.11 до Vista та Linux, починаючи з дитинства до кубічної зебри Ubuntu чи будь-чого іншого, тому я думаю Я маю досить гарне уявлення про те, що означає портативність), але в той час воно не вимагало запам’ятовування (або виявлення) незліченних розбіжностей між інтерпретаціями GNU та Microsoft щодо криптованих та роздутих специфік монстра STL.

Результати проти Ластівки

arena1 arena2 arena3 arena4

Як це працює

По суті, це простий шлях, спрямований на заповнення грубої сили.

Межа кольору гравця (тобто пікселі, що мають принаймні одного білого сусіда) використовується як насіння для виконання класичного алгоритму затоплення відстані.

Коли точка досягає винності ворожих кольорів, обчислюється відсталий шлях для отримання рядка пікселів, що рухається до найближчого ворога.

Процес повторюється, поки не буде зібрано достатньо балів для відповіді потрібної тривалості.

Це повторення нецензурно дорого, особливо при боях поблизу ворога.
Щоразу, коли рядок пікселів, що ведуть від кордону до ворожих пікселів, знайдений (і нам потрібно більше очок для завершення відповіді), заливка потопу переробляється з початку, при цьому новий шлях додається до кордону. Це означає, що вам доведеться виконати 5 заливних чи більше, щоб отримати відповідь 10 пікселів.

Якщо більше ворожих пікселів недоступні, вибираються арбітражні сусіди прикордонних пікселів.
Алгоритм переходить до досить неефективного заповнення, але це відбувається лише після того, як буде вирішено результат гри (тобто більше немає нейтральної території, за яку потрібно боротися).
Я оптимізував це, щоб суддя не витрачав віки на заповнення карти після розгляду змагань. У своєму нинішньому стані час виконання страт є незначним порівняно із самим суддею.

Оскільки кольори противника невідомі на початку, початкове зображення арени зберігається в магазині, щоб скопіювати стартові зони противника під час першого кроку.
Якщо код відтвориться спочатку, він просто заповнить кілька довільних пікселів.

Це робить алгоритм здатним боротися з довільною кількістю супротивників і навіть, можливо, з новими супротивниками, що приходять у випадковий момент часу, або кольорами, що з’являються без початкової області (хоча це практично не має практичного використання).

Поводження з ворогом на основі кольору за кольором також дозволить співпрацювати два екземпляри бота (за допомогою піксельних координат для передачі секретного знаку розпізнавання).
Здається, це весело, я, мабуть, спробую це :).

Обчислювальний шлях для обчислень проводиться, як тільки з’являються нові дані (після повідомлення про переміщення), а деякі оптимізації (пограничне оновлення) проводяться одразу після надання відповіді (зробити якомога більше обчислень під час інших ботів. ).

Знову ж таки, можуть бути способи робити більш тонкі речі, якби було більше 1 супротивника (наприклад, перервати обчислення, якщо з’являться нові дані), але в будь-якому випадку я не бачу, де потрібна багатозадачність, доки алгоритм є вміє працювати на повне навантаження.

Питання продуктивності

Все це не може працювати без швидкого доступу до даних (і більше обчислювальної потужності, ніж уся програма Appolo, тобто ваш середній ПК, коли використовується більше, ніж опублікувати кілька твітів).

Швидкість сильно залежить від компілятора. Зазвичай GNU долає Майкрософт з 30% запасом (це магічне число, яке я помітив у 3 інших проблемах, пов'язаних з кодом), але цей пробіг може змінюватися, звичайно.

Код у своєму нинішньому стані ледве розбиває потужність на арені 4. Перфметр Windows повідомляє про 4–7% використання процесора, тому він повинен мати можливість справлятися з картою 1000х1000 у межах 100 мс часу відгуку.

В основі кожного алгоритму трасування лежить FIFO (можливо, проритизований, хоча і не в цьому випадку), що, в свою чергу, вимагає швидкого розподілу елементів.

Оскільки ОП обов'язково встановив обмеження розміру арени, я зробив декілька математик і побачив, що фіксовані структури даних розміром до максимуму (тобто 1.000.000 пікселів) не будуть споживати більше ніж пару десятків мегабайт, які ваш середній ПК їсть на сніданок.
Дійсно, під Win7 та складений з MSVC 2013, на арені 4 код споживає близько 14 Мб, тоді як два потоки Ластівки використовують більше 20 Мб.

Я почав з контейнерів STL для більш легкого прототипування, але STL зробив код ще менш читабельним, оскільки я не мав бажання створювати клас, щоб інкапсулювати кожний шматочок даних, щоб приховати заплутаність подалі (чи це через мої власні нездатності впоратися зі СТЛ залишається на оцінку читача).
Незважаючи на це, результат був настільки жорстоко повільним, що спочатку я подумав, що будую версію налагодження помилково.

Я вважаю, що це частково пов’язано з надзвичайно поганою реалізацією STL від Microsoft (де, наприклад, вектори та бітсети виконують зв'язані перевірки або інші криптовалютні операції з оператором [], в прямому порушенні специфікації), а частково і з дизайном STL себе.

Я міг би впоратися із жорстокими проблемами синтаксису та портативності (тобто Microsoft проти GNU), якби виступи там були, але це, звичайно, не так.

Наприклад, deque вона по своїй суті є повільною, тому що вона перетасовує безліч даних бухгалтерського обліку навколо, чекаючи нагоди, щоб зробити її супер розумний розмір, про який я не міг би піклуватися менше.
Звичайно, я міг реалізувати спеціальний розподільник та інший користувальницький біт шаблону, але сам призначений розподільник коштує кілька сотень рядків коду та більшу частину дня, щоб перевірити, що з десяток інтерфейсів він має реалізувати, а еквівалентна структура ручної роботи - це приблизно нульові рядки коду (хоч і небезпечніше, але алгоритм не працював би, якби я не знав - або думав, що знаю - що я все-таки робив).

Тож зрештою я зберігав контейнери STL у некритичних частинах коду, і створив власний жорстокий розподільник та FIFO з двома масивами по близько 1970 та трьома неподписаними шортами.

Проковтування ковтальника

Як підтвердив її автор, помилкові зразки Ластівки спричинені відставанням між сповіщеннями про переміщення ворога та оновленнями від потокової лінії.
Системний перфметр чітко показує, що потоковий контур витрачає 100% процесора весь час, і нерівні візерунки, як правило, з'являються, коли фокус боротьби зміщується на нову область. Це також досить очевидно з анімаціями.

Проста, але ефективна оптимізація

Подивившись епічні собачі бої між Ластівчиною та моїм бійцем, я згадав стару приказку з гри Go: захищай близько, але атакуй здалеку.

У цьому є мудрість. Якщо ви будете намагатися дотримуватися свого противника занадто багато, ви витратите дорогоцінні кроки, намагаючись перекрити кожен можливий шлях. Навпаки, якщо ви залишаєтесь лише на один піксель, ви, швидше за все, уникнете заповнення невеликих прогалин, які набрали б дуже мало, і використовуйте свої кроки для протидії більш важливим загрозам.

Для втілення цієї ідеї я просто розширив ходи ворога (позначаючи 4 сусіди кожного ходу як піксель ворога).
Це зупиняє алгоритм проходження маршруту на один піксель від межі ворога, дозволяючи моєму винищувачеві обходити супротивника, не потрапляючи в занадто багато собачих боїв.

Ви можете побачити поліпшення
(хоча всі запуски не такі успішні, ви можете помітити набагато більш плавні контури):

before after


1
Ого. Я думав, що нічого не збирається бити Ластівку. Відмінне рішення з чудовим описом. Я пам'ятаю K&R C з старих добрих часів, але потім C пішов у темну сторону. Я думаю, що вам сподобається Python .
Логічний лицар

Було справжньою насолодою вирішити такий виклик так ... ну .. складно та весело. Це дозволило мені протестувати цю маленьку дорогоцінну дорогоцінну дорогоцінну камеру LODEpng в повному масштабі, і результати настільки обіцяють, що я можу переглянути гонщик png, протестуючи ще раз свої любовні / ненависні стосунки з цим сумнозвісним

1
Ластівка часом трохи нестабільна, щоб дотримуватись цього терміну. Це частково те, для чого призначена багатонарізка. Хороша робота!! Думаю, я подвоїть свій бонус ...
TheNumberOne

1
Подушка має завантаження для 64-біт. Його можна використовувати так само, як і PIL.
TheNumberOne

@TheBestOne Я так думав. Мій жорстокий художник скористався цими моментами, коли ваш ковтальник збирає застарілі дані :). Що стосується PIL, я завантажував про всі версії Amd64 PIL та Pillow, доступні у всесвітній павутині, але вони не працюватимуть з моїм основним 63,5 бітним Python, який, ймовірно, був завантажувальною та / або застарілою версією. У будь-якому випадку, порт Win32 працює так само добре, і якщо одного дня мені потрібно щось швидше, мені доведеться все-таки перейти на PyPy.

21

Глибина перший Blob проти Blob

Мова = Python (3.2)

Оцінка = 111,475388276 153.34210035

Оновлення. Тепер для користування користувальницьким Setкласомpop() метод для створення свого роду сітки, яка різко покращує зону, охоплену на початку, відрізаючи великі частини зображення від ворога. Примітка: я використовую сітку 12 x 12 для цього, який з випадкової вибірки розмірів сітки, здавалося, дає найкращі результати для arena3 (той, який отримав найгірший бал перед оновленням), однак, дуже ймовірно, що буде більш оптимальним розмір сітки існує для даного вибору арен.

Я пішов на просту модифікацію до опорного бота, щоб зробити його корисним для вибору можливих точок, які межують з якомога меншою кількістю кольорових точок. Поліпшенням може бути також сприяння вибору можливих точок, які межують з якомога більшою кількістю кольорів противника.

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

Оригінальний суддя був трохи модифікований для роботи з Python 3.2 (і для додавання ботів функціонального режиму ведення журналу + періодично зберігати зображення арени для анімації):

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

Результати арени випливають. Бот dfblob отримав червоний колір для всіх арен.

Арена 1:

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

1

Арена 2:

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

2

Арена 3:

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

3

Арена 4:

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

4


Ваш алгоритм такий самий, як той, який я реалізував у більш сильному браті Боксера. Я збирався використовувати Boxer, якщо Blob не був достатньою проблемою. Дуже приємні анімації теж.
Логічний лицар

Щоб використовувати PIL в python 3, ви використовуєте подушку ?
трихоплакс

@githubphagocyte Так
SamYonnou

Яке програмне забезпечення ви використовували для створення цих GIF?
TheNumberOne

1
@TheBestOne Я спеціально використовував команду ImageMagick,convert -delay 5 -loop 0 result*.png animated.gif хоча деякі gif-файли потрібно було згодом скоротити вручну, щоб завантажити їх сюди
SamYonnou

18

Ластівка

Мова = Java

Оцінка = 162.3289512601408075 169.4020975612382575

Шукає ворогів і оточує їх. Можливо, вам доведеться дати йому більш тривалий термін. Можна було б трохи покращити. Іноді друкується недійсні пікселі.

Оновлення: оточує набагато швидше. Використовує інший потік для оновлення пріоритетів. Завжди повертається протягом .1 секунди. Оцінка не може бути переможена без збільшення 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;
    }

}

Як це працює:

Цей бот підтримує чергу пріоритетів пікселів, які він може додати. Пріоритет ворожих пікселів - 0. Пріоритет порожнього пікселя на 1 більший, ніж найнижчий пріоритет навколо нього. Усі інші пікселі мають пріоритет Integer.MAX_VALUE. Нитка оновлення постійно оновлює пріоритети пікселів. Кожного ходу найнижчі N пікселів вискакують із черги пріоритету.

Зелений клубок проти червоного ластівка

Показник Blob = 1.680553372583887225

Оцінка ластівки = 169.4020975612382575

Арена 1:

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

enter image description here

Арена 2:

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

enter image description here

Арена 3:

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

enter image description here

Арена 4:

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

enter image description here

Зелена ластівка проти Червоної кулі

Показник Blob = 1.6852943642218457375

Оцінка ластівки = 169.3923095387498625

Арена 1:

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

enter image description here

Арена 2:

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

enter image description here

Арена 3:

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

enter image description here

Арена 4:

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

enter image description here

Червоний Ластівка проти Зеленої Глибини Перший Куля

Оцінка ластівки = 157.0749775233111925

Перший показник глибини Blob = 18.192783547939744

Арена 1:

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

enter image description here

Арена 2:

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

enter image description here

Арена 3:

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

enter image description here

Арена 4:

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

enter image description here

Зелений ластівчик проти червоної глибини перший куля

Оцінка ластівки = 154.3368355651281075

Перший показник поглиблення Blob = 18,84463249420435425

Арена 1:

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

enter image description here

Арена 2:

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

enter image description here

Арена 3:

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

enter image description here

Арена 4:

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

enter image description here

Зелений кульб проти червоної глибини Перший клубок проти синього ластівка:

Показник Blob = 6.347962032393275525

Перший показник поглиблення Blob = 27,34842554331698275

Оцінка ластівки = 227.720728953415375

Арена 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

enter image description here

Арена 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

enter image description here

Арена 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

enter image description here

Арена 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

enter image description here

Ось суддя Сем Йонну з кількома змінами, щоб ви вказали файли та команду окремо:

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

Приклад 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']),
    ]

Примітка: Кожен, кому вдалося проковтнути Ластівку, отримає велику репутацію в 100. Будь ласка, публікуйте коментарі нижче, якщо вам це вдасться.


2
@githubphagocyte За запитом
TheNumberOne

1
Приємна робота з суддею змінюється. Окреме копіювання та команда файлів - це гарна ідея, і реєстрація помилок була дуже потрібна.
Логічний лицар

1
Якщо ви мали на увазі MAXTURNS, сміливо міняйте це. Це не є частиною правил. Це просто заважає судді працювати назавжди (але я думаю, що умови припинення це все-таки заважають).
Логічний лицар

1
Виправлено @githubphagocyte
TheNumberOne

1
Переглянувши ваші анімовані битви, я почав цікавитись, як виглядатиме битва Ластівка проти Ластівки. Чи один швидко вловлює іншого, чи це була б постійна боротьба за домінування в космосі?
Логічний лицар

6

Випадково, мова = java, оцінка = 0,43012126100275

Ця програма випадковим чином ставить пікселі на екран. Деякі (якщо не всі) пікселі будуть недійсними. Зі сторони, скласти більш швидку програму, ніж ця, має бути складно.

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

Арена 1:

1

Арена 2:

2

Арена 3:

3

Арена 4:

4


7
Я бачу, ви не потрапили в пастку передчасної оптимізації .
Логічний лицар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.