マイニングロボットを構築する


12

あなたのプログラムは、貴重な鉱物を地下で検索する採掘ロボットを制御します。ロボットは、移動して掘りたい場所をコントローラーに伝え、コントローラーはロボットの状態に関するフィードバックを提供します。

最初に、いくつかの採掘シャフトがすでに存在する鉱山のイメージマップと、鉱山内の鉱物の値と硬度を指定するデータファイルがロボットに提供されます。次に、ロボットがシャフトを移動して、採掘する貴重な鉱物を探します。ロボットは地球を掘ることができますが、ハードロックによって速度が低下します。

小さな鉱山の画像

24時間のシフトの後、最も価値のある貨物で戻ってくるロボットが勝者になります。複雑な課題のように思えるかもしれませんが、基本的なマイニングロボットを作成するのは簡単です(以下のサンプルマイニングロボットの回答を参照)。

操作

プログラムは、鉱山の画像、鉱物のデータ、および機器のファイル名を使用して、コントローラーによって開始されます。ロボットは、鉱山の画像と鉱物のデータを使用して貴重な鉱石を見つけ、硬い岩を避けます。ロボットは、機器リストから機器を購入することもできます。

例えば: python driller.py mineimage.png minerals.txt equipmentlist.txt

2秒間の初期化期間の後、コントローラーはstdinおよびstdoutを介してロボットプログラムと通信します。ロボットは、ステータスメッセージを受信して​​から0.1秒以内にアクションで応答する必要があります。

コントローラーは毎ターン、ロボットにステータスラインを送信します。

timeleft cargo battery cutter x y direction

例えば: 1087 4505 34.65 88.04 261 355 right

整数timeleftは、シフトが終了するまでのゲームの秒数です。これ cargoは、これまでに採掘した鉱物の整数値から、設備に支払った金額を差し引いたものです。batteryレベルは、あなたのバッテリ残量の整数割合です。cutter整数レベルが基準値のパーセンテージとしてカッターの現在の鮮鋭度です。xそしてy値が(0、0)に左上隅から参照ロボット位置との正の整数です。方向は、ロボットが向いている現在の方向(左、右、上、下)です。

ロボットが「エンドシフト」または「失敗」の入力を受け取ると、プログラムはすぐに終了します。ロボットがデバッグ/パフォーマンスデータを最初にファイルに書き込むようにすることができます。

コントローラが受け入れる4つの可能なコマンドがあります。direction left|right|up|downその方向にロボットを向け、15ゲーム秒を必要とします。move <integer>ロボットにその数のユニットを前方に移動または掘るように指示します。これは、切断される鉱物の硬度とカッターの鋭さに応じて時間がかかります(以下を参照)。buy <equipment>指定された機器を設置し、貨物の値からコストを差し引きますが、これはロボットが水面にある場合のみです(y値<=開始y値)。機器の設置には300ゲーム秒かかります。特別なコマンドsnapshotは、現在の地雷イメージをディスクに書き込み、ゲーム時間はかかりません。スナップショットを使用して、ロボットをデバッグしたり、アニメーションを作成したりできます。

ロボットは、100個のバッテリーと100個のカッターシャープネスで起動します。移動と旋回には、少量のバッテリー電力が使用されます。掘りははるかに多くを使用し、鉱物の硬度とカッターの現在の鋭さの関数です。ロボットが鉱物を掘ると、時間と鉱物の硬さに応じて、カッターの鋭さが失われます。ロボットが十分な貨物価値を持っている場合、新しいバッテリーまたはカッターを購入するために地表に戻ることがあります。高品質の機器の初期有効性は100%を超えることに注意してください。バッテリーの名前には「battery」という文字列があり、(サプライズ)カッターの名前には「cutter」があります。

次の関係は、移動と切断を定義します。

