UPDATE: Zum Einstieg wurde ein Python-Framework hinzugefügt.
Die Raumstation wurde von Crusher-Bots überholt. Sie müssen so viele unserer teuren und zerbrechlichen Tech-Bots, die "Kaninchen" genannt werden, zu einem Ausgangsteleporter leiten, bevor sich die Station selbst zerstört, aber die Brecher-Bots patrouillieren durch die Korridore.
Ihr Programm erhält eine ASCII-Karte und in jeder Runde wird Ihnen mitgeteilt, wo sich die Brecher-Bots und Ihre aktuellen Kaninchen befinden. Ihr Programm sollte dann Ihre Kaninchen in Richtung des Ausgangsteleporters bewegen, während Sie den Brecherbots ausweichen.
Ausführung
Führen Sie den Python 2-Controller aus mit:
python controller.py <mapfile> <turns> <seed> <runs> <prog>...
<prog> can be <interpreter> <yourprog> or similar.
Der Startwert ist eine kleine Ganzzahl, die für den Brecher und Ihr Programm PRNG verwendet wird, damit die Abläufe wiederholt werden können. Ihr Programm sollte unabhängig vom tatsächlich verwendeten Startwert eine konsistente Leistung erbringen. Wenn der Startwert Null ist, verwendet der Controller für jeden Lauf einen zufälligen Startwert.
Die Steuerung führt Ihr Programm mit dem Namen der Kartentextdatei und dem Startwert als Argument aus. Z.B:
perl wandomwabbits.pl large.map 322
Wenn Ihr Programm ein PRNG verwendet, sollten Sie es mit dem angegebenen Startwert initialisieren. Die Steuerung sendet dann Ihre Programmaktualisierungen über STDIN und liest Ihre Kaninchenbewegungen über STDOUT.
In jeder Umdrehung gibt der Controller 3 Zeilen aus:
turnsleft <INT>
crusher <x,y> <movesto|crushes> <x,y>; ...
rabbits <x,y> <x,y> ...
Dann wartet das Programm auf die Ausgabe einer Zeile:
move <x,y> to <x,y>; ...
UPDATE: Ihr Programm muss innerhalb von 2 Sekunden initialisiert werden, bevor die ersten Zeilen von der Steuerung gesendet werden.
Wenn Ihr Programm länger als 0,5 Sekunden benötigt, um nach der Eingabe der Kaninchenposition für die Steuerung mit Bewegungen zu reagieren, wird die Steuerung beendet.
Wenn sich keine Hasen im Raster befinden, hat die Hasenzeile keine Werte, und Ihr Programm sollte eine leere "Move" -Stringzeile ausgeben.
Denken Sie daran, Ihren Programmausgabestream bei jeder Umdrehung zu leeren, da der Controller sonst hängen bleibt.
Beispiel
Prog-Eingabe:
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
Prog-Ausgabe:
move 14,3 to 14,4; 14,2 to 14,3; 6,4 to 7,4
Steuerungslogik
Die Logik für jede Runde:
- Wenn links null ist, Punktzahl drucken und beenden.
- Fügen Sie für jede leere Startzelle ein Kaninchen hinzu, wenn kein Brecher in Sicht ist.
- Entscheiden Sie für jeden Brecher die Bewegungsrichtung (siehe unten).
- Bewegen Sie sich für jeden Brecher, wenn möglich.
- Wenn sich der Brecher an einem Kaninchenstandort befindet, entfernen Sie das Kaninchen.
- Ausgang links, Brecheraktionen und Kaninchenstandorte zum Programmieren.
- Kaninchenbewegungsanforderungen aus dem Programm lesen.
- Wenn ein Kaninchen nicht existiert oder keine Bewegung möglich ist, überspringen Sie.
- Plotten Sie jeden neuen Standort von Kaninchen.
- Wenn ein Kaninchen einen Brecher trifft, wird es zerstört.
- Wenn sich das Kaninchen im Ausgangsteleporter befindet, wird es entfernt und die Punktzahl erhöht.
- Wenn Kaninchen kollidieren, werden beide zerstört.
Jeder Brecher hat immer eine Kursrichtung (eine von NSEW). Bei jeder Runde folgt ein Brecher dieser Navigationslogik:
- Wenn ein oder mehrere Kaninchen in einer der vier orthogonalen Richtungen sichtbar sind, wechseln Sie die Richtung zu einem der nächsten Kaninchen. Beachten Sie, dass Brecher nicht an einem anderen Brecher vorbei sehen können.
- Andernfalls können Sie nach Möglichkeit nach dem Zufallsprinzip zwischen "Vorwärts", "Links" und "Rechts" wählen.
- Andernfalls, wenn Hindernisse (Mauer oder andere Brecher) vorne links und rechts nach hinten weisen.
Dann für jeden Brecher:
- Wenn sich in der Richtung des neuen Brechers kein Hindernis befindet, bewegen Sie sich (und quetschen Sie möglicherweise).
Die Kartensymbole
Die Karte ist ein rechteckiges Raster aus ASCII-Zeichen. Die Karte besteht aus Wänden
#
, Korridorräumen , Startpositionen für Kaninchen
s
, Ausgangsteleportern e
und Startpositionen für Brecherc
. Die obere linke Ecke ist Position (0,0).
Kleine Karte
###################
# c #
# # ######## # # ##
# ###s # ####e#
# # # # ## ## #
### # ### # ## # #
# ## #
###################
Karte testen
#################################################################
#s ############################ s#
## ## ### ############ # ####### ##### ####### ###
## ## ### # # ####### ########## # # #### ###### ###
## ## ### # ############ ####### ########## ##### ####### ###
## ## ## # ####### ########## # # ##### #### #
## ### #### #### ######## ########## ##### #### ## ###
######### #### ######## ################ ####### #### ###
######### ################# ################ c ####### ###
######### ################## ####### ####### ###########
######### ################## ######## ####### ###########
##### ### c ###### ###################
# #### ### # # # # # # # # # # ###### ############## #
# ####### #### ### #### ##### ## #
# #### ### # # # # # # # # # # ### # ### ######### #
##### ### #### ### ##### ### # ######## ####
############## ### # # # # # # # # # # ####### ## ####### ####
#### #### #### ### ### # # ### ###### ####
## ### # # # # # # # # # # ### ### # ### ##### ####
##### ######## ### # # # ##### # # # # ### ### # ##### #### ####
##### ##### ###### c # ### ### ###### ### ####
## c ######################### ### ##### ####### ### ####
##### # ### ####### ######## ### ##### c ## ## ####
##### # ####### ########## ## ######## # ######## ## ####
######### # ####### ## # ## # # # ##### # ####
### ##### # ### # ############## # ### # ### ## # ####
# ## # ### ### # ############## # ### ##### ##### ## ####
### ## ## # ### # ######## #
#s ## ###################################################e#
#################################################################
Beispiel großer Kartenlauf
Ergebnis
Um Ihr Programm zu bewerten, führen Sie die Steuerung mit der Testkarte, 500 Umdrehungen, 5 Durchläufen und dem Startwert 0 aus. Ihre Punktzahl ist die Gesamtzahl der Kaninchen, die erfolgreich von der Station in Sicherheit teleportiert wurden. Bei Stimmengleichheit gewinnt die Antwort mit den meisten Stimmen.
Ihre Antwort sollte einen Titel mit dem Namen des Eintrags, der verwendeten Sprache und der Punktzahl enthalten. Geben Sie im Antworttext die Controller-Punktzahl mit den Startnummern an, damit andere Ihre Läufe wiederholen können. Beispielsweise:
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
Sie können Standardbibliotheken und frei verfügbare Bibliotheken verwenden, die Standardlücken sind jedoch verboten. Sie dürfen Ihr Programm nicht für einen bestimmten Startwert, eine bestimmte Anzahl von Runden, einen Kartenfeaturesatz oder andere Parameter optimieren. Ich behalte mir das Recht vor, die Karte zu ändern, zu zählen und zu setzen, wenn ich einen Verstoß gegen diese Regel vermute.
Controller-Code
#!/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
Der Controller erstellt ein Textprotokoll der eingehenden Abläufe run.log
und eine Reihe von Bildern im anim
Verzeichnis. Wenn Ihre Python-Installation die PIL-Bildbibliothek nicht finden kann (als Kissen herunterladen), werden keine Bilder generiert. Ich habe die Bilderserie mit ImageMagick animiert. Z.B:
convert -delay 100 -loop 0 anim/run01* run1anim.gif
Sie können gerne interessante Animationen oder Bilder mit Ihrer Antwort posten.
Sie können diese Funktionen ausschalten und den Controller beschleunigen, indem Sie GRIDLOG
= False
und / oder MKIMAGE = False
in den ersten Zeilen des Controller-Programms.
Vorgeschlagenes Python-Framework
Hier finden Sie ein Framework in Python, mit dem Sie loslegen können. Der erste Schritt besteht darin, die Kartendatei einzulesen und Pfade zu den Ausgängen zu finden. In jeder Runde sollte es einen Code geben, um zu speichern, wo sich die Brecher befinden, und einen Code, der entscheidet, wohin unsere Kaninchen bewegt werden sollen. Die einfachste Strategie ist zunächst, die Kaninchen zu einem Ausgang zu bewegen und dabei die Brecher zu ignorieren - einige Kaninchen könnten durchkommen.
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()