色のイメージバトル


33

最高のエントリーと@TheBestOne(優秀なスポーツマンシップ!)からの200バウンティを獲得するための@kuroinekoへのお祝い。

反対派のプログラムが行う前に、できるだけ多くの画像に色を付けるプログラムを書いてください。

簡単なルール

  • プログラムには、画像、色、整数Nが与えられます。
  • 各ターンには、他のプログラムからピクセルの更新が送信され、N個の更新を要求されます。
  • 色のピクセルの隣にある任意の白いピクセルを更新できます。
  • 最も多くのピクセルを追加したプログラムが勝ちます。

ルールの詳細

プログラムには、PNG画像のファイル名、ホームカラー、および数値Nが与えられます。数値Nは、プログラムが各ターンに色付けできるピクセルの最大数です。

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

入力画像は、辺が20〜1000ピクセルの長方形になります。黒、白、カラーのピクセルで構成されます。あなたのプログラムは白のシーケンスを選ぶかもしれません新しいピクセルごとに、自分の色の4つの隣接ピクセルのうち少なくとも1つを持たなければならないという条件で、ピクセルのして独自の色に。画像には最初、少なくとも1ピクセルの色があります。また、プログラムが割り当てられていない色のピクセルがある場合もあります。アルファチャネルは使用されません。

あなたの目標は、敵をブロックし、できるだけ多くのピクセルに色を書き込むことです。

各ターンは、STDINで1行以上のメッセージ行を受け入れ、STDOUTでピクセル座標で構成される行を書き込みます。STDOUTをバッファなしとして割り当てるか、毎回STDOUTバッファをフラッシュすることを忘れないでください。

各ターンに呼び出されるプレイヤーの順序はランダムに割り当てられます。これは、対戦相手(またはプログラム)が連続して2ターンを持っている可能性があることを意味します。

プログラムにはcolour (N,N,N) chose X,Y X,Y ... X,Y、プレーヤープログラムによって入力されたピクセルを説明する情報メッセージが送信されます。プレーヤーが移動を行わない場合、または有効な移動を行わない場合、そのプレーヤーの移動に関するメッセージは送信されません。プログラムには、受け入れられた独自の移動に関するメッセージも送信されます(少なくとも1つの有効な移動を指定した場合)。ピクセル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の平均)でタイトルを付けます。戦闘の写真やアニメーションもいいでしょう。勝者は、参照ボットに対して最高のスコアを獲得したプログラムです。

The Blobの勝ち方が簡単すぎると判明した場合は、より強力な参照相手と2回目のラウンドを追加することができます。

また、4つ以上のプレーヤープログラムを試すこともできます。回答として投稿された他のボットに対してボットをテストすることもできます。

裁判官

裁判官プログラムは、共通のPython Imaging Library(PIL)を必要とし、LinuxのOSパッケージマネージャーから簡単にインストールできる必要があります。PILはWindows 7の64ビットPythonでは動作しないという報告がありますので、このチャレンジを開始する前に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

戦いの例


これは[王様の王様]であってはいけませんか?
ジャスティン

私はそれについて考えました。ボットは互いに直接戦いません。彼らはリファレンスボットと戦います。それはKOTHを除外していますか?
ロジックナイト14

はい、これはKOTHではないので、お互いよりもリファレンスボットと戦うと確信しているかどうか尋ねました。
ジャスティン14

1
@ TheBestOne、Javaサポートを追加。ただし、Javaプログラムではテストされていません。うまくいかない場合は教えてください。
ロジックナイト14

1
10ピクセルは順番に配置されるため、後のピクセルは前のピクセル配置に依存する場合があります。あなたが提案するように、彼らはお互いに構築することができます。
ロジックナイト

回答:


17

ColorFighter-C ++-朝食に数人の嚥下者を食べる

編集

  • コードを整理しました
  • シンプルだが効果的な最適化を追加
  • いくつかのGIFアニメーションを追加しました

私はヘビが嫌いだ(彼らがクモであるふりをする、インディ)

実際、私はPythonが大好きです。私は怠け者ではなく、適切に学習し始めたらいいのに、それだけです。

