ATUALIZAÇÃO: adicionada uma estrutura Python para começar.
A estação espacial foi ultrapassada por bots trituradores. Você deve direcionar tantos dos nossos caras e frágeis robôs tecnológicos chamados "coelhos" para um teleportador de saída antes que a estação se autodestrua, mas os robôs trituradores estão patrulhando os corredores.
Seu programa recebe um mapa ASCII, e a cada turno é informado onde estão os bots do triturador e seus coelhos atuais. Seu programa deve então mover seus coelhos em direção ao teleportador de saída, evitando os bots do triturador.
Execução
Execute o controlador Python 2 com:
python controller.py <mapfile> <turns> <seed> <runs> <prog>...
<prog> can be <interpreter> <yourprog> or similar.
A semente é um pequeno número inteiro usado para o britador e seu programa PRNG, para que as execuções sejam repetíveis. Seu programa deve ter um desempenho consistente, independentemente da semente real usada. Se a semente for zero, o controlador usará uma semente aleatória para cada execução.
O controlador executará seu programa com o nome do arquivo de texto do mapa e será propagado como argumentos. Por exemplo:
perl wandomwabbits.pl large.map 322
Se o seu programa usa um PRNG, você deve inicializá-lo com a semente especificada. O controlador envia as atualizações do seu programa através do STDIN e lê os movimentos do seu coelho através do STDOUT.
Cada vez que o controlador produz 3 linhas:
turnsleft <INT>
crusher <x,y> <movesto|crushes> <x,y>; ...
rabbits <x,y> <x,y> ...
então aguarda o programa gerar uma linha:
move <x,y> to <x,y>; ...
ATUALIZAÇÃO: Seu programa terá 2 segundos para inicializar antes que as primeiras linhas sejam enviadas pelo controlador.
Se o seu programa demorar mais de 0,5 segundos para responder aos movimentos após a entrada da localização do coelho do controlador, o controlador sairá.
Se não houver coelhos na grade, a linha de coelhos não terá valores, e seu programa deve gerar uma linha de seqüência de caracteres "move" simples.
Lembre-se de liberar o fluxo de saída do programa a cada turno ou o controlador pode travar.
Exemplo
entrada prog:
turnsleft 35
crusher 22,3 crushes 21,3; 45,5 movesto 45,4
rabbits 6,4 8,7 7,3 14,1 14,2 14,3
saída prog:
move 14,3 to 14,4; 14,2 to 14,3; 6,4 to 7,4
Lógica do controlador
A lógica para cada turno:
- se virar à esquerda é zero, imprima a pontuação e saia.
- para cada célula inicial vazia, adicione um coelho se não houver triturador à vista.
- para cada triturador, decida a direção do movimento (veja abaixo).
- para cada triturador, mova se possível.
- se o triturador estiver em um local de coelho, remova o coelho.
- virada de saída, ações do triturador e locais dos coelhos para programar.
- leia solicitações de movimentação de coelhos do programa.
- se um coelho não existir ou não for possível, pule.
- traçar cada nova localização de coelhos.
- se o coelho atinge um triturador, o coelho é destruído.
- se o coelho estiver no teleporter de saída, o coelho será removido e a pontuação aumentada.
- se os coelhos colidem, ambos são destruídos.
Cada triturador sempre tem uma direção de direção (uma das NSEW). Um triturador segue essa lógica de navegação a cada turno:
- Se um ou mais coelhos estiverem visíveis em qualquer uma das 4 direções ortogonais, mude a direção para um dos coelhos mais próximos. Observe que os trituradores não podem ver além de outro triturador.
- caso contrário, escolha aleatoriamente entre as opções de avanço aberto, esquerda e direita, se possível.
- caso contrário, se obstáculos (parede ou outro triturador) na frente, esquerda e direita, definir a direção para trás.
Então, para cada triturador:
- Se não houver obstáculos na nova direção do britador, mova-se (e possivelmente esmague).
Os símbolos do mapa
O mapa é uma grade retangular de caracteres ASCII. O mapa é composto de paredes
#
, espaços de corredor , posições iniciais de coelho
s
, teleportadores de saída e
e locais de partida do triturador c
. O canto superior esquerdo é a localização (0,0).
Mapa pequeno
###################
# c #
# # ######## # # ##
# ###s # ####e#
# # # # ## ## #
### # ### # ## # #
# ## #
###################
Mapa de teste
#################################################################
#s ############################ s#
## ## ### ############ # ####### ##### ####### ###
## ## ### # # ####### ########## # # #### ###### ###
## ## ### # ############ ####### ########## ##### ####### ###
## ## ## # ####### ########## # # ##### #### #
## ### #### #### ######## ########## ##### #### ## ###
######### #### ######## ################ ####### #### ###
######### ################# ################ c ####### ###
######### ################## ####### ####### ###########
######### ################## ######## ####### ###########
##### ### c ###### ###################
# #### ### # # # # # # # # # # ###### ############## #
# ####### #### ### #### ##### ## #
# #### ### # # # # # # # # # # ### # ### ######### #
##### ### #### ### ##### ### # ######## ####
############## ### # # # # # # # # # # ####### ## ####### ####
#### #### #### ### ### # # ### ###### ####
## ### # # # # # # # # # # ### ### # ### ##### ####
##### ######## ### # # # ##### # # # # ### ### # ##### #### ####
##### ##### ###### c # ### ### ###### ### ####
## c ######################### ### ##### ####### ### ####
##### # ### ####### ######## ### ##### c ## ## ####
##### # ####### ########## ## ######## # ######## ## ####
######### # ####### ## # ## # # # ##### # ####
### ##### # ### # ############## # ### # ### ## # ####
# ## # ### ### # ############## # ### ##### ##### ## ####
### ## ## # ### # ######## #
#s ## ###################################################e#
#################################################################
Exemplo de execução de mapa grande
Ponto
Para avaliar seu programa, execute o controlador com o mapa de teste, 500 voltas, 5 execuções e semente de 0. Sua pontuação é o número total de coelhos teleportados com sucesso da estação para a segurança. Em caso de empate, a resposta com mais votos vencerá.
Sua resposta deve incluir um título com o nome da entrada, o idioma usado e a pontuação. No corpo da resposta, inclua a saída da pontuação do controlador completa com números de sementes, para que outros possam repetir suas execuções. Por exemplo:
Running: controller.py small.map 100 0 5 python bunny.py
Run Seed Score
1 965 0
2 843 6
3 749 11
4 509 10
5 463 3
Total Score: 30
Você pode usar bibliotecas padrão e disponíveis gratuitamente, mas as brechas padrão são proibidas. Você não deve otimizar seu programa para uma determinada semente, contagem de turnos, conjunto de recursos do mapa ou outros parâmetros. Reservo-me o direito de alterar o mapa, contagem de turnos e propagação se suspeitar de uma violação desta regra.
Código do controlador
#!/usr/bin/env python
# Control Program for the Rabbit Runner on PPCG.
# Usage: controller.py <mapfile> <turns> <seed> <runs> <prog>...
# Tested on Python 2.7 on Ubuntu Linux. May need edits for other platforms.
# v1.0 First release.
# v1.1 Fixed crusher reporting bug.
# v1.2 Control for animation image production.
# v1.3 Added time delay for program to initialise
import sys, subprocess, time, re, os
from random import *
# Suggest installing Pillow if you don't have PIL already
try:
from PIL import Image, ImageDraw
except:
Image, ImageDraw = None, None
GRIDLOG = True # copy grid to run.log each turn (off for speed)
MKIMAGE = False # animation image creation (much faster when off)
IMGWIDTH = 600 # animation image width estimate
INITTIME = 2 # Allow 2 seconds for the program to initialise
point = complex # use complex numbers as 2d integer points
ORTH = [1, -1, 1j, -1j] # all 4 orthogonal directions
def send(proc, msg):
proc.stdin.write((msg+'\n').encode('utf-8'))
proc.stdin.flush()
def read(proc):
return proc.stdout.readline().decode('utf-8')
def cansee(cell):
# return a dict of visible cells containing robots with distances
see = {} # see[cell] = dist
robots = rabbits | set(crushers)
if cell in robots:
see[cell] = 0
for direc in ORTH:
for dist in xrange(1,1000):
test = cell + direc*dist
if test in walls:
break
if test in robots:
see[test] = dist
if test in crushers:
break # can't see past them
return see
def bestdir(cr, direc):
# Decide in best direction for this crusher-bot
seen = cansee(cr)
prey = set(seen) & rabbits
if prey:
target = min(prey, key=seen.get) # Find closest
vector = target - cr
return vector / abs(vector)
obst = set(crushers) | walls
options = [d for d in ORTH if d != -direc and cr+d not in obst]
if options:
return choice(options)
return -direc
def features(fname):
# Extract the map features
walls, crusherstarts, rabbitstarts, exits = set(), set(), set(), set()
grid = [line.strip() for line in open(fname, 'rt')]
grid = [line for line in grid if line and line[0] != ';']
for y,line in enumerate(grid):
for x,ch in enumerate(line):
if ch == ' ': continue
cell = point(x,y)
if ch == '#': walls.add(cell)
elif ch == 's': rabbitstarts.add(cell)
elif ch == 'e': exits.add(cell)
elif ch == 'c': crusherstarts.add(cell)
return grid, walls, crusherstarts, rabbitstarts, exits
def drawrect(draw, cell, scale, color, size=1):
x, y = int(cell.real)*scale, int(cell.imag)*scale
edge = int((1-size)*scale/2.0 + 0.5)
draw.rectangle([x+edge, y+edge, x+scale-edge, y+scale-edge], fill=color)
def drawframe(runno, turn):
if Image == None:
return
scale = IMGWIDTH/len(grid[0])
W, H = scale*len(grid[0]), scale*len(grid)
img = Image.new('RGB', (W,H), (255,255,255))
draw = ImageDraw.Draw(img)
for cell in rabbitstarts:
drawrect(draw, cell, scale, (190,190,255))
for cell in exits:
drawrect(draw, cell, scale, (190,255,190))
for cell in walls:
drawrect(draw, cell, scale, (190,190,190))
for cell in crushers:
drawrect(draw, cell, scale, (255,0,0), 0.8)
for cell in rabbits:
drawrect(draw, cell, scale, (0,0,255), 0.4)
img.save('anim/run%02uframe%04u.gif' % (runno, turn))
def text2point(textpoint):
# convert text like "22,6" to point object
return point( *map(int, textpoint.split(',')) )
def point2text(cell):
return '%i,%i' % (int(cell.real), int(cell.imag))
def run(number, nseed):
score = 0
turnsleft = turns
turn = 0
seed(nseed)
calltext = program + [mapfile, str(nseed)]
process = subprocess.Popen(calltext,
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=errorlog)
time.sleep(INITTIME)
rabbits.clear()
crushers.clear()
crushers.update( dict((cr, choice(ORTH)) for cr in crusherstarts) )
while turnsleft > 0:
# for each empty start cell, add a rabbit if no crusher in sight.
for cell in rabbitstarts:
if cell in rabbits or set(cansee(cell)) & set(crushers):
continue
rabbits.add(cell)
# write the grid to the runlog and create image frames
if GRIDLOG:
for y,line in enumerate(grid):
for x,ch in enumerate(line):
cell = point(x,y)
if cell in crushers: ch = 'X'
elif cell in rabbits: ch = 'o'
runlog.write(ch)
runlog.write('\n')
runlog.write('\n\n')
if MKIMAGE:
drawframe(number, turn)
# for each crusher, decide move direction.
for cr, direc in crushers.items():
crushers[cr] = bestdir(cr, direc)
# for each crusher, move if possible.
actions = []
for cr, direc in crushers.items():
newcr = cr + direc
if newcr in walls or newcr in crushers:
continue
crushers[newcr] = crushers.pop(cr)
action = ' movesto '
# if crusher is at a rabbit location, remove rabbit.
if newcr in rabbits:
rabbits.discard(newcr)
action = ' crushes '
actions.append(point2text(cr)+action+point2text(newcr))
# output turnsleft, crusher actions, and rabbit locations to program.
send(process, 'turnsleft %u' % turnsleft)
send(process, 'crusher ' + '; '.join(actions))
rabbitlocs = [point2text(r) for r in rabbits]
send(process, ' '.join(['rabbits'] + rabbitlocs))
# read rabbit move requests from program.
start = time.time()
inline = read(process)
if time.time() - start > 0.5:
print 'Move timeout'
break
# if a rabbit not exist or move not possible, no action.
# if rabbit hits a crusher, rabbit is destroyed.
# if rabbit is in exit teleporter, rabbit is removed and score increased.
# if two rabbits collide, they are both destroyed.
newrabbits = set()
for p1,p2 in re.findall(r'(\d+,\d+)\s+to\s+(\d+,\d+)', inline):
p1, p2 = map(text2point, [p1,p2])
if p1 in rabbits and p2 not in walls:
if p2-p1 in ORTH:
rabbits.discard(p1)
if p2 in crushers:
pass # wabbit squished
elif p2 in exits:
score += 1 # rabbit saved
elif p2 in newrabbits:
newrabbits.discard(p2) # moving rabbit collision
else:
newrabbits.add(p2)
# plot each new location of rabbits.
for rabbit in newrabbits:
if rabbit in rabbits:
rabbits.discard(rabbit) # still rabbit collision
else:
rabbits.add(rabbit)
turnsleft -= 1
turn += 1
process.terminate()
return score
mapfile = sys.argv[1]
turns = int(sys.argv[2])
argseed = int(sys.argv[3])
runs = int(sys.argv[4])
program = sys.argv[5:]
errorlog = open('error.log', 'wt')
runlog = open('run.log', 'wt')
grid, walls, crusherstarts, rabbitstarts, exits = features(mapfile)
rabbits = set()
crushers = dict()
if 'anim' not in os.listdir('.'):
os.mkdir('anim')
for fname in os.listdir('anim'):
os.remove(os.path.join('anim', fname))
total = 0
print 'Running:', ' '.join(sys.argv)
print >> runlog, 'Running:', ' '.join(sys.argv)
fmt = '%10s %20s %10s'
print fmt % ('Run', 'Seed', 'Score')
for n in range(runs):
nseed = argseed if argseed else randint(1,1000)
score = run(n, nseed)
total += score
print fmt % (n+1, nseed, score)
print 'Total Score:', total
print >> runlog, 'Total Score:', total
O controlador faz um log de texto das execuções run.log
e uma série de imagens no anim
diretório. Se sua instalação do Python não conseguir encontrar a biblioteca de imagens PIL (faça o download como Pillow), nenhuma imagem será gerada. Venho animando a série de imagens com o ImageMagick. Por exemplo:
convert -delay 100 -loop 0 anim/run01* run1anim.gif
Você pode publicar animações ou imagens interessantes com sua resposta.
Você pode desativar esses recursos e acelerar o controlador configurando GRIDLOG
= False
e / ou MKIMAGE = False
nas primeiras linhas do programa do controlador.
Estrutura Python sugerida
Para ajudar a começar, aqui está uma estrutura em Python. O primeiro passo é ler o arquivo de mapa e encontrar caminhos para as saídas. Em cada turno, deve haver algum código para armazenar onde estão os trituradores e um código que decide para onde mover nossos coelhos. A estratégia mais simples para começar é mover os coelhos em direção a uma saída, ignorando os trituradores - alguns coelhos podem passar.
import sys, re
from random import *
mapfile = sys.argv[1]
argseed = int(sys.argv[2])
seed(argseed)
grid = [line.strip() for line in open(mapfile, 'rt')]
#
# Process grid to find teleporters and paths to get there
#
while 1:
msg = sys.stdin.readline()
if msg.startswith('turnsleft'):
turnsleft = int(msg.split()[1])
elif msg.startswith('crusher'):
actions = re.findall(r'(\d+),(\d+) (movesto|crushes) (\d+),(\d+)', msg)
#
# Store crusher locations and movement so we can avoid them
#
elif msg.startswith('rabbits'):
moves = []
places = re.findall(r'(\d+),(\d+)', msg)
for rabbit in [map(int, xy) for xy in places]:
#
# Compute the best move for this rabbit
newpos = nextmoveforrabbit(rabbit)
#
moves.append('%u,%u to %u,%u' % tuple(rabbit + newpos))
print 'move ' + '; '.join(moves)
sys.stdout.flush()