Costruisci un robot da miniera


12

Il tuo programma controllerà un robot da miniera alla ricerca sotterranea di minerali preziosi. Il robot comunicherà al controller dove si desidera spostare e scavare e il controller fornirà un feedback sullo stato del robot.

Inizialmente al tuo robot verrà fornita una mappa immagine della miniera con alcuni alberi di estrazione già presenti e un file di dati che specifica il valore e la durezza dei minerali nella miniera. Il robot si sposterà quindi attraverso gli alberi alla ricerca di minerali preziosi da estrarre. Il tuo robot può scavare attraverso la terra, ma è rallentato dalla roccia dura.

piccola miniera

Il robot che ritorna con il carico più prezioso dopo un turno di 24 ore sarà il vincitore. Può sembrare una sfida complicata, ma è semplice creare un robot di mining di base (vedere la risposta del robot di mining di esempio di seguito).

operazione

Il programma verrà avviato dal controller con l'immagine della miniera, i dati minerali e i nomi dei file delle attrezzature. I robot possono utilizzare l'immagine di miniera e i dati dei minerali per trovare minerali preziosi ed evitare l'hard rock. Il robot potrebbe anche voler acquistare apparecchiature dall'elenco delle apparecchiature.

per esempio: python driller.py mineimage.png minerals.txt equipmentlist.txt

Dopo un periodo di inizializzazione di 2 secondi, il controller comunica con il programma robot tramite stdin e stdout. I robot devono rispondere con un'azione entro 0,1 secondi dalla ricezione di un messaggio di stato.

Ad ogni turno, il controller invia al robot una riga di stato:

timeleft cargo battery cutter x y direction

per esempio: 1087 4505 34.65 88.04 261 355 right

I numeri interi timeleftsono i secondi di gioco rimasti prima della fine del turno. Il cargovalore intero dei minerali che hai estratto è molto meno quello che hai pagato per le attrezzature. Il batterylivello è una percentuale intera della carica residua della batteria. Il cutterlivello intero è l'attuale nitidezza del cutter come percentuale del valore standard. I valori xe ysono numeri interi positivi con la posizione del robot a cui fa riferimento l'angolo in alto a sinistra in (0, 0). La direzione è la direzione corrente verso cui è rivolto il robot (sinistra, destra, su, giù).

Quando il robot riceve l'ingresso "endshift" o "fail", il programma verrà presto interrotto. È possibile che si desideri che il robot scriva prima i dati di debug / prestazioni in un file.

Ci sono 4 possibili comandi che il controller accetterà. direction left|right|up|downindicherà il tuo robot in quella direzione e richiederà 15 secondi di gioco. move <integer>indicherà al tuo robot di spostare o scavare in avanti tante unità, il che richiede tempo a seconda della durezza del taglio dei minerali e della nitidezza del cutter (vedi sotto). buy <equipment>installerà l'equipaggiamento specificato e detrarrà il costo dal valore del carico, ma solo se il robot è in superficie (valore y <= valore y iniziale). L'installazione dell'attrezzatura richiede 300 secondi di gioco. Il comando speciale snapshotscrive l'immagine della miniera corrente su disco e non richiede tempo di gioco. È possibile utilizzare le istantanee per eseguire il debug del robot o creare animazioni.

Il robot inizierà con 100 batterie e 100 nitidezza della taglierina. Lo spostamento e la rotazione utilizzano una piccola quantità di energia della batteria. Lo scavo utilizza molto di più ed è una funzione della durezza dei minerali e della nitidezza attuale del cutter. Man mano che il robot scava nei minerali, la taglierina perderà la sua nitidezza, a seconda del tempo impiegato e della durezza dei minerali. Se il robot ha un valore di carico sufficiente, potrebbe tornare in superficie per acquistare una nuova batteria o un cutter. Si noti che le apparecchiature di alta qualità hanno un'efficacia iniziale di oltre il 100%. Le batterie hanno la stringa "batteria" nel nome e le taglierine (a sorpresa) hanno "taglierina" nel nome.

Le seguenti relazioni definiscono lo spostamento e il taglio:

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

Notare che spostare 1 unità senza tagliare minerali richiede 1 secondo di gioco e utilizza 0,0178 della batteria. Quindi il robot può guidare 5600 unità in 93 minuti di gioco con una carica standard di 100, se non sta tagliando minerali o girando.

NOVITÀ: il robot ha una larghezza di 11 pixel, quindi taglierà fino a 11 pixel con ogni pixel di movimento. Se ci sono meno di 11 pixel da tagliare, il robot impiegherà meno tempo a muoversi e causerà meno usura sulla taglierina. Se un file di pixel non è specificato nel file di dati minerali, è spazio libero di durezza zero e valore zero.

La corsa termina allo scadere del tempo, la batteria del robot è scarica, una parte del robot supera il limite dell'immagine, viene inviato un comando illegale o si verifica un timeout della comunicazione del robot.