これがすべて言われていると、私はこの蛇の64ビット版と格闘しなければならなかった。Win7で64ビットバージョンのPythonでPILを動作させるには、この課題に専念する準備ができていたよりも忍耐が必要なので、最終的に(苦痛なことに)Win32バージョンに切り替えました。

また、ボットが遅すぎて応答できない場合、ジャッジはひどくクラッシュする傾向があります。
Pythonに精通していないので修正しませんでしたが、stdinのタイムアウト後に空の回答を読み取ることに関係しています。

マイナーな改善は、各ボットのファイルにstderr出力を置くことです。これにより、事後デバッグのトレースが容易になります。

これらの小さな問題を除いて、私は裁判官が非常にシンプルで使いやすいと感じました。
さらに別の独創的で楽しいチャレンジに対する称賛。

コード

#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.cppLODEpng.hを使用してpng画像を読み取りました。
この遅延C ++言語に、6ダースのライブラリを構築せずに画像を読む方法を教える最も簡単な方法について。
メインとボブの叔父と一緒にLODEpng.cppをコンパイルしてリンクするだけです。

私はMSVC2013でコンパイルしましたが、いくつかのSTL基本コンテナー(dequeおよびvector)しか使用しなかったため、gccで動作する可能性があります(運がよければ)。
そうでない場合は、MinGWビルドを試すかもしれませんが、率直に言って、C ++の移植性の問題にうんざりしています。

私は日々多くのポータブルC / C ++を実行しました(さまざまな8から32ビットプロセッサ用のエキゾチックコンパイラ、SunOS、3.11からVistaまでのWindows、およびLinuxの初期からUbuntuのクーニングゼブラなどまで)私は移植性が何を意味するのかについてかなり良い考えを持っています)が、当時はSTLモンスターの不可解で肥大化した仕様のGNUとMicrosoftの解釈の間の無数の矛盾を記憶(または発見)する必要はありませんでした。

Swallowerに対する結果

arena1 arena2 arena3 arena4

使い方

基本的に、これは単純なブルートフォースフラッドフィルパスです。

プレイヤーの色のフロンティア(つまり、少なくとも1つの白い隣人を持つピクセル)は、古典的な距離フラッディングアルゴリズムを実行するためのシードとして使用されます。

ポイントが敵の色の近くに達すると、後方のパスが計算され、最も近い敵のスポットに向かって移動するピクセルのストリングが生成されます。

このプロセスは、必要な長さの応答に対して十分なポイントが収集されるまで繰り返されます。

この繰り返しは、特に敵の近くで戦っているとき、わいせつに高価です。
フロンティアから敵のピクセルにつながるピクセルのストリングが見つかるたびに(そして、答えを完了するためにより多くのポイントが必要です)、塗りつぶしは最初からやり直され、新しいパスがフロンティアに追加されます。つまり、10ピクセルの回答を得るには、5回以上塗りつぶす必要があります。

到達可能な敵ピクセルがなくなると、フロンティアピクセルの任意の隣接ピクセルが選択されます。
アルゴリズムはやや非効率的な洪水に陥りますが、これはゲームの結果が決定された後にのみ発生します(つまり、戦うべき中立の領域はもうありません)。
競争が処理された後、ジャッジがマップを埋める年齢を費やさないように最適化しました。現在の状態では、実行時間は裁判官自身と比較して無視できます。

敵の色は開始時にはわからないため、最初の移動時に敵の開始エリアをコピーするために、初期のアリーナ画像が保存されます。
コードが最初に再生される場合、いくつかの任意のピクセルが塗りつぶされます。

これにより、アルゴリズムは任意の数の敵、場合によってはランダムな時点で到着する新しい敵、または開始エリアなしで表示される色と戦うことができます(ただし、これは絶対に実用的ではありません)。

また、カラーごとの敵のハンドリングにより、ボットの2つのインスタンスが協力するようになります(ピクセル座標を使用して秘密の認識サインを渡す)。
楽しそうに聞こえますが、おそらく試してみます:)。

