Baue einen Minenroboter

12

Ihr Programm steuert einen Bergbauroboter, der im Untergrund nach wertvollen Mineralien sucht. Ihr Roboter teilt der Steuerung mit, wohin Sie sich bewegen und graben möchten, und die Steuerung gibt eine Rückmeldung über Ihren Roboterstatus.

Zu Beginn erhält Ihr Roboter eine Image-Karte der Mine mit bereits vorhandenen Schächten und eine Datendatei, in der der Wert und die Härte der Mineralien in der Mine angegeben sind. Ihr Roboter bewegt sich dann durch die Schächte und sucht nach wertvollen Mineralien, die abgebaut werden können. Ihr Roboter kann durch die Erde graben, wird aber von hartem Fels abgebremst.

kleines Minenbild

Der Roboter, der nach einer 24-Stunden-Schicht mit der wertvollsten Fracht zurückkehrt, wird der Gewinner sein. Es scheint eine komplizierte Herausforderung zu sein, aber es ist einfach, einen einfachen Mining-Roboter zu bauen (siehe die Antwort des Sample Mining Robot weiter unten).

Betrieb

Ihr Programm wird vom Controller mit dem Minenbild, den Mineraldaten und den Dateinamen der Geräte gestartet. Roboter können das Bild und die Mineraldaten der Mine verwenden, um wertvolles Erz zu finden und hartes Gestein zu vermeiden. Der Roboter möchte möglicherweise auch Geräte aus der Geräteliste kaufen.

z.B: python driller.py mineimage.png minerals.txt equipmentlist.txt

Nach einer Initialisierungsperiode von 2 Sekunden kommuniziert die Steuerung über stdin und stdout mit dem Roboterprogramm. Roboter müssen innerhalb von 0,1 Sekunden nach Erhalt einer Statusmeldung mit einer Aktion antworten.

In jeder Runde sendet die Steuerung dem Roboter eine Statuszeile:

timeleft cargo battery cutter x y direction

z.B: 1087 4505 34.65 88.04 261 355 right

Die Ganzzahl timeleftist die Spielsekunde vor Schichtende. Dies cargoist der ganzzahlige Wert der Mineralien, die Sie bisher abgebaut haben, abzüglich des Betrags, den Sie für die Ausrüstung bezahlt haben. Der batteryLadezustand ist ein ganzzahliger Prozentsatz Ihrer verbleibenden Akkuladung. Die cutterganzzahlige Ebene ist die aktuelle Schärfe des Schneiders als Prozentsatz des Standardwerts. Die Werte xund ysind positive Ganzzahlen, wobei die Roboterposition von der oberen linken Ecke bei (0, 0) angegeben wird. Die Richtung ist die aktuelle Richtung, in die der Roboter schaut (links, rechts, oben, unten).

Wenn Ihr Roboter die Eingabe "Endshift" oder "Failed" empfängt, wird Ihr Programm bald beendet. Möglicherweise möchten Sie, dass Ihr Roboter zuerst Debugging- / Leistungsdaten in eine Datei schreibt.

Es gibt 4 mögliche Befehle, die der Controller akzeptiert. direction left|right|up|downzeigt Ihren Roboter in diese Richtung und benötigt 15 Spielsekunden. move <integer>Weist Ihren Roboter an, so viele Einheiten vorwärts zu bewegen oder zu graben, was je nach Härte der geschnittenen Mineralien und Schärfe Ihres Fräsers einige Zeit in Anspruch nimmt (siehe unten). buy <equipment>Installiert die angegebene Ausrüstung und zieht die Kosten von Ihrem Frachtwert ab, jedoch nur, wenn sich der Roboter an der Oberfläche befindet (y-Wert <= Start-y-Wert). Die Installation der Ausrüstung dauert 300 Spielsekunden. Der spezielle Befehl snapshotschreibt das aktuelle Minenbild auf die Festplatte und benötigt keine Spielzeit. Sie können Snapshots zum Debuggen Ihres Roboters oder zum Erstellen von Animationen verwenden.

