Zbuduj robota górniczego

12

Twój program będzie sterował robotem wydobywczym poszukującym pod ziemią cennych minerałów. Twój robot poinformuje kontroler, gdzie chcesz się poruszać i kopać, a kontroler przekaże informację zwrotną na temat Twojego robota.

Początkowo twój robot otrzyma mapę obrazową kopalni z niektórymi już szybami wydobywczymi oraz plik danych określający wartość i twardość minerałów w kopalni. Twój robot będzie wtedy poruszał się po szybach w poszukiwaniu cennych minerałów do wydobycia. Twój robot może kopać ziemię, ale spowalnia go twarda skała.

mały obraz kopalni

Zwycięzcą zostanie robot, który powróci z najcenniejszym ładunkiem po 24-godzinnej zmianie. To może wydawać się skomplikowanym wyzwaniem, ale łatwo jest zbudować podstawowego robota górniczego (patrz odpowiedź Przykładowego robota górniczego poniżej).

Operacja

Twój program zostanie uruchomiony przez kontrolera z obrazem kopalni, danymi mineralnymi i nazwami plików sprzętu. Roboty mogą wykorzystywać obraz kopalni i dane minerałów, aby znaleźć cenną rudę i uniknąć twardego kamienia. Robot może również chcieć kupić sprzęt z listy urządzeń.

na przykład: python driller.py mineimage.png minerals.txt equipmentlist.txt

Po 2 sekundach okresu inicjalizacji sterownik komunikuje się z programem robota za pośrednictwem stdin i stdout. Roboty muszą odpowiedzieć działaniem w ciągu 0,1 sekundy od otrzymania komunikatu o stanie.

Za każdym razem kontroler wysyła robotowi linię statusu:

timeleft cargo battery cutter x y direction

na przykład: 1087 4505 34.65 88.04 261 355 right

Liczba całkowita timeleftto sekunda gry pozostała do końca zmiany. Jest cargoto całkowita liczba minerałów, które wydobywałeś o wiele mniej, niż zapłaciłeś za sprzęt. batteryPoziom odsetek jest całkowitą swojej naładowania baterii. cutterWysokość całkowita jest obecny ostrość freza procent wartości standardowe. Wartości xi ysą dodatnimi liczbami całkowitymi z pozycją robota wskazywaną od lewego górnego rogu w (0, 0). Kierunek to aktualny kierunek, w którym skierowany jest robot (w lewo, w prawo, w górę, w dół).

Kiedy robot otrzyma komunikat „zmiana biegu” lub „błąd”, program wkrótce zostanie zakończony. Być może najpierw chcesz, aby robot zapisał dane debugowania / wydajności w pliku.

Istnieją 4 możliwe polecenia, które kontroler zaakceptuje. direction left|right|up|downskieruje robota w tym kierunku i zajmie 15 sekund gry. move <integer>poinstruuje twojego robota, aby ruszył lub kopał o tyle jednostek do przodu, co zajmuje czas w zależności od twardości wydobytych minerałów i ostrości twojego noża (patrz poniżej). buy <equipment>zainstaluje określony sprzęt i odliczy koszt od wartości ładunku, ale tylko wtedy, gdy robot znajdzie się na powierzchni (wartość y <= początkowa wartość y). Instalacja sprzętu zajmuje 300 sekund gry. Specjalne polecenie snapshotzapisuje bieżący obraz kopalni na dysk i nie zajmuje czasu gry. Za pomocą migawek można debugować robota lub tworzyć animacje.

Twój robot uruchomi się ze 100 bateriami i 100 ostrością noża. Przenoszenie i obracanie zużywa niewielką ilość energii baterii. Kopanie zużywa znacznie więcej i jest funkcją twardości minerałów i aktualnej ostrości noża. Gdy robot wkopuje się w minerały, frez straci swoją ostrość, w zależności od czasu i twardości minerałów. Jeśli twój robot ma wystarczającą wartość ładunku, może wrócić na powierzchnię, aby kupić nową baterię lub nóż. Należy pamiętać, że wysokiej jakości sprzęt ma początkową skuteczność ponad 100%. Baterie mają w nazwie ciąg „bateria”, a (zaskakujące) kutry mają w nazwie „nóż”.

Następujące relacje definiują przenoszenie i cięcie:

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

Pamiętaj, że przeniesienie 1 jednostki bez cięcia minerałów zajmuje 1 sekundę gry i zużywa 0,0178 baterii. Robot może więc prowadzić 5600 jednostek w 93 minuty gry przy standardowym ładunku 100, jeśli nie tnie minerałów ani nie obraca się.

NOWOŚĆ: robot ma szerokość 11 pikseli, dzięki czemu będzie przycinać do 11 pikseli przy każdym pikselu ruchu. Jeśli do cięcia jest mniej niż 11 pikseli, robot będzie się poruszał krócej i spowoduje mniejsze zużycie noża. Jeśli w pliku danych mineralnych nie określono koloru piksela, to jest to wolna przestrzeń o zerowej twardości i zerowej wartości.

Bieg kończy się, gdy skończy się czas, bateria robota jest wyczerpana, część robota przekracza granicę obrazu, wysyłane jest niedozwolone polecenie lub upłynął limit czasu komunikacji robota.