timecutting = sum(hardness of pixels cut) * 100 / cutter
cutterwear = 0.01 for each second cutting
cutters will not wear below 0.1 sharpness
timemoving = 1 + timecutting
batterydrain = 0.0178 for each second moving
changing direction takes 15 seconds and drains 0.2 from the battery
installing new equipment takes 300 seconds

ミネラルを切断せずに1ユニットを移動するには1ゲーム秒かかり、バッテリーの0.0178を使用します。したがって、ロボットは、ミネラルを切断したり回転させたりしない場合、標準の100回の充電で93ゲーム分で5600ユニットを駆動できます。

新規:ロボットの幅は11ピクセルなので、動きのピクセルごとに最大11ピクセルをカットします。カットするピクセルが11ピクセル未満の場合、ロボットの移動時間が短くなり、カッターの摩耗が少なくなります。ミネラルデータファイルでピクセルの色が指定されていない場合、硬度と値がゼロの自由空間になります。

時間が切れる、ロボットのバッテリーが使い果たされる、ロボットの一部が画像の境界を超える、不正なコマンドが送信される、またはロボットの通信がタイムアウトになると、実行は終了します。

あなたのスコアはロボット貨物の最終値です。コントローラーは、スコアと最終的なマップ画像を出力します。プログラムのstderr出力は、robot.logファイルに記録されます。ロボットが死んだ場合、致命的なエラーがログに記録される場合があります。

鉱山データ

equipment.txt:

Equipment_Name      Cost    Initial_Value
std_cutter          200     100
carbide_cutter      600     160
diamond_cutter      2000    250
forcehammer_cutter  7200    460
std_battery         200     100
advanced_battery    500     180
megapower_battery   1600    320
nuclear_battery     5200    570

ミネラルデータ.txt:

Mineral_Name        Color           Value   Hardness
sandstone           (157,91,46)     0       3
conglomerate        (180,104,102)   0       12
igneous             (108,1,17)      0       42
hard_rock           (219,219,219)   0       15
tough_rock          (146,146,146)   0       50
super_rock          (73,73,73)      0       140
gem_ore1            (0,255,0)       10      8
gem_ore2            (0,0,255)       30      14
gem_ore3            (255,0,255)     100     6
gem_ore4            (255,0,0)       500     21

私の画像:

テスト鉱山

地雷画像にはアルファチャンネルが含まれる場合がありますが、これは使用されません。

コントローラー

コントローラーはPython 2.7で動作し、PILライブラリが必要です。Python Pillowは、PILイメージモジュールを取得するためのWindowsフレンドリーなダウンロードであることがわかりました。

現在のディレクトリにあるロボットプログラム、cfg.py、イメージ、およびデータファイルを使用してコントローラーを起動します。推奨されるコマンドラインは次のとおりです。

python controller.py [<interpreter>] {<switches>} <robotprogram>

例えば: python controller.py java underminer.class

コントローラーは、実行の最後にrobot.logファイルとfinalmine.pngファイルを書き込みます。

#!/usr/bin/env python
# controller.py
# Control Program for the Robot Miner on PPCG.
# Tested on Python 2.7 on Ubuntu Linux. May need edits for other platforms.
# V1.0 First release.
# V1.1 Better error catching

import sys, subprocess, time
# Suggest installing Pillow here if you don't have PIL already
from PIL import Image, ImageDraw

from cfg import *

program = sys.argv[1:]
calltext = program + [MINEIMAGE, MINERALFILE, EQUIPMENTFILE]
errorlog = open(ERRORFILE, 'wb')
process = subprocess.Popen(calltext,
            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=errorlog)

image = Image.open(MINEIMAGE)
draw = ImageDraw.Draw(image)
BLACK, ORANGE, WHITE = (0,0,0), (255,160,160), (255,255,255)
W,H = image.size
dirmap = dict(right=(1,0), left=(-1,0), up=(0,-1), down=(0,1))

# read in mineral file (Name, Color, Value, Hardness):
data = [v.split() for v in open(MINERALFILE)][1:]
mineralvalue = dict((eval(color), int(value)) for 
    name, color, value, hard in data)