Il tuo punteggio è il valore finale del carico del robot. Il controller emetterà il tuo punteggio e l'immagine della mappa finale. L'output stderr del programma è registrato nel file robot.log. Se il robot muore, l'errore irreversibile potrebbe essere presente nel registro.

I miei dati

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

la mia immagine:

prova il mio

L'immagine della miniera potrebbe avere un canale alfa, ma questa non viene utilizzata.

Il controller

Il controller dovrebbe funzionare con Python 2.7 e richiede la libreria PIL. Sono stato informato che Python Pillow è un download compatibile con Windows per ottenere il modulo immagine PIL.

Avviare il controller con il programma robot, cfg.py, i file di immagine e dati nella directory corrente. La riga di comando suggerita è:

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

Per esempio: python controller.py java underminer.class

Il controller scriverà un file robot.log e un file finalmine.png alla fine della corsa.

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

Il file di configurazione collegato (da non modificare):

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

Formato di risposta

Le risposte dovrebbero avere un titolo che includa linguaggio di programmazione, nome del robot e punteggio finale (come Python 3 , Tunnel Terror , 1352 ). Il corpo della risposta dovrebbe avere il tuo codice e l'immagine finale della mappa delle mine. Altre immagini o animazioni sono benvenute. Il vincitore sarà il robot con il miglior punteggio.

Altre regole

  • Le scappatoie comuni sono vietate.
  • Se si utilizza un generatore di numeri casuali, è necessario codificare un seme nel proprio programma, in modo che l'esecuzione del programma sia riproducibile. Qualcun altro deve essere in grado di eseguire il programma e ottenere la stessa immagine e punteggio della mia miniera finale.
  • Il tuo programma deve essere programmato per qualsiasi immagine di miniera. Non devi codificare il tuo programma per questi file di dati o queste dimensioni di immagine, layout minerale, layout di tunnel, ecc. Se sospetto che un robot stia infrangendo questa regola, mi riservo il diritto di modificare l'immagine di miniera e / o i file di dati.

Le modifiche

  • Spiegazione della regola di risposta di 0,1 secondi.
  • Espanso su robot a partire da opzioni e file della riga di comando.
  • Aggiunta una nuova versione del controller con una migliore rilevazione degli errori.
  • Aggiunta nota robot.log.
  • Spiegazione della durezza e del valore minerali predefiniti.
  • Spiegazione batteria vs attrezzatura taglierina.
  • Rendere esplicita la dimensione del robot 11.
  • Aggiunti calcoli per tempo, usura della taglierina e batteria.

2
@TApicella 1. I robot ottengono il nome file dell'immagine come argomento e possono leggerlo ed elaborarlo come preferiscono. L'immagine dei controller cambierà mentre il robot si muove e il robot non sarà in grado di vederlo. I robot possono utilizzare PIL o altre librerie di terze parti OSS. 2. I robot hanno 2 secondi per inizializzare e quindi 0,1 secondi per risposta al comando.
Logic Knight,

1
Dovresti documentare la risposta di 0,1 secondi per comando nella domanda.
Peter Taylor,

1
@KeithRandall No. Devi leggere nell'immagine e 2 file di dati dai nomi dei file indicati nella riga di comando. Possono essere cambiati.
Logic Knight,

1
@TApicella Ho aggiunto un'altra risposta con un framework Python che potrebbe aiutare.
Logic Knight,

2
È una caratteristica. Usalo a tuo vantaggio se puoi :)
Logic Knight,

Risposte:


3

Python 2, Sample Miner, 350

Questo è un esempio del codice minimo per un robot di mining. Scava semplicemente verso il basso fino a quando la batteria non si scarica (tutti i robot iniziano a puntare verso il basso). Guadagna solo un punteggio di 350. Ricorda di eliminare lo stdout altrimenti il ​​controller si bloccherà.

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

percorso del minatore del campione


2

Python 2, modello Python Robot Miner, 410

Questo è un modello di robot di mining per mostrare come funziona un robot e fornire un framework per costruire i tuoi robot. C'è una sezione per l'analisi dei dati minerali e una sezione per rispondere con le azioni. Gli algoritmi segnaposto non funzionano bene. Il robot trova alcuni minerali preziosi, ma non abbastanza per acquistare batterie e taglierine di ricambio. Si ferma con una batteria scarica sulla strada per la seconda volta.

Un piano migliore consiste nell'utilizzare i tunnel esistenti per avvicinarsi a minerali preziosi e ridurre al minimo gli scavi.

Si noti che questo robot scrive un file di registro di ogni messaggio di stato che riceve in modo da poter controllare le sue decisioni dopo una corsa.

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

mappa mineraria finale


Grazie mille, esporre il loop che guida l'interazione tra controller e programma robot è davvero utile.
TApicella,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.