색상의 이미지 전투


33

최고의 응모를 위해 @kuroineko에 축하를 전하고 @TheBestOne (우수한 스포츠맨 쉽)에서 200 현상금을 획득했습니다.

야당 프로그램보다 가능한 한 많은 이미지를 색칠하는 프로그램을 작성하십시오.

간단한 규칙

  • 프로그램에 이미지, 색상 및 정수 N이 제공됩니다.
  • 매 턴마다 다른 프로그램에 의해 픽셀 업데이트가 전송되고 N 업데이트를 요청합니다.
  • 색상의 픽셀 옆에있는 흰색 픽셀을 업데이트 할 수 있습니다.
  • 가장 많은 픽셀을 추가 한 프로그램이 승리합니다.

세부 규칙

프로그램에 PNG 이미지 파일 이름, 홈 컬러 및 숫자 N이 부여됩니다. 숫자 N은 프로그램이 매번 색상을 지정할 수있는 최대 픽셀 수입니다.

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

입력 이미지는 20에서 1000 픽셀 사이의면을 가진 사각형입니다. 검은 색, 흰색 및 컬러 픽셀로 구성됩니다. 프로그램은 각각의 새로운 픽셀이 자신의 색상으로 된 네 개의 인접 픽셀 중 하나 이상을 가져야하는 조건으로 자신 만의 색상으로 흰색 픽셀 시퀀스를 선택할 수 있습니다 . 이미지에는 처음에 컬러의 픽셀이 하나 이상 있습니다. 또한 프로그램이 지정되지 않은 색상의 픽셀이있을 수 있습니다. 알파 채널은 사용되지 않습니다.

당신의 목표는 상대방을 차단하고 가능한 한 많은 픽셀에 색상을 쓰는 것입니다.

매번 프로그램은 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

게시 점수

참조 상대 "Blob"이 제공됩니다. 각 답변에 대해 기준 상대에 대한 이름, 언어 및 점수 (평균 1-4)로 봇의 제목을 지정하십시오. 전투 중 하나의 그림이나 애니메이션도 좋습니다. 우승자는 참조 로봇에 대해 가장 높은 점수를받은 프로그램입니다.

Blob이 너무 쉽게 이길 수없는 경우, 더 강력한 참조 상대와 함께 두 번째 라운드를 추가 할 수 있습니다.

4 개 이상의 플레이어 프로그램을 시험해 볼 수도 있습니다. 답변으로 게시 된 다른 봇에 대해 봇을 테스트 할 수도 있습니다.

판사

판사 프로그램에는 일반적인 PIL (Python Imaging Library)이 필요하며 Linux의 OS 패키지 관리자에서 쉽게 설치할 수 있습니다. Windows 7에서 64 비트 Python에서 PIL이 작동하지 않는다는 보고서가 있으므로이 도전을 시작하기 전에 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를 배제합니까?
논리 기사

그렇습니다. 이것은 KOTH가 아닙니다. 서로보다 봇과 싸우고 싶었는지 확신하고있었습니다.
저스틴

1
@TheBestOne, Java 지원 추가. Java 프로그램으로 테스트되지 않았습니다. 작동하지 않는 경우 알려주십시오.
논리 기사

1
10 픽셀은 순서대로 배치되므로 이후의 픽셀은 이전 픽셀 배치에 의존 할 수 있습니다. 당신이 제안한대로 서로 구축 할 수 있습니다.
로직 나이트

답변:


17

ColorFighter-C ++-아침에 삼키기 두 개를 먹는다

편집하다

  • 코드를 정리
  • 간단하지만 효과적인 최적화 추가
  • GIF 애니메이션을 추가했습니다.

신 나는 뱀을 싫어한다 (그들은 거미 인 척, 인디)

사실 저는 파이썬을 좋아합니다. 나는 게으른 소년이 아니었고 제대로 배우기 시작했으면 좋겠다. 그게 다야.