hardness = dict((eval(color), int(hard)) for
    name, color, value, hard in data)

# read in the equipment list:
data = [v.split() for v in open(EQUIPMENTFILE)][1:]
equipment = dict((name, (int(cost), float(init))) for 
    name, cost, init in data)

# Set up simulation variables:
status = 'OK'
rx, ry, direction = START_X, START_Y, START_DIR    # center of robot
cargo, battery, cutter = 0, 100.0, 100.0
clock = ENDSHIFT
size = ROBOTSIZE / 2
msgfmt = '%u %u %u %u %u %u %s'
snapnum = 1

def mkcutlist(x, y, direc, size):
    dx, dy = dirmap[direc]
    cx, cy = x+dx*(size+1), y+dy*(size+1)
    output = [(cx, cy)]
    for s in range(1, size+1):
        output += [ (cx+dy*s, cy+dx*s), (cx-dy*s, cy-dx*s)]
    return output

def send(msg):
    process.stdin.write((msg+'\n').encode('utf-8'))
    process.stdin.flush()

def read():
    return process.stdout.readline().decode('utf-8')

time.sleep(INITTIME)
while clock > 0:
    try:
        start = time.time()
        send(msgfmt % (clock, cargo, battery, cutter, rx, ry, direction))
        inline = read()
        if time.time() - start > TIMELIMIT:
            status = 'Move timeout'
            break
    except:
        status = 'Robot comslink failed'
        break

    # Process command:
    movecount = 0
    try:
        arg = inline.split()
        cmd = arg.pop(0)
        if cmd == 'buy':
            if ry <= START_Y and arg and arg[0] in equipment:
                cost, initperc = equipment[arg[0]]
                if cost <= cargo:
                    cargo -= cost
                    if 'battery' in arg[0]:
                        battery = initperc
                    elif 'cutter' in arg[0]:
                        cutter = initperc
                    clock -= 300
        elif cmd == 'direction':
            if arg and arg[0] in dirmap:
                direction = arg[0]
                clock -= 15
                battery -= 0.2
        elif cmd == 'move':
            if arg and arg[0].isdigit():
                movecount = abs(int(arg[0]))
        elif cmd == 'snapshot':
            image.save('snap%04u.png' % snapnum)
            snapnum += 1
    except:
        status = 'Robot command malfunction'
        break

    for move in range(movecount):
        # check image boundaries
        dx, dy = dirmap[direction]
        rx2, ry2 = rx + dx, ry + dy
        print rx2, ry2
        if rx2-size < 0 or rx2+size >= W or ry2-size < 0 or ry2+size >= H:
            status = 'Bounds exceeded'
            break
        # compute time to move/cut through 1 pixel
        try:
            cutlist = mkcutlist(rx2, ry2, direction, size)
            colors = [image.getpixel(pos)[:3] for pos in cutlist]
        except IndexError:
            status = 'Mining outside of bounds'
            break
        work = sum(hardness.get(c, 0) for c in colors)
        timetaken = work * 100 / cutter
        cutter = max(0.1, cutter - timetaken / 100)
        clock -= 1 + int(timetaken + 0.5)
        battery -= (1 + timetaken) / 56
        if battery <= 0:
            status = 'Battery exhausted'
            break
        cargo += sum(mineralvalue.get(c, 0) for c in colors)
        draw.rectangle([rx-size, ry-size, rx+size+1, ry+size+1], BLACK, BLACK)
        rx, ry = rx2, ry2
        draw.rectangle([rx-size, ry-size, rx+size+1, ry+size+1], ORANGE, WHITE)
        if clock <= 0:
            break

    if status != 'OK':
        break

del draw
image.save('finalmine.png')
if status in ('Battery exhausted', 'OK'):
    print 'Score = %s' % cargo
    send('endshift')
else:
    print 'Error: %s at clock %s' % (status, clock)
    send('failed')