Ihr Roboter startet mit 100 Akkus und 100 Messerschärfen. Bewegen und Drehen verbrauchen nur wenig Batteriestrom. Das Graben verbraucht viel mehr und hängt von der Härte der Mineralien und der aktuellen Schärfe des Fräsers ab. Wenn Ihr Roboter in Mineralien gräbt, verliert der Fräser abhängig von der Zeit und der Härte der Mineralien seine Schärfe. Wenn Ihr Roboter über genügend Ladungswert verfügt, kehrt er möglicherweise an die Oberfläche zurück, um eine neue Batterie oder einen neuen Cutter zu kaufen. Beachten Sie, dass hochwertige Geräte eine anfängliche Wirksamkeit von über 100% haben. Batterien haben die Zeichenfolge "Batterie" im Namen und (Überraschungs-) Schneider haben "Schneider" im Namen.

Die folgenden Beziehungen definieren das Bewegen und Schneiden:

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

Beachten Sie, dass das Bewegen von 1 Einheit ohne Zerschneiden von Mineralien 1 Spielsekunde dauert und 0,0178 der Batterie verbraucht. Der Roboter kann also in 93 Spielminuten 5600 Einheiten mit einer Standardladung von 100 Einheiten fahren, wenn er keine Mineralien schneidet oder nicht dreht.

NEU: Der Roboter ist 11 Pixel breit und schneidet mit jedem Bewegungspixel bis zu 11 Pixel. Wenn weniger als 11 Pixel zu schneiden sind, benötigt der Roboter weniger Zeit, um sich zu bewegen, und verursacht weniger Verschleiß am Cutter. Wenn in der Mineraldatendatei keine Pixelfarbe angegeben ist, handelt es sich um einen freien Bereich mit einer Härte von Null und einem Wert von Null.

Der Lauf wird beendet, wenn die Zeit abgelaufen ist, die Roboterbatterie leer ist, ein Teil des Roboters die Bildgrenze überschreitet, ein ungültiger Befehl gesendet wird oder die Roboterkommunikation eine Zeitüberschreitung aufweist.

Ihre Punktzahl ist der Endwert der Roboterladung. Der Controller gibt Ihre Punktzahl und das endgültige Kartenbild aus. Die stderr-Ausgabe Ihres Programms wird in der Datei robot.log protokolliert. Wenn Ihr Roboter stirbt, ist der schwerwiegende Fehler möglicherweise im Protokoll verzeichnet.

Die Minendaten

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

mineraldata.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

Mein Bild:

teste meine

Das Minenbild hat möglicherweise einen Alpha-Kanal, dieser wird jedoch nicht verwendet.

Der Controller

Der Controller sollte mit Python 2.7 arbeiten und benötigt die PIL-Bibliothek. Ich wurde informiert, dass das Python-Kissen ein Windows-freundlicher Download ist, um das PIL-Bildmodul zu erhalten.

Starten Sie die Steuerung mit dem Roboterprogramm, cfg.py, Bild- und Datendateien im aktuellen Verzeichnis. Die vorgeschlagene Befehlszeile lautet:

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

Z.B: python controller.py java underminer.class

Die Steuerung schreibt am Ende des Laufs eine robot.log-Datei und eine finalmine.png-Datei.

#!/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()

Die verknüpfte Konfigurationsdatei (nicht zu ändern):

# 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'

Antwortformat

Die Antworten sollten einen Titel enthalten, der Programmiersprache, Roboternamen und Endergebnis enthält (z. B. Python 3 , Tunnel Terror , 1352 ). Der Antworttext sollte Ihren Code und das endgültige Minenkartenbild enthalten. Andere Bilder oder Animationen sind ebenfalls willkommen. Der Gewinner ist der Roboter mit der besten Punktzahl.