新しいデータが利用可能になるとすぐに(移動通知の後)、計算が多いパスが行われ、応答が与えられた直後に最適化(フロンティアの更新)が行われます(他のボットのターン中にできるだけ多くの計算を行うため) )。

ここでも、複数の敵がいる場合(新しいデータが利用可能になった場合に計算を中止するなど)、より微妙なことを行う方法がありますが、アルゴリズムがあれば、いずれにしてもマルチタスクが必要な場所がわかりません全負荷で作業することができます。

パフォーマンスの問題

これはすべて、高速データアクセスなしでは機能しません(また、Appoloプログラム全体よりも高い計算能力、つまり、数件のツイートを投稿する以上のことを行う場合の平均的なPC)。

速度はコンパイラに大きく依存します。通常、GNUはMicrosoftを30%のマージン(他の3つのパス関連のコードチャレンジで気づいた魔法の数)で打ち負かしますが、この距離はもちろん異なります。

現在の状態のコードは、アリーナ4の問題をほとんど解決しません。Windowsのパフォーマンスメーターは、CPU使用率を約4〜7%と報告するため、応答時間100ms以内で1000x1000マップに対応できるはずです。

ほぼすべてのパスアルゴリズムの中心にはFIFOがあり(その場合はそうではありませんが、有効化されている可能性があります)、これには高速な要素割り当てが必要です。

OPはアリーナサイズに制限を設定しているので、いくつかの計算を行い、最大(つまり1.000.000ピクセル)に固定されたデータ構造が数十メガバイトを超えないことを確認しました。
実際、Win7でMSVC 2013でコンパイルされたコードは、アリーナ4で約14Mbを消費しますが、Swallowerの2つのスレッドは20Mb以上を使用しています。

プロトタイプ作成を容易にするためにSTLコンテナーから始めましたが、STLはコードをさらに読みにくくしました。これは、難読化を隠すためにデータの各ビットをカプセル化するクラスを作成する必要がなかったためですSTLに対処することは読者の評価に任されています)。
とにかく、結果は非常にひどく遅かったため、最初は誤ってデバッグバージョンを作成していると思いました。

これは、MicrosoftのSTLの実装が信じられないほど貧弱であるため(たとえば、ベクターとビットセットがoperator []でバインドチェックまたはその他のクライピック操作を行う場合、仕様に直接違反するため)、一部はSTL設計によるものと考えられます自体。

パフォーマンスがあった場合、ひどい構文と移植性(つまりMicrosoft対GNU)の問題に対処できましたが、これは確かにそうではありません。

例えば、それdequeは本質的に遅いです。なぜなら、非常にスマートなサイズ変更を行う機会を待って、大量の簿記データをシャッフルするからです。
確かにカスタムアロケーターと他のカスタムテンプレートビットを実装できたかもしれませんが、カスタムアロケーターだけで数百行のコードがかかり、テストするのに1日の大半を費やします。手作りの同等の構造は約ゼロ行のコードです(より危険ではありますが、とにかく私が何をしていたのかわからない-または知っていたとしたら-アルゴリズムは機能しませんでした)。

そのため、最終的にコードの重要ではない部分にSTLコンテナーを保持し、1970年頃の2つのアレイと3つの符号なしショートで独自の残忍なアロケーターとFIFOを構築しました。

ツバメを飲み込む

作者が確認したように、Swallowerの不規則なパターンは、敵の移動の通知とパススレッドからの更新の遅れに起因します。
システムパフォーマンスメーターは、常に100%CPUを消費しているパススレッドを明確に示しており、戦闘の焦点が新しい領域に移動すると、ギザギザのパターンが表示される傾向があります。これは、アニメーションでも明らかです。

シンプルだが効果的な最適化

Swallowerと私の戦闘機との間の壮大な空中戦を見た後、囲oldのゲームからの古い格言を思い出しました。

その中に知恵があります。敵に固執しすぎると、考えられる各パスをブロックしようとする貴重な動きを無駄にします。それどころか、1ピクセルだけ離れていれば、ほとんど得られない小さな隙間を埋めることを避け、より重要な脅威に対抗するために自分の動きを使用するでしょう。

