PARABÉNS a @kuroineko pela melhor participação e pela conquista de 200 prêmios da @TheBestOne (excelente espírito esportivo!).
Escreva um programa para colorir o máximo possível de uma imagem antes dos programas da oposição.
Regras breves
- Seu programa receberá uma imagem, sua cor e número inteiro N.
- A cada turno, você recebe atualizações de pixels de outros programas e solicita suas N atualizações.
- Você pode atualizar qualquer pixel branco próximo a um pixel da sua cor.
- O programa que adicionou mais pixels vence.
Regras em detalhe
Seu programa receberá um nome de arquivo da imagem PNG, cor da casa e um número N. O número N é o número máximo de pixels que seu programa pode colorir a cada turno.
Exemplo: MyProg arena.png (255,0,0) 30
A imagem de entrada será um retângulo com lados entre 20 e 1000 pixels de comprimento. Ele consistirá em pixels preto, branco e colorido. Seu programa pode escolher uma sequência de pixels brancos para colorir como sua, com a condição de que cada novo pixel tenha pelo menos um dos quatro pixels vizinhos da sua própria cor. A imagem terá inicialmente pelo menos um pixel da sua cor. Também pode ter pixels de cores aos quais nenhum programa está atribuído. O canal alfa não é usado.
Seu objetivo é bloquear seus oponentes e escrever sua cor no máximo de pixels possível.
A cada turno, seu programa aceita 1 ou mais linhas de mensagem no STDIN e escreve uma linha que consiste em coordenadas de pixel no STDOUT. Lembre-se de atribuir STDOUT como sem buffer ou liberar o buffer STDOUT a cada turno.
A ordem dos jogadores chamados a cada turno será atribuída aleatoriamente. Isso significa que um oponente (ou seu programa) pode ter 2 turnos seguidos.
Seu programa receberá colour (N,N,N) chose X,Y X,Y ... X,Y
mensagens informativas que descrevem os pixels preenchidos pelos programas do player. Se um jogador não fizer jogadas, ou nenhuma jogada válida, você não receberá uma mensagem sobre as jogadas desse jogador. Seu programa também receberá uma mensagem sobre seus próprios movimentos aceitos (se você especificou pelo menos um movimento válido). O pixel 0,0 está no canto superior esquerdo da imagem.
Ao receber pick pixels
, seu programa produzirá X,Y X,Y ... X,Y
até N pixels (é permitida uma string vazia que consiste apenas em '\ n'). Os pixels devem estar em ordem de plotagem. Se um pixel for inválido, ele será ignorado e não estará no relatório para os jogadores. Seu programa tem 2 segundos para inicializar após o início, mas apenas 0,1 segundo para responder com uma resposta a cada turno ou ele perderá esse turno. Uma atualização de pixel enviada após 0,1 segundo registrará uma falha. Após 5 falhas, seu programa é suspenso e não receberá atualizações ou pick pixels
solicitações.
Quando o programa do juiz recebe uma opção de pixel vazio ou inválido de cada programa de jogador não suspenso, a imagem será considerada concluída e os programas receberão a mensagem "exit". Os programas devem terminar após receber "exit".
Pontuação
O juiz marcará pontos após a conclusão da imagem. Sua pontuação será o número de pixels atualizados dividido pela captura média de pixels nessa rodada, expressa em porcentagem.
O número de pixels adicionados à imagem pelo seu player é A. O número total de pixels adicionados por todos os P players é T.
avg = T/P
score = 100*A/avg
Postar pontuações
Um oponente de referência "The Blob" é dado. Para cada resposta, nomeie seu bot com um nome, idioma e sua pontuação (média de arena 1 a 4) contra o oponente de referência. Uma imagem ou animação de uma de suas batalhas também seria boa. O vencedor é o programa com a maior pontuação em relação ao bot de referência.
Se o Blob for muito fácil de derrotar, posso adicionar um segundo round com um oponente de referência mais forte.
Você também pode experimentar 4 ou mais programas de player. Você também pode testar seu bot em relação a outros bots publicados como respostas.
O juiz
O programa de juízes requer a PIL (Python Imaging Library) comum e deve ser fácil de instalar a partir do gerenciador de pacotes do SO no Linux. Tenho um relatório de que o PIL não funciona com o Python de 64 bits no Windows 7; portanto, verifique se o PIL funcionará para você antes de iniciar esse desafio (atualizado em 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')
Exemplo de configuração - cfg.py
BATTLE = 'Green Blob vs Red Blob'
MAXTURNS = 20000
PIXELBATCH = 10
INITTIME = 2.0
TIMELIMIT = 0.1
FAULTLIMIT = 5
imagename = 'arena1.png'
colourspec = (0,255,0), (255,0,0)
botspec = [
('blob.py', 'python'),
('blob.py', 'python'),
]
The Blob - o oponente de referência
# Blob v1.0 - A reference opponent for the Image Battle challenge on PPCG.
import sys, os
from PIL import Image
image = Image.open(sys.argv[1])
pix = image.load()
W,H = image.size
mycolour = eval(sys.argv[2])
pixbatch = int(sys.argv[3])
ORTH = ((-1,0), (1,0), (0,-1), (0,1))
def canchoose(loc, colour):
if pix[loc] == (255,255,255):
plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
if any(pix[p]==colour for p in plist if 0<=p[0]<W and 0<=p[1]<H):
return True
return False
def near(loc):
plist = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
pboard = [p for p in plist if 0<=p[0]<W and 0<=p[1]<H]
return [p for p in pboard if pix[p] == (255,255,255)]
def updateimage(image, msg):
ctext, colourtext, chose, points = msg.split(None, 3)
colour = eval(colourtext)
plist = [tuple(int(v) for v in pr.split(',')) for pr in points.split()]
for p in plist:
pix[p] = colour
skin.discard(p)
if colour == mycolour:
for np in near(p):
skin.add(np)
board = [(x,y) for x in range(W) for y in range(H)]
skin = set(p for p in board if canchoose(p, mycolour))
while 1:
msg = sys.stdin.readline()
if msg.startswith('colour'):
updateimage(image, msg.strip())
if msg.startswith('pick'):
plen = min(pixbatch, len(skin))
moves = [skin.pop() for i in range(plen)]
movetext = ' '.join('%u,%u'%p for p in moves)
sys.stdout.write(movetext + '\n')
sys.stdout.flush()
if msg.startswith('exit'):
break
image.save('blob.png')
Arena 1
Arena 2
Arena 3
Arena 4
Um exemplo de batalha - Blob vs Blob
Essa batalha teve um resultado previsível:
Bot blob.py with colour (255, 0, 0) scored 89.2883333333
Bot blob.py with colour (0, 255, 0) scored 89.365
fonte
Respostas:
ColorFighter - C ++ - come dois engolidores no café da manhã
EDITAR
Deus, eu odeio cobras (apenas finja que são aranhas, Indy)
Na verdade, eu amo Python. Eu gostaria de ser menos um garoto preguiçoso e comecei a aprender direito, só isso.
Tudo isso dito, eu tive que lutar com a versão de 64 bits desta cobra para fazer o juiz funcionar. Fazer o PIL funcionar com a versão de 64 bits do Python no Win7 requer mais paciência do que eu estava pronto para me dedicar a esse desafio; portanto, no final, mudei (dolorosamente) para a versão do Win32.
Além disso, o juiz tende a falhar muito quando um bot é muito lento para responder.
Como não sou experiente em Python, não o corrigi, mas tem a ver com a leitura de uma resposta vazia após um tempo limite no stdin.
Uma pequena melhoria seria colocar a saída stderr em um arquivo para cada bot. Isso facilitaria o rastreamento para depuração post-mortem.
Exceto por esses pequenos problemas, achei o juiz muito simples e agradável de usar.
Parabéns por mais um desafio inventivo e divertido.
O código
Construindo o executável
Eu usei LODEpng.cpp e LODEpng.h para ler imagens png.
Sobre a maneira mais fácil que encontrei para ensinar a essa linguagem C ++ retardada como ler uma imagem sem precisar criar meia dúzia de bibliotecas.
Basta compilar e vincular o LODEpng.cpp junto com o principal e Bob é seu tio.
Compilei com o MSVC2013, mas como usei apenas alguns contêineres básicos STL (deque e vetores), ele pode funcionar com o gcc (se você tiver sorte).
Se isso não acontecer, eu poderia tentar uma compilação MinGW, mas, sinceramente, eu estou ficando cansado de problemas de portabilidade C ++.
Eu fiz bastante C / C ++ portátil nos meus dias (em compiladores exóticos para vários processadores de 8 a 32 bits, bem como SunOS, Windows desde 3.11 até Vista e Linux desde a sua infância até o Ubuntu arremessando zebra ou qualquer outra coisa, então eu acho Eu tenho uma boa idéia do que significa portabilidade), mas na época não era necessário memorizar (ou descobrir) as inúmeras discrepâncias entre as interpretações GNU e Microsoft das especificações enigmáticas e inchadas do monstro STL.
Resultados contra Swallower
Como funciona
No fundo, trata-se de um simples caminho de aterro de força bruta.
A fronteira da cor do jogador (ou seja, os pixels que têm pelo menos um vizinho branco) é usada como uma semente para executar o algoritmo clássico de inundação à distância.
Quando um ponto atinge a proximidade de uma cor inimiga, um caminho para trás é calculado para produzir uma sequência de pixels se movendo em direção ao ponto inimigo mais próximo.
O processo é repetido até que pontos suficientes tenham sido reunidos para uma resposta do comprimento desejado.
Essa repetição é obscenamente cara, especialmente quando lutamos perto do inimigo.
Cada vez que uma seqüência de pixels que leva da fronteira para um pixel inimigo é encontrada (e precisamos de mais pontos para concluir a resposta), o preenchimento é refeito desde o início, com o novo caminho adicionado à fronteira. Isso significa que você pode ter que fazer 5 preenchimentos ou mais para obter uma resposta de 10 pixels.
Se não houver mais pixels inimigos disponíveis, vizinhos arbitrários dos pixels da fronteira serão selecionados.
O algoritmo se transforma em um preenchimento bastante ineficiente, mas isso só acontece depois que o resultado do jogo é decidido (ou seja, não há mais território neutro pelo qual lutar).
Eu o otimizei para que o juiz não passasse anos preenchendo o mapa depois que a competição fosse resolvida. Em seu estado atual, o tempo de execução é negligenciável em comparação com o próprio juiz.
Como as cores inimigas não são conhecidas no início, a imagem inicial da arena é mantida na loja para copiar as áreas iniciais do inimigo quando ele faz seu primeiro movimento.
Se o código for reproduzido primeiro, ele simplesmente preencherá alguns pixels arbitrários.
Isso torna o algoritmo capaz de combater um número arbitrário de adversários, e até possivelmente novos adversários que chegam em um ponto aleatório no tempo, ou cores que aparecem sem uma área inicial (embora isso não tenha absolutamente nenhum uso prático).
O manuseio do inimigo em uma base de cor por cor também permitiria que duas instâncias do bot cooperassem (usando coordenadas de pixel para passar um sinal de reconhecimento secreto).
Parece divertido, provavelmente vou tentar isso :).
O processamento pesado de computação é feito assim que novos dados estão disponíveis (após uma notificação de movimentação) e algumas otimizações (a atualização da fronteira) são feitas logo após uma resposta ter sido dada (para fazer o máximo de computação possível durante os outros turnos de bots )
Aqui, novamente, poderia haver maneiras de fazer coisas mais sutis se houvesse mais de um adversário (como interromper uma computação se novos dados se tornarem disponíveis), mas, de qualquer forma, não consigo ver onde a multitarefa é necessária, desde que o algoritmo seja capaz de trabalhar em carga máxima.
Problemas de desempenho
Tudo isso não pode funcionar sem acesso rápido aos dados (e mais poder computacional do que todo o programa Appolo, ou seja, seu PC comum, quando fazia mais do que postar alguns tweets).
A velocidade é fortemente dependente do compilador. Normalmente, o GNU vence a Microsoft com uma margem de 30% (esse é o número mágico que notei em outros 3 desafios relacionados a códigos), mas essa milhagem pode variar, é claro.
O código em seu estado atual mal suou na arena 4. O perfmeter do Windows relata cerca de 4 a 7% de uso da CPU, portanto, deve ser capaz de lidar com um mapa de 1000x1000 dentro do prazo de resposta de 100ms.
No cerne de quase todos os algoritmos de caminho encontra-se um FIFO (possivelmente proritizado, embora não nesse caso), que por sua vez requer uma alocação rápida de elementos.
Como o OP obrigatoriamente estabeleceu um limite para o tamanho da arena, fiz algumas contas e vi que as estruturas de dados fixas dimensionadas para o máximo (ou seja, 1.000.000 pixels) não consumiriam mais do que algumas dúzias de megabytes, que o seu PC comum come no café da manhã.
De fato, no Win7 e compilado com o MSVC 2013, o código consome cerca de 14Mb na arena 4, enquanto os dois threads do Swallower estão usando mais de 20Mb.
Comecei com os contêineres STL para facilitar a criação de protótipos, mas o STL tornou o código ainda menos legível, pois não desejava criar uma classe para encapsular cada bit de dados para ocultar a ofuscação (seja devido às minhas próprias inabilidades). lidar com o STL é deixado à apreciação do leitor).
Independentemente disso, o resultado foi tão atrozmente lento que a princípio pensei que estava criando uma versão de depuração por engano.
Eu acho que isso se deve em parte à implementação incrivelmente ruim da STL da Microsoft (onde, por exemplo, vetores e conjuntos de bits fazem verificações vinculadas ou outras operações criptográficas no operador [], em violação direta das especificações) e em parte ao design da STL em si.
Eu poderia lidar com as questões atrozes de sintaxe e portabilidade (por exemplo, Microsoft vs GNU) se as performances estivessem presentes, mas esse certamente não é o caso.
Por exemplo,
deque
é inerentemente lento, porque embaralha muitos dados da contabilidade aguardando a ocasião para fazer seu redimensionamento super inteligente, sobre o qual eu não poderia me importar menos.Claro que eu poderia ter implementado um alocador personalizado e o que outros bits de modelo personalizados, mas um alocador personalizado sozinho custa algumas centenas de linhas de código e a maior parte do dia para testar, com a dúzia de interfaces que ele precisa implementar, enquanto um A estrutura equivalente artesanal tem cerca de zero linhas de código (embora mais perigosa, mas o algoritmo não funcionaria se eu não soubesse - ou pense que soubesse - o que estava fazendo de qualquer maneira).
Por fim, mantive os contêineres da STL em partes não críticas do código e construí meu próprio alocador brutal e FIFO com duas matrizes de cerca de 1970 e três shorts não assinados.
Engolir o engolidor
Como o autor confirmou, os padrões irregulares do Swallower são causados por atrasos entre notificações e atualizações dos movimentos do inimigo a partir do fio do caminho.
O medidor de desempenho do sistema mostra claramente o segmento de processamento que consome 100% da CPU o tempo todo, e os padrões irregulares tendem a aparecer quando o foco da luta muda para uma nova área. Isso também é bastante aparente nas animações.
Uma otimização simples, mas eficaz
Depois de observar as épicas brigas de cães entre Swallower e meu lutador, lembrei-me de um velho ditado do jogo Go: defender de perto, mas atacar de longe.
Há sabedoria nisso. Se você tentar se ater ao seu adversário demais, desperdiçará movimentos preciosos tentando bloquear cada caminho possível. Pelo contrário, se você ficar a apenas um pixel de distância, provavelmente evitará preencher pequenas lacunas que ganhariam muito pouco e usará seus movimentos para combater ameaças mais importantes.
Para implementar essa idéia, eu simplesmente estendi os movimentos de um inimigo (marcando os 4 vizinhos de cada movimento como um pixel inimigo).
Isso interrompe o algoritmo de desvio a um pixel da fronteira do inimigo, permitindo que meu lutador contorne um adversário sem ser pego em muitas brigas de cães.
Você pode ver a melhoria
(embora todas as execuções não sejam tão bem-sucedidas, você pode observar os contornos muito mais suaves):
fonte
Primeiro blob de profundidade vs. Blob
Idioma = Python (3.2)
Pontuação =
111.475388276153.34210035Atualização: agora, usando uma
Set
classe personalizada para obter opop()
método para produzir um tipo de padrão de grade que melhora drasticamente a área coberta no início, cortando grandes partes da imagem do inimigo. Nota: Estou usando uma grade 12 x 12 para isso, que de uma amostra aleatória de tamanhos de grade parecia dar os melhores resultados para o arena3 (aquele que obteve a pior pontuação antes da atualização), no entanto, é muito provável que um melhor O tamanho da grade existe para a seleção de arenas.Fiz uma modificação simples no bot de referência para torná-lo favorável à escolha de pontos viáveis, limitados pelo menor número possível de pontos de cor. Uma melhoria pode ser fazer com que ele também escolha pontos viáveis que sejam limitados pelo maior número possível de pontos inimigos.
dfblob.py:
O juiz original foi ligeiramente modificado para trabalhar com o Python 3.2 (e para adicionar uma funcionalidade de registro bruto aos bots + salvar a imagem da arena periodicamente para criar animação):
Os resultados da arena seguem. O bot dfblob recebeu a cor vermelha em todas as arenas.
Arena 1:
Arena 2:
Arena 3:
Arena 4:
fonte
convert -delay 5 -loop 0 result*.png animated.gif
embora alguns dos gifs teve que ser posteriormente cortada manualmente para baixo para ser carregado aquiAndorinha
Idioma = Java
Pontuação =
162.3289512601408075169.4020975612382575Procura inimigos e rodeia.
Você pode ter que dar um prazo mais longo. Pode ser melhorado um pouco. Às vezes, imprime pixels inválidos.Atualização: envolve muito mais rápido. Usa outro thread para atualizar prioridades. Sempre retorna dentro de 0,1 segundos. Pontuação deve ser impossível de bater sem aumentar
MAX_TURNS
.Como funciona:
Este bot mantém uma fila prioritária de pixels que pode ser adicionada. A prioridade de um pixel inimigo é 0. A prioridade de um pixel em branco é 1 maior que a menor prioridade ao seu redor. Todos os outros pixels têm uma prioridade Integer.MAX_VALUE. O encadeamento do atualizador está atualizando constantemente as prioridades dos pixels. A cada turno, os N pixels mais baixos são disparados da fila de prioridade.
Green Blob vs Red Swallower
Pontuação do Blob = 1.680553372583887225
Pontuação da andorinha = 169.4020975612382575
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Swallower verde vs. Red Blob
Pontuação do Blob = 1.6852943642218457375
Pontuação da andorinha = 169.3923095387498625
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Red Swallower vs Green Depth First Blob
Pontuação da andorinha = 157.0749775233111925
Pontuação do primeiro blob de profundidade = 18.192783547939744
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Swallower verde vs profundidade vermelha primeiro blob
Pontuação da andorinha = 154.3368355651281075
Pontuação do primeiro blob de profundidade = 18,84463249420435425
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Green Blob vs Red Depth Primeiro Blob vs Blue Swallower:
Pontuação do Blob = 6.347962032393275525
Pontuação do primeiro blob de profundidade = 27.34842554331698275
Pontuação da andorinha = 227.720728953415375
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Aqui está o juiz de Sam Yonnou com algumas alterações para que você especifique os arquivos e o comando separadamente:
Exemplo cfg:
Nota: Qualquer um que consiga engolir o Swallower recebe uma recompensa de 100 reputação. Por favor, poste nos comentários abaixo se você conseguir isso.
fonte
Aleatório, Idioma = java, Pontuação = 0,43012126100275
Este programa coloca aleatoriamente pixels na tela. Alguns (se não todos) os pixels não serão válidos. Em uma nota lateral, deve ser difícil criar um programa mais rápido que este.
Arena 1:
Arena 2:
Arena 3:
Arena 4:
fonte