이 모든 것을 말하면서, 나는이 뱀의 64 비트 버전으로 재판관이 일하기 위해 고투해야했다. Win7에서 64 비트 버전의 Python에서 PIL을 작동 시키려면이 과제에 전념 할 준비가 된 것보다 더 많은 인내심이 필요하므로 결국 (고통 적으로) Win32 버전으로 전환했습니다.

또한 봇이 응답하기에 너무 느리면 판사가 심하게 충돌하는 경향이 있습니다.
파이썬에 정통하지 않기 때문에 수정하지는 않았지만 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를 컴파일하고 메인과 Bob의 아저씨와 함께 연결하십시오.

MSVC2013으로 컴파일했지만 소수의 STL 기본 컨테이너 (데크 및 벡터) 만 사용했기 때문에 gcc (운이 좋으면)와 함께 작동 할 수 있습니다 .
그렇지 않다면 MinGW 빌드를 시도 할 수도 있지만 솔직히 C ++ 이식성 문제에 질려 있습니다.

필자는 요즘 꽤 많은 휴대용 C / C ++ (다양한 8 ~ 32 비트 프로세서 용 이국적인 컴파일러와 SunOS, Windows 3.11 ~ Vista 및 Linux부터 유아기부터 우분투 쿠밍 지브라 또는 기타 모든 것에 대해 그렇게 생각했습니다. 나는 이식성이 무엇을 의미하는지 잘 알고 있지만, 당시에는 STL 몬스터의 암호화 및 부풀린 스펙에 대한 GNU와 Microsoft의 해석 사이의 수많은 불일치를 암기하거나 발견 할 필요가 없었습니다.

삼키는 사람에 대한 결과

경기장 1 경기장 2 경기장 3 경기장 4

작동 원리

핵심적으로 이것은 단순한 무차별 범람 경로입니다.

플레이어의 색상의 경계 (즉, 하나 이상의 흰색 이웃이있는 픽셀)는 전형적인 거리 홍수 알고리즘을 수행하기위한 시드로 사용됩니다.

점이 적 색상의 주변에 도달하면 가장 가까운 적 지점을 향해 이동하는 일련의 픽셀을 생성하기 위해 역방향 경로가 계산됩니다.

원하는 길이의 응답에 대해 충분한 포인트가 수집 될 때까지 프로세스가 반복됩니다.

이 반복은 특히 적 근처에서 싸울 때 외설적으로 비쌉니다.
프론티어에서 적의 픽셀로 이어지는 일련의 픽셀이 발견 될 때마다 (그리고 답을 완성하기 위해 더 많은 포인트가 필요함), 새로운 경로가 프론티어에 추가 된 상태에서 플러드 필이 다시 시작됩니다. 즉, 10 픽셀의 답변을 얻으려면 5 번 이상의 플러드 채우기를 수행해야 할 수 있습니다.

더 이상 적 픽셀에 도달 할 수없는 경우, 국경 픽셀의 인접 이웃이 선택됩니다.
알고리즘은 다소 비효율적 인 플러드 필 (flood fill)로 진행되지만, 이는 게임의 결과가 결정된 후에 만 ​​발생합니다 (즉, 더 이상 중립 영역을두고 싸울 수 없습니다).
대회가 처리 된 후 판사가지도를 작성하는 데 시간이 걸리지 않도록 최적화했습니다. 현재 상태에서, 실행 시간은 판사 자체와 비교하여 무시할 수 있습니다.

적의 색은 시작시 알려지지 않았기 때문에, 최초 경기장 이미지는 적의 시작 영역을 처음 이동할 때 복사하기 위해 저장되어 있습니다.
코드가 먼저 재생되면 몇 개의 임의 픽셀이 채워집니다.

이것은 알고리즘이 임의의 수의 적과 싸울 수있게하며, 심지어 임의의 특정 시점에 도착하는 새로운 적, 또는 시작 영역없이 나타나는 색상 (실제로 사용되지는 않지만)입니다.

색상별로 적을 처리하면 봇의 두 인스턴스가 협력 할 수 있습니다 (비밀 인식 부호를 전달하기 위해 픽셀 좌표 사용).
재미있을 것 같아요. 아마도 시도해 볼 것입니다 :).