time.sleep(0.3)
process.terminate()

リンクされた構成ファイル(変更しないでください):

# This is cfg.py

# Scenario files:
MINEIMAGE = 'testmine.png'
MINERALFILE = 'mineraldata.txt'
EQUIPMENTFILE = 'equipment.txt'

# Mining Robot parameters:
START_X = 270
START_Y = 28
START_DIR = 'down'
ROBOTSIZE = 11      # should be an odd number

ENDSHIFT = 24 * 60 * 60   # seconds in an 24 hour shift

INITTIME = 2.0
TIMELIMIT = 0.1

ERRORFILE = 'robot.log'

回答フォーマット

回答には、プログラミング言語、ロボット名、最終スコア(Python 3Tunnel Terror1352など)を含むタイトルが必要です。回答の本文には、コードと最終的な鉱山マップ画像が含まれている必要があります。他の画像やアニメーションも歓迎します。勝者は最高得点のロボットになります。

その他の規則

  • 一般的な抜け穴は禁止されています。
  • 乱数ジェネレーターを使用する場合は、プログラムの実行を再現できるように、プログラムにシードをハードコーディングする必要があります。他の誰かがあなたのプログラムを実行し、同じ最終的な鉱山の画像とスコアを取得できる必要があります。
  • プログラムは、すべての鉱山イメージ用にプログラムする必要があります。これらのデータファイルまたはこのイメージサイズ、鉱物レイアウト、トンネルレイアウトなどにプログラムをコーディングしないでください。ロボットがこの規則に違反していると思われる場合、鉱山イメージやデータファイルを変更する権利を留保します。

編集

  • 0.1秒の応答ルールの説明。
  • ロボット起動コマンドラインオプションとファイルを拡張しました。
  • より優れたエラーキャッチを備えた新しいコントローラーバージョンを追加
  • robot.logのメモを追加しました。
  • デフォルトのミネラル硬度と値の説明。
  • バッテリーとカッター装置の説明。
  • ロボットサイズ11を明示的にしました。
  • 時間、カッターの摩耗、およびバッテリーの計算が追加されました。

2
@TApicella 1.ロボットは引数として画像ファイル名を取得し、好きなように読み取り、処理できます。ロボットが移動するとコントローラーの画像が変化し、ロボットはそれを見ることができなくなります。ロボットはPILまたはその他のOSSサードパーティライブラリを使用できます。2.ロボットは初期化するのに2秒かかり、コマンド応答ごとに0.1秒かかります。
ロジックナイト

1
質問のコマンド応答ごとに0.1秒を文書化する必要があります。
ピーターテイラー

1
@KeithRandallいいえ。コマンドラインで指定されたファイル名から画像と2つのデータファイルを読み込む必要があります。変更される場合があります。
ロジックナイト

1
@TApicella役立つPythonフレームワークを使用して、別の回答を追加しました。
ロジックナイト

2
これは機能です。可能であればあなたの利点にそれを使用します:)
ロジックナイト

回答:


3

Python 2、サンプルマイナー、350

これは、マイニングロボットの最小コードの例です。バッテリーが切れるまでまっすぐ掘り下げます(すべてのロボットが下向きになり始めます)。350のスコアのみを獲得します。stdoutをフラッシュすることを忘れないでください。フラッシュしないと、コントローラーがハングします。

import sys
# Robots are started with 3 arguments:
mineimage, mineralfile, equipmentfile = sys.argv[1:4]
raw_input()           # ignore first status report
print 'move 1000'     # dig down until battery dies
sys.stdout.flush()    # remember to flush stdout
raw_input()           # wait for end message

サンプルマイナーパス


2

Python 2、Robot Miner Pythonテンプレート、410