Twój wynik to końcowa wartość ładunku robota. Kontroler wyśle ​​twój wynik i ostateczny obraz mapy. Dane wyjściowe stderr programu są zapisywane w pliku robot.log. Jeśli twój robot zginie, w dzienniku może znajdować się błąd krytyczny.

Dane kopalni

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

mój obraz:

przetestuj mój

Obraz kopalni może mieć kanał alfa, ale nie jest używany.

Kontroler

Kontroler powinien współpracować z Pythonem 2.7 i wymaga biblioteki PIL. Zostałem poinformowany, że Python Pillow to przyjazny do pobrania system Windows, aby uzyskać moduł obrazu PIL.

Uruchom kontroler z programem robota, cfg.py, plikami obrazów i danych w bieżącym katalogu. Sugerowana linia poleceń to:

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

Na przykład: python controller.py java underminer.class

Sterownik zapisze plik robot.log i plik finalmine.png na końcu przebiegu.

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

Połączony plik konfiguracyjny (nie do zmiany):

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

Format odpowiedzi

Odpowiedzi powinny mieć tytuł zawierający język programowania, nazwę robota i końcowy wynik (np. Python 3 , Tunnel Terror , 1352 ). Treść odpowiedzi powinna zawierać Twój kod i końcowy obraz mapy kopalni. Inne obrazy lub animacje są również mile widziane. Zwycięzcą zostanie robot z najlepszym wynikiem.

Inne zasady

  • Częste luki są zabronione.
  • Jeśli używasz generatora liczb losowych, musisz zakodować ziarno w swoim programie, aby program mógł być odtwarzany. Ktoś inny musi być w stanie uruchomić Twój program i uzyskać ten sam końcowy obraz kopalni i wynik.
  • Twój program musi być zaprogramowany dla każdego obrazu kopalni. Nie wolno kodować swój program dla tych danych lub tym rozmiar, mineralny układ, układ tunel itp Jeśli podejrzewam robotem łamie tę zasadę, że zastrzegamy sobie prawo do zmiany wizerunku kopalni i / lub plików danych.

Edycje

  • Wyjaśniona zasada 0,1 sekundy odpowiedzi.
  • Rozbudowany na robotach uruchamiających opcje wiersza poleceń i pliki.
  • Dodano nową wersję kontrolera z lepszym wykrywaniem błędów.
  • Dodano notatkę robot.log.
  • Wyjaśniona domyślna twardość i wartość mineralna.
  • Wyjaśniony akumulator w porównaniu do wyposażenia noża.
  • Rozmiar robota 11 jest wyraźny.
  • Dodano obliczenia czasu, zużycia noża i baterii.
Logic Knight
źródło
2
@TApicella 1. Roboty pobierają nazwę pliku obrazu jako argument i mogą go czytać i przetwarzać w dowolny sposób. Obraz kontrolerów będzie się zmieniać wraz z ruchem robota i robot nie będzie w stanie tego zobaczyć. Roboty mogą korzystać z bibliotek PIL lub innych zewnętrznych bibliotek OSS. 2. Roboty mają 2 sekundy na zainicjowanie, a następnie 0,1 sekundy na odpowiedź na polecenie.
Logic Knight
1
Powinieneś udokumentować 0,1 sekundy na odpowiedź polecenia w pytaniu.
Peter Taylor
1
@KeithRandall Nie. Musisz odczytać obraz i 2 pliki danych z nazw plików podanych w wierszu poleceń. Mogą być zmienione.
Logic Knight
1
@TApicella Dodałem kolejną odpowiedź ze strukturą Python, która może pomóc.
Logic Knight
2
To funkcja. Jeśli możesz, użyj go na swoją korzyść :)
Logic Knight

Odpowiedzi:

3

Python 2, Sample Miner, 350

To jest przykład minimalnego kodu dla robota górniczego. Po prostu kopie prosto w dół, aż rozładuje się bateria (wszystkie roboty zaczną być skierowane w dół). Zarabia tylko 350 punktów. Pamiętaj, aby spłukać stdout, w przeciwnym razie kontroler się zawiesi.

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

przykładowa ścieżka górnika

Logic Knight
źródło
2

Python 2, szablon robota Miner Python, 410

Jest to szablon robota górniczego, który pokazuje, jak działa robot, i zapewnia ramy do budowy własnych robotów. Istnieje sekcja do analizy danych mineralnych i sekcja do reagowania na działania. Algorytmy zastępcze nie działają dobrze. Robot znajduje cenne minerały, ale nie wystarcza, aby kupić wystarczającą liczbę zapasowych baterii i noży. Zatrzymuje się z rozładowaną baterią w drodze na powierzchnię po raz drugi.

Lepszym planem jest wykorzystanie istniejących tuneli, aby zbliżyć się do cennych minerałów i zminimalizować kopanie.

Zauważ, że ten robot zapisuje plik dziennika każdego otrzymanego komunikatu o stanie, abyś mógł sprawdzić swoje decyzje po uruchomieniu.

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

ostateczna mapa kopalni

Logic Knight
źródło
Dziękuję bardzo, ujawnienie pętli, która napędza interakcję między sterownikiem a programem robota, jest naprawdę pomocne.
TApicella,