새로운 데이터를 사용할 수있게되면 (이동 알림 후) 계산이 많은 경로 지정이 수행되고, 응답이 제공된 직후에 다른 최적화 (프론티어 업데이트)가 수행됩니다 (다른 봇 회전 중에 가능한 한 많은 계산 수행). ).

여기서 다시 말하지만, 새로운 데이터를 사용할 수있게되면 계산 중단과 같이 1 명 이상의 적이있는 경우 더 미묘한 작업을 수행하는 방법이있을 수 있지만, 알고리즘이있는 한 멀티 태스킹이 필요한 위치를 알 수는 없습니다. 완전 부하에서 작동 할 수 있습니다.

성능 문제

이 모든 것은 빠른 데이터 액세스 (및 전체 Appolo 프로그램보다 더 많은 컴퓨팅 성능, 즉 몇 개의 트윗을 게시하는 데 사용되는 평균 PC) 없이는 작동하지 않습니다.

속도는 컴파일러에 따라 크게 다릅니다. 일반적으로 GNU는 30 %의 마진 (마법의 다른 세 가지 경로 관련 코드 문제에서 눈에 띄는 수치)보다 Microsoft를 능가하지만이 마일리지는 다를 수 있습니다.

현재 상태의 코드는 경기장 4에서 거의 땀을 흘리지 않습니다. Windows 성능 계는 약 4-7 %의 CPU 사용량을보고하므로 100ms 응답 시간 제한 내에서 1000x1000 맵에 대처할 수 있어야합니다.

모든 경로 지정 알고리즘의 핵심에는 FIFO (이 경우는 아니지만 가능)가 있으며 빠른 요소 할당이 필요합니다.

OP는 의무적으로 경기장 크기에 한계를 설정했기 때문에 몇 가지 수학을 수행했으며 최대 (예 : 1.000.000 픽셀) 크기의 고정 데이터 구조가 수십 메가 바이트 이상을 소비하지 않으므로 평균 PC가 아침 식사로 먹는다는 것을 알았습니다.
실제로 Win7에서 MSVC 2013으로 컴파일 된 코드는 경기장 4에서 약 14Mb를 소비하는 반면 Swallower의 두 스레드는 20Mb 이상을 사용합니다.

더 쉬운 프로토 타이핑을 위해 STL 컨테이너로 시작했지만 STL은 코드를 읽기 어렵게 만들었습니다. 왜냐하면 난독 화를 숨길 수있는 각 데이터 비트를 캡슐화하는 클래스를 만들고 싶지 않았기 때문입니다. STL에 대처하는 것은 독자에게 감사의 마음을 전합니다).
어쨌든 결과는 너무 심하게 느려서 처음에는 실수로 디버그 버전을 작성한다고 생각했습니다.

나는 이것이 부분적으로 STL의 매우 나쁜 Microsoft 구현 (예를 들어, 벡터 및 비트 세트가 사양을 직접 위반하여 연산자 []에 대한 바운드 검사 또는 기타 crypic 작업을 수행하는 경우) 및 부분적으로 STL 디자인 때문이라고 생각합니다. 그 자체.

퍼포먼스가 있다면 끔찍한 구문과 이식성 (즉, Microsoft vs GNU) 문제에 대처할 수 있지만, 반드시 그렇지는 않습니다.

예를 들어, deque매우 스마트 한 크기 조정을 할 때까지 기다리는 동안 많은 부기 데이터를 섞어서 본질적으로 속도가 느립니다.
물론 사용자 지정 할당 자와 다른 사용자 지정 템플릿 비트를 구현할 수는 있지만 사용자 지정 할당 자만으로도 수백 줄의 코드와 테스트하는 데 더 좋은 하루가 소요됩니다. 수제 등가 구조는 0 줄의 코드에 관한 것입니다.

