AGGIORNAMENTO: aggiunto un framework Python per iniziare.
La stazione spaziale è stata superata dai robot frantoi. Devi dirigere molti dei nostri robot tecnici costosi e fragili chiamati "conigli" verso un teletrasporto di uscita prima che la stazione si autodistrugga, ma i robot frantoi pattugliano i corridoi.
Al tuo programma viene assegnata una mappa ASCII e ad ogni turno viene indicato dove si trovano i robot frantoi e i tuoi conigli attuali. Il programma dovrebbe quindi spostare i conigli verso il teletrasporto di uscita evitando i robot frantoi.
Esecuzione
Esegui il controller Python 2 con:
python controller.py <mapfile> <turns> <seed> <runs> <prog>...
<prog> can be <interpreter> <yourprog> or similar.
Il seed è un piccolo numero intero utilizzato per il frantoio e il programma PRNG in modo che le esecuzioni siano ripetibili. Il programma dovrebbe funzionare in modo coerente indipendentemente dal seme effettivamente utilizzato. Se il seme è zero, il controller utilizzerà un seme casuale per ogni corsa.
Il controller eseguirà il tuo programma con il nome del file di testo della mappa e il seme come argomenti. Per esempio:
perl wandomwabbits.pl large.map 322
Se il tuo programma utilizza un PRNG, dovresti inizializzarlo con il seed specificato. Il controller invia quindi gli aggiornamenti del programma tramite STDIN e legge i movimenti del tuo coniglio tramite STDOUT.
Ad ogni giro il controller emetterà 3 linee:
turnsleft <INT>
crusher <x,y> <movesto|crushes> <x,y>; ...
rabbits <x,y> <x,y> ...
quindi attende che il programma emetta una riga:
move <x,y> to <x,y>; ...
AGGIORNAMENTO: il programma avrà 2 secondi per inizializzare prima che le prime righe vengano inviate dal controller.
Se il programma impiega più di 0,5 secondi per rispondere con le mosse dopo l'inserimento della posizione del coniglio del controller, il controller uscirà.
Se non ci sono conigli sulla griglia, la linea di conigli non avrà valori e il tuo programma dovrebbe produrre una riga di stringa "sposta" nuda.
Ricordarsi di svuotare il flusso di output del programma ogni turno o il controller potrebbe bloccarsi.
Esempio
input 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
uscita prog:
move 14,3 to 14,4; 14,2 to 14,3; 6,4 to 7,4
Logica del controller
La logica per ogni turno:
- se la svolta a sinistra è zero, stampare il punteggio ed uscire.
- per ogni cella iniziale vuota, aggiungi un coniglio se non si vede un frantoio.
- per ogni frantoio, decidere la direzione di spostamento (vedi sotto).
- per ogni frantoio, spostare se possibile.
- se il frantoio si trova in una posizione del coniglio, rimuovere il coniglio.
- uscita di svolta, azioni del frantoio e posizioni dei conigli da programmare.
- leggere le richieste di spostamento dei conigli dal programma.
- se un coniglio non esiste o non si sposta, saltare.
- tracciare ogni nuova posizione di conigli.
- se il coniglio colpisce un frantoio, il coniglio viene distrutto.
- se il coniglio è nel teletrasporto di uscita, il coniglio viene rimosso e il punteggio aumenta.
- se i conigli si scontrano, vengono entrambi distrutti.
Ogni frantoio ha sempre una direzione di rotta (una delle NSEW). Un frantoio segue questa logica di navigazione ogni turno:
- Se uno o più conigli sono visibili in una delle 4 direzioni ortogonali, cambia la direzione in uno dei conigli più vicini. Nota che i frantoi non possono vedere oltre un altro frantoio.
- altrimenti scegliere casualmente tra le opzioni di andata avanti, sinistra, destra, se possibile.
- altrimenti se gli ostacoli (muro o altro frantoio) davanti, a sinistra e a destra, impostano la direzione dietro.
Quindi per ogni frantoio:
- Se non ci sono ostacoli nella nuova direzione del frantoio, spostati (e possibilmente schiacciare).
I simboli della mappa
La mappa è una griglia rettangolare di caratteri ASCII. La mappa è composta da pareti
#
, spazi del corridoio , posizioni di partenza dei conigli
s
, teletrasporti di uscita e
e posizioni di partenza del frantoio c
. L'angolo in alto a sinistra è la posizione (0,0).
Piccola mappa
###################
# c #
# # ######## # # ##
# ###s # ####e#
# # # # ## ## #
### # ### # ## # #
# ## #
###################
Mappa di prova
#################################################################
#s ############################ s#
## ## ### ############ # ####### ##### ####### ###
## ## ### # # ####### ########## # # #### ###### ###
## ## ### # ############ ####### ########## ##### ####### ###
## ## ## # ####### ########## # # ##### #### #
## ### #### #### ######## ########## ##### #### ## ###
######### #### ######## ################ ####### #### ###
######### ################# ################ c ####### ###
######### ################## ####### ####### ###########
######### ################## ######## ####### ###########
##### ### c ###### ###################
# #### ### # # # # # # # # # # ###### ############## #
# ####### #### ### #### ##### ## #
# #### ### # # # # # # # # # # ### # ### ######### #
##### ### #### ### ##### ### # ######## ####
############## ### # # # # # # # # # # ####### ## ####### ####
#### #### #### ### ### # # ### ###### ####
## ### # # # # # # # # # # ### ### # ### ##### ####
##### ######## ### # # # ##### # # # # ### ### # ##### #### ####
##### ##### ###### c # ### ### ###### ### ####
## c ######################### ### ##### ####### ### ####
##### # ### ####### ######## ### ##### c ## ## ####
##### # ####### ########## ## ######## # ######## ## ####
######### # ####### ## # ## # # # ##### # ####
### ##### # ### # ############## # ### # ### ## # ####
# ## # ### ### # ############## # ### ##### ##### ## ####
### ## ## # ### # ######## #
#s ## ###################################################e#
#################################################################
Esempio di corsa su mappa di grandi dimensioni
Punto
Per valutare il tuo programma, esegui il controller con la mappa di prova, 500 giri, 5 corse e seme di 0. Il tuo punteggio è il numero totale di conigli teletrasportati con successo dalla stazione in sicurezza. In caso di pareggio, vincerà la risposta con il maggior numero di voti.
La risposta dovrebbe includere un titolo con il nome della voce, la lingua utilizzata e il punteggio. Nel corpo della risposta, includere l'output del punteggio del controller completo di numeri seme in modo che gli altri possano ripetere le prove. Per esempio:
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
È possibile utilizzare librerie standard e disponibili gratuitamente ma le scappatoie standard sono vietate. Non è necessario ottimizzare il programma per un determinato seed, conteggio dei turni, set di funzioni della mappa o altri parametri. Mi riservo il diritto di cambiare la mappa, girare il conteggio e seme se sospetto una violazione di questa regola.
Codice del controller
#!/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
Il controller crea un registro di testo delle esecuzioni run.log
e una serie di immagini nella anim
directory. Se l'installazione di Python non riesce a trovare la libreria di immagini PIL (scarica come Pillow), non verrà generata alcuna immagine. Ho animato le serie di immagini con ImageMagick. Per esempio:
convert -delay 100 -loop 0 anim/run01* run1anim.gif
Ti invitiamo a pubblicare animazioni o immagini interessanti con la tua risposta.
È possibile disattivare queste funzioni e accelerare il controller impostando GRIDLOG
= False
e / o MKIMAGE = False
nelle prime righe del programma del controller.
Framework Python suggerito
Per iniziare, ecco un framework in Python. Il primo passo è leggere il file della mappa e trovare i percorsi verso le uscite. Ad ogni turno dovrebbe esserci un codice per memorizzare dove si trovano i frantoi e un codice che decide dove spostare i nostri conigli. La strategia più semplice per cominciare è spostare i conigli verso un'uscita ignorando i frantoi: alcuni conigli potrebbero passare.
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()