これは、ロボットの動作を示し、独自のロボットを構築するためのフレームワークを提供するマイニングロボットテンプレートです。鉱物データを分析するセクションと、アクションで応答するセクションがあります。プレースホルダーアルゴリズムはうまく機能しません。ロボットはいくつかの貴重な鉱物を見つけましたが、十分な交換用バッテリーとカッターを購入するには不十分です。二度目に地表に向かう途中で、バッテリー切れで停止します。

より良い計画は、既存のトンネルを使用して貴重な鉱物に近づき、掘削を最小限にすることです。

このロボットは、受信した各ステータスメッセージのログファイルを書き込むので、実行後に決定を確認できます。

import sys
from PIL import Image

MINEIMAGE, MINERALFILE, EQUIPMENTFILE = sys.argv[1:4]
image = Image.open(MINEIMAGE)
W,H = image.size
robotwidth = 11
halfwidth = robotwidth / 2

# read in mineral file (Name, Color, Value, Hardness):
data = [v.split() for v in open(MINERALFILE)][1:]
mineralvalue = dict((eval(color), int(value)) for 
    name, color, value, hard in data)
hardness = dict((eval(color), int(hard)) for
    name, color, value, hard in data)

# read in the equipment list:
data = [v.split() for v in open(EQUIPMENTFILE)][1:]
equipment = [(name, int(cost), float(init)) for 
    name, cost, init in data]
# Find the cheapest battery and cutter for later purchase:
minbatcost, minbatname = min([(c,n) for 
    n,c,v in equipment if n.endswith('battery')])
mincutcost, mincutname = min([(c,n) for 
    n,c,v in equipment if n.endswith('cutter')])

# process the mine image to find good places to mine:
goodspots = [0] * W
for ix in range(W):
    for iy in range(H):
        color = image.getpixel((ix, iy))[:3]   # keep RGB, lose Alpha
        value = mineralvalue.get(color, 0)
        hard = hardness.get(color, 0)
        #
        # -------------------------------------------------------------
        # make a map or list of good areas to mine here
        if iy < H/4:
            goodspots[ix] += value - hard/10.0
        # (you will need a better idea than this)
goodshafts = [sum(goodspots[i-halfwidth : i+halfwidth+1]) for i in range(W)]
goodshafts[:halfwidth] = [-1000]*halfwidth   # stop robot going outside bounds
goodshafts[-halfwidth:] = [-1000]*halfwidth
bestspot = goodshafts.index(max(goodshafts))
# -----------------------------------------------------------------
#

dirmap = dict(right=(1,0), left=(-1,0), up=(0,-1), down=(0,1))
logging = open('mylog.txt', 'wt')
logfmt = '%7s %7s %7s %7s %7s %7s %7s\n'
logging.write(logfmt % tuple('Seconds Cargo Battery Cutter x y Direc'.split()))
surface = None
plan = []

while True:
    status = raw_input().split()
    if status[0] in ('endshift', 'failed'):
        # robot will be terminated soon
        logging.close()
        continue
    logging.write(logfmt % tuple(status))
    direction = status.pop(-1)
    clock, cargo, battery, cutter, rx, ry = map(int, status)
    if surface == None:
        surface = ry    # return to this level to buy equipment
    #
    # -----------------------------------------------------------------
    # Decide here to choose direction, move, buy, or snapshot
    if not plan and rx != bestspot:
        plan.append('direction right' if bestspot > rx else 'direction left')
        plan.append('move %u' % abs(bestspot - rx))
        plan.append('direction down')

    if plan:
        action = plan.pop(0)
    elif battery < 20 and cargo > minbatcost + mincutcost:
        action = 'direction up'
        move = 'move %u' % (ry - surface)
        buybat = 'buy %s' % minbatname
        buycut = 'buy %s' % mincutname
        plan = [move, buybat, buycut, 'direction down', move]
    else:
        action = 'move 1'
    # -----------------------------------------------------------------
    #
    print action
    sys.stdout.flush()

ファイナルマインマップ


コントローラーとロボットプログラム間の相互作用を駆動するループを公開することは、本当にありがとうございます。
TApicella
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.