FELICITACIONES a @kuroineko por la mejor entrada y ganar la recompensa de 200 de @TheBestOne (¡excelente deportividad!).
Escriba un programa para colorear la mayor cantidad de imagen posible antes de que lo hagan los programas de oposición.
Reglas breves
- Su programa recibirá una imagen, su color y un número entero N.
- Cada turno, otros programas le envían actualizaciones de píxeles y le solicitan sus N actualizaciones.
- Puede actualizar cualquier píxel blanco que esté al lado de un píxel de su color.
- El programa que ha agregado más píxeles gana.
Reglas en detalle
Su programa recibirá un nombre de archivo de imagen PNG, color de inicio y un número N. El número N es el número máximo de píxeles que su programa puede colorear cada turno.
Ejemplo: MyProg arena.png (255,0,0) 30
La imagen de entrada será un rectángulo con lados entre 20 y 1000 píxeles de largo. Consistirá en píxeles negros, blancos y de color. Su programa puede elegir una secuencia de píxeles blancos para colorear como suya, con la condición de que cada nuevo píxel debe tener al menos uno de sus cuatro píxeles vecinos de su propio color. La imagen tendrá inicialmente al menos un píxel de su color. También puede tener píxeles de colores a los que no se asigna ningún programa. El canal alfa no se utiliza.
Tu objetivo es bloquear a tus oponentes y escribir tu color en tantos píxeles como puedas.
Cada turno, su programa aceptará 1 o más líneas de mensaje en STDIN y escribirá una línea que consiste en coordenadas de píxeles en STDOUT. Recuerde asignar STDOUT como sin búfer o vaciar el búfer STDOUT cada turno.
El orden de los jugadores llamados a cada turno se asignará al azar. Esto significa que un oponente (o su programa) puede tener 2 turnos seguidos.
Su programa recibirá colour (N,N,N) chose X,Y X,Y ... X,Y
mensajes de información que describen los píxeles completados por los programas del jugador. Si un jugador no hace movimientos o no tiene movimientos válidos, no se le enviará un mensaje sobre los movimientos de ese jugador. Su programa también recibirá un mensaje sobre sus propios movimientos aceptados (si ha especificado al menos un movimiento válido). El píxel 0,0 está en la esquina superior izquierda de la imagen.
Al recibir pick pixels
, su programa generará X,Y X,Y ... X,Y
hasta N píxeles (se permite una cadena vacía que consta de solo '\ n'). Los píxeles deben estar en orden de trazado. Si un píxel no es válido, se ignorará y no aparecerá en el informe para los jugadores. Su programa tiene 2 segundos para inicializar después de comenzar, pero solo 0.1 segundos para responder con una respuesta cada turno o perderá ese turno. Una actualización de píxeles enviada después de 0.1 segundos registrará una falla. Después de 5 fallas, su programa se suspende y no se enviarán actualizaciones o pick pixels
solicitudes.
Cuando el programa de jueces recibe una opción de píxeles vacía o inválida de cada programa de jugador no suspendido, la imagen se considerará completa y los programas recibirán el mensaje "salir". Los programas deben finalizar después de recibir "salir".
Tanteo
El juez obtendrá puntos después de que se complete la imagen. Su puntaje será su número de píxeles actualizados dividido por la captura de píxeles promedio en esa ronda, expresada como un porcentaje.
El número de píxeles agregados a la imagen por su reproductor es A. El número total de píxeles agregados por todos los jugadores P es T.
avg = T/P
score = 100*A/avg
Puntuaciones contables
Se da un oponente de referencia "The Blob". Para cada respuesta, titula tu bot con un nombre, idioma y tu puntaje (promedio de arena 1 a 4) contra el oponente de referencia. Una imagen o animación de una de tus batallas también sería buena. El ganador es el programa con la puntuación más alta contra el bot de referencia.
Si The Blob resulta demasiado fácil de vencer, puedo agregar una segunda ronda con un oponente de referencia más fuerte.
También te gustaría experimentar con 4 o más programas de jugador. También puedes probar tu bot contra otros bots publicados como respuestas.
El juez
El programa de juez requiere la Biblioteca de imágenes de Python (PIL) común y debe ser fácil de instalar desde el administrador de paquetes de su sistema operativo en Linux. Tengo un informe de que PIL no funciona con Python de 64 bits en Windows 7, así que compruebe si PIL funcionará para usted antes de comenzar este desafío (actualizado 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')
Configuración de ejemplo: 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 - el oponente de referencia
# 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
Un ejemplo de batalla - Blob vs Blob
Esta batalla tuvo un resultado predecible:
Bot blob.py with colour (255, 0, 0) scored 89.2883333333
Bot blob.py with colour (0, 255, 0) scored 89.365
fuente
Respuestas:
ColorFighter - C ++ - come un par de golondrinas para el desayuno
EDITAR
Dios, odio las serpientes (solo finge que son arañas, Indy)
En realidad me encanta Python. Desearía ser menos flojo y comenzar a aprenderlo correctamente, eso es todo.
Dicho todo esto, tuve que luchar con la versión de 64 bits de esta serpiente para que el juez funcionara. Hacer que PIL funcione con la versión de 64 bits de Python en Win7 requiere más paciencia de la que estaba listo para dedicar a este desafío, así que al final cambié (dolorosamente) a la versión Win32.
Además, el juez tiende a fallar gravemente cuando un bot es demasiado lento para responder.
Al no ser un experto en Python, no lo solucioné, pero tiene que ver con leer una respuesta vacía después de un tiempo de espera en stdin.
Una mejora menor sería colocar la salida stderr en un archivo para cada bot. Eso facilitaría el rastreo para la depuración post mortem.
Excepto por estos problemas menores, el juez me pareció muy simple y agradable de usar.
Felicitaciones por otro desafío inventivo y divertido.
El código
Construyendo el ejecutable
Solía LODEpng.cpp y LODEpng.h para leer imágenes PNG.
Sobre la forma más fácil que encontré para enseñarle a este lenguaje C ++ retrasado cómo leer una imagen sin tener que construir media docena de bibliotecas.
Simplemente compile y vincule LODEpng.cpp junto con el principal y Bob es su tío.
Compilé con MSVC2013, pero como solo utilicé unos pocos contenedores básicos STL (deque y vectores), podría funcionar con gcc (si tienes suerte).
Si no es así, podría intentar una compilación MinGW, pero, francamente, me estoy cansando de los problemas de portabilidad de C ++.
Hice bastante C / C ++ portátil en mis días (en compiladores exóticos para varios procesadores de 8 a 32 bits, así como SunOS, Windows desde 3.11 hasta Vista y Linux desde su infancia hasta Ubuntu Cooing Zebra o lo que sea, así que creo Tengo una idea bastante clara de lo que significa la portabilidad), pero en ese momento no requería memorizar (o descubrir) las innumerables discrepancias entre las interpretaciones de GNU y Microsoft de las especificaciones crípticas e hinchadas del monstruo STL.
Resultados contra Swallower
Cómo funciona
En el fondo, esta es una simple ruta de relleno de fuerza bruta.
La frontera del color del jugador (es decir, los píxeles que tienen al menos un vecino blanco) se utiliza como semilla para realizar el clásico algoritmo de inundación a distancia.
Cuando un punto alcanza la vinculación de un color enemigo, se calcula un camino hacia atrás para producir una cadena de píxeles que se mueven hacia el punto enemigo más cercano.
El proceso se repite hasta que se hayan reunido suficientes puntos para una respuesta de la longitud deseada.
Esta repetición es obscenamente costosa, especialmente cuando se lucha cerca del enemigo.
Cada vez que se encuentra una cadena de píxeles que va de la frontera a un píxel enemigo (y necesitamos más puntos para completar la respuesta), el relleno de inundación se vuelve a hacer desde el principio, con el nuevo camino agregado a la frontera. Significa que podría tener que hacer 5 rellenos de inundación o más para obtener una respuesta de 10 píxeles.
Si no se puede acceder a más píxeles enemigos, se seleccionan vecinos arbitrarios de los píxeles fronterizos.
El algoritmo se convierte en un relleno de inundación bastante ineficiente, pero esto solo sucede después de que se ha decidido el resultado del juego (es decir, no hay más territorio neutral por el que luchar).
Lo optimicé para que el Juez no pase años llenando el mapa una vez que se haya tratado la competencia. En su estado actual, el tiempo de ejecución es despreciable en comparación con el propio juez.
Dado que los colores enemigos no se conocen al principio, la imagen inicial de la arena se mantiene almacenada para copiar las áreas iniciales del enemigo cuando realiza su primer movimiento.
Si el código se reproduce primero, simplemente inundará unos pocos píxeles arbitrarios.
Esto hace que el algoritmo sea capaz de luchar contra un número arbitrario de adversarios, e incluso posiblemente nuevos adversarios que lleguen a un punto aleatorio en el tiempo, o que aparezcan colores sin un área de inicio (aunque esto no tiene ningún uso práctico).
El manejo del enemigo en una base de color por color también permitiría que dos instancias del bot cooperen (usando coordenadas de píxeles para pasar un signo de reconocimiento secreto).
Suena divertido, probablemente lo intentaré :).
La ruta de cálculo pesado se realiza tan pronto como hay nuevos datos disponibles (después de una notificación de movimiento), y algunas optimizaciones (la actualización de la frontera) se realizan justo después de que se haya dado una respuesta (para hacer la mayor cantidad de cómputo posible durante los turnos de otros bots) )
Una vez más, podría haber formas de hacer cosas más sutiles si hubiera más de 1 adversario (como abortar un cálculo si hay nuevos datos disponibles), pero de todos modos no veo dónde se necesita la multitarea, siempre que el algoritmo sea capaz de trabajar a plena carga.
Problemas de desempeño
Todo esto no puede funcionar sin un acceso rápido a los datos (y más potencia de cómputo que todo el programa Appolo, es decir, su PC promedio cuando solía hacer más que publicar algunos tweets).
La velocidad depende en gran medida del compilador. Por lo general, GNU supera a Microsoft por un margen del 30% (ese es el número mágico que noté en otros 3 desafíos de código relacionados con la ruta), pero este kilometraje puede variar, por supuesto.
El código en su estado actual apenas comienza a sudar en la arena 4. El perfímetro de Windows informa entre un 4 y un 7% de uso de CPU, por lo que debería ser capaz de hacer frente a un mapa de 1000x1000 dentro del límite de tiempo de respuesta de 100 ms.
En el corazón de casi todos los algoritmos de ruta se encuentra un FIFO (posiblemente proritizado, aunque no en ese caso), que a su vez requiere una rápida asignación de elementos.
Dado que el OP obligó obligatoriamente un límite al tamaño de la arena, hice algunos cálculos y vi que las estructuras de datos fijas dimensionadas al máximo (es decir, 1,000,000 de píxeles) no consumirían más de un par de docenas de megabytes, que su PC promedio come para el desayuno.
De hecho, bajo Win7 y compilado con MSVC 2013, el código consume alrededor de 14Mb en arena 4, mientras que los dos hilos de Swallower están usando más de 20Mb.
Comencé con contenedores STL para crear prototipos más fácilmente, pero STL hizo que el código fuera aún menos legible, ya que no deseaba crear una clase para encapsular todos y cada uno de los datos para ocultar la ofuscación (ya sea debido a mis propias incapacidades para hacer frente a la STL se deja a la apreciación del lector).
De todos modos, el resultado fue tan atrozmente lento que al principio pensé que estaba creando una versión de depuración por error.
Creo que esto se debe en parte a la increíblemente pobre implementación de Microsoft del STL (donde, por ejemplo, los vectores y los conjuntos de bits realizan comprobaciones encuadernadas u otras operaciones crípticas en el operador [], en violación directa de la especificación), y en parte al diseño del STL sí mismo.
Podría hacer frente a los atroces problemas de sintaxis y portabilidad (es decir, Microsoft vs GNU) si las actuaciones estuvieran allí, pero ciertamente este no es el caso.
Por ejemplo,
deque
es inherentemente lento, ya que baraja muchos datos de contabilidad esperando que la ocasión haga su cambio de tamaño súper inteligente, por lo que no podría importarme menos.Claro que podría haber implementado un asignador personalizado y cualquier otro bit de plantilla personalizada, pero un asignador personalizado solo cuesta unos cientos de líneas de código y la mejor parte del día para probar, con la docena de interfaces que tiene que implementar, mientras que un La estructura equivalente hecha a mano tiene aproximadamente cero líneas de código (aunque es más peligroso, pero el algoritmo no habría funcionado si no supiera, o creo que sabía, lo que estaba haciendo de todos modos).
Así que eventualmente mantuve los contenedores STL en partes no críticas del código, y construí mi propio asignador brutal y FIFO con dos arreglos de alrededor de 1970 y tres cortos sin firmar.
Tragar la golondrina
Como confirmó su autor, los patrones erráticos de Swallower son causados por el retraso entre las notificaciones de movimientos enemigos y las actualizaciones del hilo de ruta.
El perfímetro del sistema muestra claramente el hilo de ruta que consume 100% de CPU todo el tiempo, y los patrones irregulares tienden a aparecer cuando el foco de la lucha se desplaza a una nueva área. Esto también es bastante evidente con las animaciones.
Una optimización simple pero efectiva
Después de mirar las épicas peleas de perros entre Swallower y mi luchador, recordé un viejo dicho del juego de Go: defender de cerca, pero atacar desde lejos.
Hay sabiduría en eso. Si intentas apegarte demasiado a tu adversario, desperdiciarás movimientos preciosos tratando de bloquear cada posible camino. Por el contrario, si te mantienes a solo un píxel de distancia, es probable que evites llenar pequeños huecos que ganarían muy poco y usar tus movimientos para contrarrestar las amenazas más importantes.
Para implementar esta idea, simplemente extendí los movimientos de un enemigo (marcando los 4 vecinos de cada movimiento como un píxel enemigo).
Esto detiene el algoritmo de ruta a un píxel de la frontera del enemigo, lo que permite a mi luchador sortear a un adversario sin quedar atrapado en demasiadas peleas de perros.
Puede ver la mejora
(aunque todas las ejecuciones no son tan exitosas, puede notar los contornos mucho más suaves):
fuente
Profundidad: primer blob contra blob
Lenguaje = Python (3.2)
Puntuación =
111.475388276153.34210035Actualización: ahora usando una
Set
clase personalizada para obtener elpop()
método para producir una especie de patrón de cuadrícula que mejora drásticamente el área cubierta al principio cortando grandes partes de la imagen del enemigo. Nota: Estoy usando una cuadrícula de 12 x 12 para esto, que en una muestra aleatoria de tamaños de cuadrícula pareció dar los mejores resultados para arena3 (el que obtuvo la peor puntuación antes de la actualización), sin embargo, es muy probable que una mejor El tamaño de la cuadrícula existe para la selección dada de arenas.Hice una modificación simple al bot de referencia para que sea más fácil elegir puntos factibles que estén bordeados por la menor cantidad posible de puntos de color propio. Una mejora podría ser hacer que también favorezca la elección de puntos factibles que estén rodeados por tantos puntos de color enemigo como sea posible.
dfblob.py:
El juez original se ha modificado ligeramente para trabajar con Python 3.2 (y para agregar una funcionalidad de registro en bruto a los bots + guardar la imagen de la arena periódicamente para hacer animación):
Los resultados de la arena siguen. El bot dfblob recibió el color rojo para todas las arenas.
Arena 1:
Arena 2:
Arena 3:
Arena 4:
fuente
convert -delay 5 -loop 0 result*.png animated.gif
aunque algunos de los gifs tuvieron que ser cortados manualmente paraGolondrina
Lenguaje = Java
Puntuación =
162.3289512601408075169.4020975612382575Busca enemigos y los rodea.
Puede que tenga que darle un límite de tiempo más largo. Podría mejorarse bastante. A veces imprime píxeles no válidos.Actualización: rodea mucho más rápido. Utiliza otro hilo para actualizar las prioridades. Siempre regresa dentro de .1 segundos. La puntuación debería ser imposible de superar sin aumentar
MAX_TURNS
.Cómo funciona:
Este bot mantiene una cola prioritaria de píxeles que puede agregar. La prioridad de un píxel enemigo es 0. La prioridad de un píxel en blanco es 1 mayor que la prioridad más baja a su alrededor. Todos los demás píxeles tienen una prioridad de Integer.MAX_VALUE. El subproceso actualizador actualiza constantemente las prioridades de los píxeles. Cada vuelta, los N píxeles más bajos se sacan de la cola de prioridad.
Green Blob vs Red Swallower
Puntuación de Blob = 1.680553372583887225
Puntuación de Swallower = 169.4020975612382575
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Golondrina Verde vs. Mancha Roja
Puntuación de Blob = 1.6852943642218457375
Puntuación de Swallower = 169.3923095387498625
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Red Swallower vs Green Depth Primera gota
Puntuación de Swallower = 157.0749775233111925
Profundidad Puntuación del primer blob = 18.192783547939744
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Green Swallower vs Red Depth Primera gota
Puntuación de Swallower = 154.3368355651281075
Profundidad Puntuación del primer blob = 18.84463249420435425
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Green Blob vs Red Depth First Blob vs Blue Swallower:
Puntuación de Blob = 6.347962032393275525
Profundidad Puntuación del primer blob = 27.34842554331698275
Puntuación de Swallower = 227.720728953415375
Arena 1:
Arena 2:
Arena 3:
Arena 4:
Aquí está el juez de Sam Yonnou con algunos cambios para que especifique los archivos y el comando por separado:
Ejemplo de cfg:
Nota: Cualquier persona que logre tragarse al Swallower tiene una recompensa de 100 reputación. Por favor, publique en los comentarios a continuación si tiene éxito en esto.
fuente
Aleatorio, Idioma = java, Puntaje = 0.43012126100275
Este programa pone al azar píxeles en la pantalla. Algunos (si no todos) de los píxeles no serán válidos. En una nota al margen, debería ser difícil hacer un programa más rápido que este.
Arena 1:
Arena 2:
Arena 3:
Arena 4:
fuente