Andere Regeln

  • Die gemeinsamen Schlupflöcher sind verboten.
  • Wenn Sie einen Zufallszahlengenerator verwenden, müssen Sie einen Startwert in Ihrem Programm fest codieren, damit Ihr Programmablauf reproduzierbar ist. Jemand anderes muss in der Lage sein, Ihr Programm auszuführen und das gleiche endgültige Minenbild und -ergebnis zu erhalten.
  • Ihr Programm muss für jedes Minenbild programmiert sein . Sie dürfen Ihr Programm nicht für diese Datendateien oder diese Bildgröße, Minerallayout, Tunnellayout usw. codieren . Wenn ich vermute, dass ein Roboter gegen diese Regel verstößt, behalte ich mir das Recht vor, das Minenbild und / oder die Datendateien zu ändern.

Bearbeitungen

  • Erläuterte 0,1-Sekunden-Antwortregel.
  • Erweiterung der Befehlszeilenoptionen und -dateien zum Starten des Roboters.
  • Neue Controller-Version mit besserer Fehlerbehebung hinzugefügt.
  • Robot.log note hinzugefügt.
  • Grundlegende Mineralhärte und -wert.
  • Erklärter Akku vs. Schneidegerät.
  • Robotergröße 11 explizit gemacht.
  • Berechnungen für Zeit, Messerverschleiß und Batterie hinzugefügt.
Logik-Ritter
quelle
2
@TApicella 1. Roboter erhalten den Bilddateinamen als Argument und können ihn nach Belieben lesen und verarbeiten. Das Bild der Steuerung ändert sich, wenn sich der Roboter bewegt, und der Roboter kann das nicht sehen. Roboter können PIL- oder andere OSS-Bibliotheken von Drittanbietern verwenden. 2. Roboter müssen 2 Sekunden initialisieren und anschließend 0,1 Sekunden pro Befehlsantwort.
Logic Knight
1
Sie sollten die Antwort von 0,1 Sekunden pro Befehl in der Frage dokumentieren.
Peter Taylor
1
@KeithRandall Nein. Sie müssen das Bild und 2 Datendateien aus den in der Befehlszeile angegebenen Dateinamen einlesen. Sie können geändert werden.
Logic Knight
1
@TApicella Ich habe eine weitere Antwort mit einem Python-Framework hinzugefügt, die möglicherweise hilfreich ist.
Logic Knight
2
Es ist ein Feature. Nutzen Sie es zu Ihrem Vorteil, wenn Sie können :)
Logic Knight

Antworten:

3

Python 2, Sample Miner, 350

Dies ist ein Beispiel für den Mindestcode eines Mining-Roboters. Es gräbt gerade nach unten, bis die Batterie leer ist (alle Roboter fangen an, nach unten zu zeigen). Es wird nur eine Punktzahl von 350 erreicht. Denken Sie daran, stdout zu spülen, da der Controller sonst hängen bleibt.

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

Beispiel Bergmann Pfad

Logik-Ritter
quelle
2

Python 2, Robot Miner-Python-Vorlage, 410

Dies ist eine Mining-Robotervorlage, die die Funktionsweise eines Roboters zeigt und ein Framework für den Bau eigener Roboter bereitstellt. Es gibt einen Abschnitt zum Analysieren der Mineraldaten und einen Abschnitt zum Reagieren mit Aktionen. Die Platzhalter-Algorithmen funktionieren nicht gut. Der Roboter findet einige wertvolle Mineralien, aber nicht genug, um genügend Ersatzbatterien und -schneider zu kaufen. Es stoppt mit einer leeren Batterie auf dem Weg zur Oberfläche ein zweites Mal.

Ein besserer Plan ist es, die vorhandenen Tunnel zu nutzen, um wertvollen Mineralien nahe zu kommen und das Graben zu minimieren.

Beachten Sie, dass dieser Roboter eine Protokolldatei für jede Statusmeldung schreibt, die er erhält, damit Sie seine Entscheidungen nach einem Lauf überprüfen können.

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()

letzte Minenkarte

Logik-Ritter
quelle
Vielen Dank, das Aufdecken der Schleife, die die Interaktion zwischen Steuerung und Roboterprogramm steuert, ist wirklich hilfreich.
TApicella,