更新:はじめにPythonフレームワークを追加しました。
宇宙ステーションはクラッシャーボットによって追い抜かれました。ステーションが自己破壊する前に、「ウサギ」と呼ばれる高価で壊れやすいハイテクボットを出口テレポーターに向ける必要がありますが、クラッシャーボットは通路をパトロールしています。
プログラムにはASCIIマップが与えられ、各ターンにはクラッシャーボットと現在のウサギがいる場所が通知されます。その後、プログラムは、クラッシャーボットを避けながらウサギを出口テレポーターに移動します。
実行
Python 2コントローラーを実行するには:
python controller.py <mapfile> <turns> <seed> <runs> <prog>...
<prog> can be <interpreter> <yourprog> or similar.
シードは、実行を繰り返し可能にするために、クラッシャーとプログラムPRNGに使用される小さな整数です。使用する実際のシードに関係なく、プログラムは一貫して実行する必要があります。シードがゼロの場合、コントローラーは実行ごとにランダムシードを使用します。
コントローラは、マップテキストファイルの名前とシードを引数として使用してプログラムを実行します。例えば:
perl wandomwabbits.pl large.map 322
プログラムでPRNGを使用する場合は、指定されたシードで初期化する必要があります。コントローラは、STDINを介してプログラムの更新を送信し、STDOUTを介してウサギの動きを読み取ります。
コントローラが回転するたびに、3行が出力されます。
turnsleft <INT>
crusher <x,y> <movesto|crushes> <x,y>; ...
rabbits <x,y> <x,y> ...
次に、プログラムが1行を出力するのを待ちます。
move <x,y> to <x,y>; ...
更新:最初の行がコントローラーによって送信される前に、プログラムの初期化に2秒かかります。
コントローラーのウサギの位置を入力した後、プログラムが動きに応答するのに0.5秒以上かかる場合、コントローラーは終了します。
グリッド上にウサギがいない場合、ウサギの行には値がなく、プログラムはむき出しの「移動」文字列行を出力する必要があります。
毎ターン、プログラムの出力ストリームをフラッシュすることを忘れないでください。
例
プログラム入力:
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
プログラム出力:
move 14,3 to 14,4; 14,2 to 14,3; 6,4 to 7,4
コントローラーロジック
各ターンのロジック:
- 左折がゼロの場合、スコアを出力して終了します。
- 空の開始セルごとに、クラッシャーが見えない場合はウサギを追加します。
- 各クラッシャーについて、移動方向を決定します(以下を参照)。
- クラッシャーごとに、可能であれば移動します。
- クラッシャーがウサギの場所にある場合は、ウサギを取り除きます。
- 出力ターン左、クラッシャーアクション、およびプログラミングするウサギの位置。
- プログラムからウサギの移動要求を読み取ります。
- ウサギが存在しないか、移動できない場合は、スキップします。
- ウサギのそれぞれの新しい場所をプロットします。
- ウサギがクラッシャーにぶつかると、ウサギは破壊されます。
- ウサギがテレポーターの出口にいる場合、ウサギは取り除かれ、スコアが増加します。
- ウサギが衝突すると、両方とも破壊されます。
各粉砕機には常に進行方向(NSEWの1つ)があります。クラッシャーは、毎ターンこのナビゲーションロジックに従います。
- 1つまたは複数のウサギが4つの直交する方向のいずれかに見える場合は、方向を最も近いウサギの1つに変更します。クラッシャーは別のクラッシャーを通過することはできません。
- それ以外の場合は、可能であれば、オープンフォワード、左、右のオプションからランダムに選択します。
- それ以外の場合、障害物(壁または他のクラッシャー)が前、左、および右にある場合、方向を後ろに設定します。
次に、各クラッシャーについて:
- 新しい粉砕機の方向に障害物がない場合は、移動します(場合によっては粉砕します)。
地図記号
マップは、ASCII文字の長方形のグリッドです。マップは、壁
#
、廊下スペース、ウサギの開始位置
s
、出口テレポーターe
、および粉砕機の開始位置で構成されていc
ます。左上の角は位置(0,0)です。
小さな地図
###################
# c #
# # ######## # # ##
# ###s # ####e#
# # # # ## ## #
### # ### # ## # #
# ## #
###################
テストマップ
#################################################################
#s ############################ s#
## ## ### ############ # ####### ##### ####### ###
## ## ### # # ####### ########## # # #### ###### ###
## ## ### # ############ ####### ########## ##### ####### ###
## ## ## # ####### ########## # # ##### #### #
## ### #### #### ######## ########## ##### #### ## ###
######### #### ######## ################ ####### #### ###
######### ################# ################ c ####### ###
######### ################## ####### ####### ###########
######### ################## ######## ####### ###########
##### ### c ###### ###################
# #### ### # # # # # # # # # # ###### ############## #
# ####### #### ### #### ##### ## #
# #### ### # # # # # # # # # # ### # ### ######### #
##### ### #### ### ##### ### # ######## ####
############## ### # # # # # # # # # # ####### ## ####### ####
#### #### #### ### ### # # ### ###### ####
## ### # # # # # # # # # # ### ### # ### ##### ####
##### ######## ### # # # ##### # # # # ### ### # ##### #### ####
##### ##### ###### c # ### ### ###### ### ####
## c ######################### ### ##### ####### ### ####
##### # ### ####### ######## ### ##### c ## ## ####
##### # ####### ########## ## ######## # ######## ## ####
######### # ####### ## # ## # # # ##### # ####
### ##### # ### # ############## # ### # ### ## # ####
# ## # ### ### # ############## # ### ##### ##### ## ####
### ## ## # ### # ######## #
#s ## ###################################################e#
#################################################################
大きなマップの実行例
スコア
プログラムを評価するには、テストマップ、500ターン、5回の実行、0のシードでコントローラーを実行します。スコアは、ステーションから安全にテレポートされたウサギの総数です。同点の場合は、投票数が最も多い回答が勝者となります。
回答には、エントリ名、使用言語、およびスコアを含むタイトルを含める必要があります。回答の本文に、他の人があなたの実行を繰り返すことができるように、シード番号で完全なコントローラースコア出力を含めてください。例えば:
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
標準の無料のライブラリを使用できますが、標準の抜け穴は禁止されています。特定のシード、ターン数、マップ機能セット、またはその他のパラメーターに対してプログラムを最適化しないでください。この規則の違反が疑われる場合、地図を変更し、カウントを切り替え、シードする権利を留保します。
コントローラーコード
#!/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
コントローラはrun.log
、anim
ディレクトリ内の実行および一連の画像のテキストログを作成します。PythonインストールでPILイメージライブラリが見つからない場合(Pillowとしてダウンロード)、イメージは生成されません。ImageMagickを使用して画像シリーズをアニメーション化しました。例えば:
convert -delay 100 -loop 0 anim/run01* run1anim.gif
興味深いアニメーションや画像を回答とともに投稿してください。
あなたはオフにし、スピード設定により、コントローラまで、これらの機能を有効にすることができますGRIDLOG
= False
および/またはMKIMAGE = False
コントローラプログラムの最初の数行で。
推奨されるPythonフレームワーク
開始を支援するために、Pythonのフレームワークを示します。最初のステップは、マップファイルを読み取り、出口へのパスを見つけることです。各ターンには、クラッシャーの場所を保存するためのコードと、ウサギを移動する場所を決定するコードが必要です。開始する最も簡単な戦略は、クラッシャーを無視してウサギを出口に向かって移動させることです。
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()