MISE À JOUR: Ajout d'un framework Python pour commencer.
La station spatiale a été dépassée par des robots concasseurs. Vous devez diriger autant de nos robots techniques coûteux et fragiles appelés «lapins» vers un téléporteur de sortie avant que la station ne s'autodétruise, mais les robots concasseurs patrouillent dans les couloirs.
Votre programme reçoit une carte ASCII et chaque tour est informé de l'emplacement des robots de broyage et de vos lapins actuels. Votre programme doit ensuite déplacer vos lapins vers le téléporteur de sortie tout en évitant les bots concasseurs.
Exécution
Exécutez le contrôleur Python 2 avec:
python controller.py <mapfile> <turns> <seed> <runs> <prog>...
<prog> can be <interpreter> <yourprog> or similar.
La graine est un petit entier utilisé pour le broyeur et votre programme PRNG afin que les exécutions soient répétables. Votre programme doit fonctionner de manière cohérente quelle que soit la semence réellement utilisée. Si la valeur de départ est nulle, le contrôleur utilise une valeur de départ aléatoire pour chaque exécution.
Le contrôleur exécutera votre programme avec le nom du fichier texte de la carte et amorcera comme arguments. Par exemple:
perl wandomwabbits.pl large.map 322
Si votre programme utilise un PRNG, vous devez l'initialiser avec la graine donnée. Le contrôleur envoie ensuite vos mises à jour de programme via STDIN et lit vos mouvements de lapin via STDOUT.
Chaque tour, le contrôleur sortira 3 lignes:
turnsleft <INT>
crusher <x,y> <movesto|crushes> <x,y>; ...
rabbits <x,y> <x,y> ...
attend ensuite que le programme affiche une ligne:
move <x,y> to <x,y>; ...
MISE À JOUR: Votre programme aura 2 secondes pour s'initialiser avant que les premières lignes soient envoyées par le contrôleur.
Si votre programme prend plus de 0,5 seconde pour répondre par des mouvements après l'entrée de l'emplacement du lapin du contrôleur, le contrôleur se fermera.
S'il n'y a pas de lapins sur la grille, la ligne de lapins n'aura pas de valeurs et votre programme devrait sortir une ligne de chaîne "move" nue.
N'oubliez pas de vider le flux de sortie de votre programme à chaque tour ou le contrôleur peut se bloquer.
Exemple
entrée 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
sortie prog:
move 14,3 to 14,4; 14,2 to 14,3; 6,4 to 7,4
Logique du contrôleur
La logique de chaque tour:
- si le virage à gauche est nul, imprimer le score et quitter.
- pour chaque cellule de départ vide, ajoutez un lapin si aucun concasseur n'est en vue.
- pour chaque broyeur, décidez de la direction du mouvement (voir ci-dessous).
- pour chaque broyeur, déplacez-vous si possible.
- si le broyeur se trouve dans un emplacement pour le lapin, retirer le lapin.
- sortie du tourne-tour, des actions du concasseur et des emplacements des lapins à programmer.
- lire les demandes de déplacement de lapin du programme.
- si un lapin n'existe pas ou ne se déplace pas, sautez.
- tracer chaque nouvel emplacement de lapins.
- si le lapin frappe un concasseur, le lapin est détruit.
- si le lapin est en sortie du téléporteur, le lapin est retiré et le score augmenté.
- si les lapins entrent en collision, ils sont tous les deux détruits.
Chaque broyeur a toujours une direction de cap (un de NSEW). Un concasseur suit cette logique de navigation à chaque tour:
- Si un ou plusieurs lapins sont visibles dans l'une des 4 directions orthogonales, changez de direction pour l'un des lapins les plus proches. Notez que les concasseurs ne peuvent pas voir au-delà d'un autre concasseur.
- sinon choisissez aléatoirement entre les options ouvertes avant, gauche, droite si possible.
- sinon, si des obstacles (mur ou autre concasseur) à l'avant, à gauche et à droite, réglez la direction vers l'arrière.
Puis pour chaque broyeur:
- S'il n'y a pas d'obstacle dans la nouvelle direction du concasseur, déplacez-vous (et éventuellement écrasez).
Les symboles de la carte
La carte est une grille rectangulaire de caractères ASCII. La carte est composée de murs
#
, d'espaces de couloir , de positions de départ de lapin
s
, de téléporteurs de sortie e
et d'emplacements de départ de broyeur c
. Le coin supérieur gauche est l'emplacement (0,0).
Petite carte
###################
# c #
# # ######## # # ##
# ###s # ####e#
# # # # ## ## #
### # ### # ## # #
# ## #
###################
Carte de test
#################################################################
#s ############################ s#
## ## ### ############ # ####### ##### ####### ###
## ## ### # # ####### ########## # # #### ###### ###
## ## ### # ############ ####### ########## ##### ####### ###
## ## ## # ####### ########## # # ##### #### #
## ### #### #### ######## ########## ##### #### ## ###
######### #### ######## ################ ####### #### ###
######### ################# ################ c ####### ###
######### ################## ####### ####### ###########
######### ################## ######## ####### ###########
##### ### c ###### ###################
# #### ### # # # # # # # # # # ###### ############## #
# ####### #### ### #### ##### ## #
# #### ### # # # # # # # # # # ### # ### ######### #
##### ### #### ### ##### ### # ######## ####
############## ### # # # # # # # # # # ####### ## ####### ####
#### #### #### ### ### # # ### ###### ####
## ### # # # # # # # # # # ### ### # ### ##### ####
##### ######## ### # # # ##### # # # # ### ### # ##### #### ####
##### ##### ###### c # ### ### ###### ### ####
## c ######################### ### ##### ####### ### ####
##### # ### ####### ######## ### ##### c ## ## ####
##### # ####### ########## ## ######## # ######## ## ####
######### # ####### ## # ## # # # ##### # ####
### ##### # ### # ############## # ### # ### ## # ####
# ## # ### ### # ############## # ### ##### ##### ## ####
### ## ## # ### # ######## #
#s ## ###################################################e#
#################################################################
Exemple de grande exécution de carte
But
Pour évaluer votre programme, exécutez le contrôleur avec la carte de test, 500 tours, 5 courses et une graine de 0. Votre score est le nombre total de lapins téléportés avec succès hors de la station en sécurité. En cas d'égalité, la réponse avec le plus de votes l'emportera.
Votre réponse doit inclure un titre avec le nom de l'entrée, la langue utilisée et le score. Dans le corps de la réponse, veuillez inclure la sortie du score du contrôleur avec les numéros de départ afin que d'autres puissent répéter vos courses. Par exemple:
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
Vous pouvez utiliser des bibliothèques standard et disponibles gratuitement, mais les failles standard sont interdites. Vous ne devez pas optimiser votre programme pour une graine, un nombre de tours, un ensemble d'entités cartographiques ou d'autres paramètres donnés. Je me réserve le droit de modifier la carte, le nombre de tours et le germe si je soupçonne une violation de cette règle.
Code contrôleur
#!/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
Le contrôleur crée un journal de texte des exécutions run.log
et une série d'images dans le anim
répertoire. Si votre installation Python ne trouve pas la bibliothèque d'images PIL (à télécharger en tant que Pillow), aucune image ne sera générée. J'ai animé la série d'images avec ImageMagick. Par exemple:
convert -delay 100 -loop 0 anim/run01* run1anim.gif
Vous êtes invités à publier des animations ou des images intéressantes avec votre réponse.
Vous pouvez désactiver ces fonctionnalités et accélérer le contrôleur en réglant GRIDLOG
= False
et / ou MKIMAGE = False
dans les premières lignes du programme du contrôleur.
Framework Python suggéré
Pour vous aider à démarrer, voici un framework en Python. La première étape consiste à lire le fichier de carte et à trouver les chemins d'accès aux sorties. À chaque tour, il devrait y avoir un code pour stocker où se trouvent les broyeurs et un code qui décide où déplacer nos lapins. La stratégie la plus simple pour commencer est de déplacer les lapins vers une sortie en ignorant les broyeurs - certains lapins pourraient passer.
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()