결국 STL 컨테이너를 코드의 중요하지 않은 부분에 보관하고 1970 년 2 개의 배열과 3 개의 부호없는 반바지로 잔인한 할당 자와 FIFO를 만들었습니다.

삼키기 삼키기

저자가 확인한 바와 같이, 삼키는 장치의 불규칙한 패턴은 적의 이동 알림과 경로 스레드의 업데이트 사이의 지연으로 인해 발생합니다.
시스템 성능 계는 항상 100 % CPU를 소비하는 경로 스레드를 명확하게 보여 주며, 전투의 초점이 새로운 영역으로 이동하면 들쭉날쭉 한 패턴이 나타나는 경향이 있습니다. 이것은 애니메이션에서도 분명합니다.

간단하지만 효과적인 최적화

Swallower와 전투기 사이의 장대 한 도그 파이트를보고 난 후 Go 게임에서 한 말을 기억했습니다.

그것에 지혜가 있습니다. 당신이 적을 너무 고집하려고하면, 당신은 각각의 가능한 길을 막으려 고 노력하는 소중한 움직임을 낭비 할 것입니다. 반대로, 한 픽셀 만 떨어져 있으면 아주 작은 간격을 채우지 않아도되고보다 중요한 위협에 대응하기 위해 자신의 움직임을 사용하게 될 것입니다.

이 아이디어를 구현하기 위해 간단히 적의 움직임을 확장했습니다 (각 움직임의 4 개 이웃을 적의 픽셀로 표시).
이렇게하면 적의 경계에서 1 픽셀 떨어진 경로 알고리즘이 중지되어 파이터가 너무 많은 공중전을 치르지 않고 적 주위를 돌아 다닐 수 있습니다.

개선 사항을 볼 수 있습니다
(모든 실행이 성공적이지는 않지만 훨씬 더 부드러운 윤곽선을 볼 수 있습니다).

전에 후


1
와우. 나는 삼키는 사람을 이길 수 없다고 생각했습니다. 훌륭한 설명과 함께 탁월한 솔루션. 나는 옛날부터 K & R C를 기억하지만 C는 어두운쪽으로 갔다. 나는 당신이 파이썬 을 좋아할 것이라고 생각합니다 .
논리 기사

도전을 해결하는 것이 정말 즐거웠습니다. 이를 통해 LODEpng 풀 스케일의이 작은 보석을 테스트 할 수 있었으며, 결과는 너무 유망하여 png 경주자를 다시 방문하여이 악명 높은 포스트 증가한 C와의 내 사랑 / 증오 관계를 다시 테스트 할 수 있습니다.

1
제한 시간 내에 유지하기 위해 삼키는 사람은 때때로 약간 불규칙합니다. 이것은 부분적으로 멀티 스레딩을위한 것입니다. 잘 했어!! 보너스를 두 배로 늘릴 것 같습니다 ...
TheNumberOne

1
베개 에는 64 비트 용 다운로드가 있습니다. PIL처럼 사용할 수 있습니다.
TheNumberOne

@ TheBestOne 나는 그렇게 생각했다. 내 잔인한 화가는 삼키는 사람이 오래된 데이터를 찌르는 순간을 활용합니다. :). PIL의 경우 월드 와이드 웹에서 사용할 수있는 모든 Amd64 PIL 및 필로우 버전에 대해 다운로드했지만 핵심 63.5 비트 파이썬에서는 작동하지 않을 것입니다. 파이썬은 아마도 부트 레그 및 / 또는 오래된 버전 일 것입니다. 어쨌든 Win32 포트도 잘 작동하며 언젠가 더 빠른 것이 필요하면 PyPy로 전환해야합니다.

21

깊이 우선 블롭 vs. 블롭

언어 = 파이썬 (3.2)

점수 = 111.475388276 153.34210035