このアイデアを実装するために、私は単純に敵の動きを拡張しました(各動きの4人の隣人を敵ピクセルとしてマークします)。
これにより、敵の境界から1ピクセル離れたパスアルゴリズムが停止し、戦闘機があまりにも多くのドッグファイトに巻き込まれることなく敵を回避できるようになります。

改善を見ることができます
(すべての実行はそれほど成功していませんが、はるかに滑らかなアウトラインに気付くことができます):

前 後


1
ワオ。飲み込むものを打つものは何もないと思った。素晴らしい説明と優れたソリューション。私は古き良き時代のK&R Cを覚えていますが、Cは暗い側に行きました。Pythonが好きになると思います。
ロジックナイト

チャレンジに取り組むことは本当にうれしいことでした。これは私がLODEpngフルスケールのこの小さな宝石をテストすることができ、その結果はので、私は、この悪名高いポストインクリメントC.で再び私の愛/憎しみの関係をテストし、PNGレーサーを再訪かもしれないと期待されている

1
飲み込む人は、制限時間内に保つために、時々少し不安定になります。これは、部分的にマルチスレッドの目的です。よくやった!!ボーナスは2倍になると思います
...-TheNumberOne

1
Pillowには64ビットのダウンロードがあります。PILと同じように使用できます。
TheNumberOne

@TheBestOneそう思いました。私の残忍な画家は、あなたの嚥下者が古いデータをむさぼり食うこれらの瞬間を利用しています:)。PILに関しては、World Wide Webで利用可能な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


あなたのアルゴリズムは、Blobのより強力な兄弟Boxerで実装したものと同じです。Blobが十分なチャレンジではない場合、ボクサーを使用するつもりでした。とても素敵なアニメーションも。
ロジックナイト14

Python 3でPILを使用するには、pillowを使用していますか?
trichoplax

@githubphagocyteはい
-SamYonnou

これらのGIFの作成に使用したソフトウェアは何ですか?
TheNumberOne

1
@TheBestOne私はImageMagickを特にコマンドを使用しconvert -delay 5 -loop 0 result*.png animated.gifましたが、いくつかのgifは後で手動でここにアップロードするためにカットする必要があり
1

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

ここに画像の説明を入力してください

アリーナ2:

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

ここに画像の説明を入力してください

アリーナ3:

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

ここに画像の説明を入力してください

アリーナ4:

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

ここに画像の説明を入力してください

緑のツバメ対赤いブロブ

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

ここに画像の説明を入力してください

アリーナ2:

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

ここに画像の説明を入力してください

アリーナ3:

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

ここに画像の説明を入力してください

アリーナ4:

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

ここに画像の説明を入力してください

赤いツバメ対緑の深さの最初のブロブ

嚥下者のスコア= 157.0749775233111925

最初のブロブのスコアの深さ= 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

ここに画像の説明を入力してください

アリーナ2:

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

ここに画像の説明を入力してください

アリーナ3:

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

ここに画像の説明を入力してください

アリーナ4:

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

ここに画像の説明を入力してください

緑のツバメ対赤の深さの最初のブロブ

嚥下者のスコア= 154.3368355651281075

最初のブロブのスコアの深さ= 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

ここに画像の説明を入力してください

アリーナ2:

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

ここに画像の説明を入力してください

アリーナ3:

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

ここに画像の説明を入力してください

アリーナ4:

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

ここに画像の説明を入力してください

緑の塊と赤の深さ最初の塊と青の嚥下者:

Blobのスコア= 6.347962032393275525

深さ最初のブロブのスコア= 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

ここに画像の説明を入力してください

アリーナ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

ここに画像の説明を入力してください

アリーナ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

ここに画像の説明を入力してください

アリーナ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

ここに画像の説明を入力してください

ファイルとコマンドを個別に指定できるように、いくつかの変更を加えたSam Yonnouの裁判官を以下に示します。

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

注: 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
あなたは時期尚早な最適化の trapに陥っていないようです。
ロジックナイト14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.