업데이트 : 이제 사용자 정의 Set클래스를 사용 하여 pop()메소드 를 가져 와서 일종의 그리드 패턴을 생성하여 처음에 덮힌 영역을 크게 향상시켜 적의 이미지를 잘라냅니다. 참고 : 나는 그리드 크기의 임의 샘플 중 arena3 (업데이트 전에 최악의 점수를 얻은 것)에 가장 적합한 결과를 보인 12 x 12 그리드를 사용하고 있지만 더 최적 일 가능성이 큽니다. 주어진 경기장 선택에 대한 격자 크기가 존재합니다.

가능한 한 자체 색상 포인트가 적은 실행 가능한 포인트를 선택하는 것을 선호하도록 참조 봇을 간단하게 수정했습니다. 개선은 가능한 한 많은 적 색 포인트로 둘러싸인 실행 가능한 포인트를 선택하는 것을 선호하게 할 수 있습니다.

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

삼

아레나 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이 충분히 도전하지 않으면 Boxer를 사용하려고했습니다. 아주 좋은 애니메이션도 있습니다.
논리 기사

파이썬 3에서 PIL을 사용하려면 베개 를 사용하고 있습니까?
trichoplax

@githubphagocyte 예
SamYonnou

GIF를 만들기 위해 어떤 소프트웨어를 사용하셨습니까?
TheNumberOne

1
@TheBestOne ImageMagick 명령을 사용 convert -delay 5 -loop 0 result*.png animated.gif했습니다. gif 중 일부는 나중에 수동으로 잘라야 여기에 업로드됩니다
SamYonnou

18

삼키는 사람

언어 = 자바

점수 = 162.3289512601408075 169.4020975612382575

적을 찾아 주위를 둘러싼 다. 더 긴 시간 제한을 두어야 할 수도 있습니다. 상당히 개선 될 수 있습니다. 때때로 유효하지 않은 픽셀을 인쇄합니다.

업데이트 : 훨씬 빠르게 둘러 쌉니다. 다른 스레드를 사용하여 우선 순위를 업데이트합니다. 항상 0.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 픽셀이 우선 순위 대기열에서 튀어 나옵니다.

그린 블롭 및 레드 스왈로

물방울 점수 = 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

여기에 이미지 설명을 입력하십시오

녹색 삼키기 vs. 레드 블롭

물방울 점수 = 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

여기에 이미지 설명을 입력하십시오

레드 삼키기 vs 녹색 깊이 첫 얼룩

삼키기 점수 = 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

여기에 이미지 설명을 입력하십시오

녹색 삼키기 vs 레드 뎁스 퍼스트 블롭

삼키기 점수 = 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

여기에 이미지 설명을 입력하십시오

그린 블롭 vs 레드 뎁스 퍼스트 블롭 vs 블루 삼키기 :

물방울 점수 = 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']),
    ]

참고 : 제비를 삼키는 사람은 누구나 100의 명성을 얻습니다. 이 작업에 성공하면 아래 의견을 적어주십시오.


2
@githubphagocyte 요청에 따라.
TheNumberOne

1
판사와의 좋은 작업 변경. 별도의 파일 복사 및 명령을 사용하는 것이 좋으며 오류 로깅이 필요했습니다.
논리 기사

1
MAXTURNS를 의미 한 경우 자유롭게 변경하십시오. 규칙의 일부가 아닙니다. 판사가 영원히 달리는 것을 막을뿐입니다 (단, 종료 조건이이를 막을 수 있다고 생각합니다).
논리 기사

1
@githubphagocyte 고정
TheNumberOne

1
애니메이션 전투를보고 난 후 Swallower vs Swallower 전투가 어떻게 보일지 궁금해지기 시작했습니다. 하나는 다른 쪽을 빨리 잡을 것인가, 아니면 공간 지배를위한 끊임없는 싸움 일까?
논리 기사

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 :

삼

아레나 4 :

4


7
나는 당신이 조기 최적화 의 함정에 빠지지 않았다 참조하십시오 .
논리 기사
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.