Construisez un robot minier

12

Votre programme contrôlera un robot minier à la recherche souterraine de minéraux précieux. Votre robot indiquera au contrôleur où vous souhaitez vous déplacer et creuser, et le contrôleur fournira des informations sur l'état de votre robot.

Initialement, votre robot recevra une carte image de la mine avec quelques puits de mine déjà présents, et un fichier de données spécifiant la valeur et la dureté des minéraux dans la mine. Votre robot se déplacera ensuite à travers les puits à la recherche de minéraux précieux à extraire. Votre robot peut creuser à travers la terre, mais est ralenti par la roche dure.

petite image de mine

Le robot qui reviendra avec la cargaison la plus précieuse après 24 heures de travail sera le gagnant. Cela peut sembler être un défi compliqué, mais il est simple de créer un robot minier de base (voir la réponse Exemple de robot minier ci-dessous).

Opération

Votre programme sera démarré par le contrôleur avec l'image de la mine, les données minérales et les noms de fichiers de l'équipement. Les robots peuvent utiliser l'image de la mine et les données sur les minéraux pour trouver du minerai précieux et éviter les roches dures. Le robot peut également vouloir acheter de l'équipement dans la liste des équipements.

par exemple: python driller.py mineimage.png minerals.txt equipmentlist.txt

Après une période d'initialisation de 2 secondes, le contrôleur communique avec le programme du robot via stdin et stdout. Les robots doivent répondre par une action dans les 0,1 secondes après avoir reçu un message d'état.

A chaque tour, le contrôleur envoie au robot une ligne d'état:

timeleft cargo battery cutter x y direction

par exemple: 1087 4505 34.65 88.04 261 355 right

L'entier timeleftest le nombre de secondes de jeu restant avant la fin du quart de travail. C'est cargola valeur entière des minéraux que vous avez extraits jusqu'à présent moins ce que vous avez payé pour l'équipement. Le batteryniveau est un pourcentage entier de votre charge de batterie restante. Le cutterniveau entier est la netteté actuelle de l'outil de coupe en pourcentage de la valeur standard. Les valeurs xet ysont des entiers positifs avec la position du robot référencée dans le coin supérieur gauche à (0, 0). La direction est la direction actuelle du robot (gauche, droite, haut, bas).

Lorsque votre robot reçoit l'entrée 'endshift' ou 'échec', votre programme sera bientôt terminé. Vous voudrez peut-être que votre robot écrive d'abord les données de débogage / performances dans un fichier.

Le contrôleur accepte 4 commandes possibles. direction left|right|up|downpointera votre robot dans cette direction et nécessitera 15 secondes de jeu. move <integer>demandera à votre robot de déplacer ou de creuser autant d'unités vers l'avant, ce qui prend du temps en fonction de la dureté des minéraux coupés et de la netteté de votre couteau (voir ci-dessous). buy <equipment>installera l'équipement spécifié et déduira le coût de votre valeur de chargement, mais uniquement si le robot est à la surface (valeur y <= valeur y de départ). L'installation de l'équipement prend 300 secondes de jeu. La commande spéciale snapshotécrit l'image de la mine actuelle sur le disque et ne prend pas de temps de jeu. Vous pouvez utiliser des instantanés pour déboguer votre robot ou créer des animations.

Votre robot démarrera avec 100 piles et une netteté de 100 couteaux. Déplacer et tourner utilise une petite quantité de batterie. Le creusement utilise beaucoup plus et est fonction de la dureté des minéraux et de la netteté actuelle de la fraise. Au fur et à mesure que votre robot creuse des minéraux, le couteau perdra de sa netteté, en fonction du temps et de la dureté des minéraux. Si votre robot a une valeur de chargement suffisante, il peut revenir à la surface pour acheter une nouvelle batterie ou un cutter. Notez qu'un équipement de haute qualité a une efficacité initiale de plus de 100%. Les batteries ont la chaîne "batterie" dans le nom et (surprise) les couteaux ont "cutter" dans le nom.

Les relations suivantes définissent le déplacement et la découpe:

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

Notez que déplacer 1 unité sans couper de minéraux prend 1 seconde de jeu et utilise 0,0178 de la batterie. Ainsi, le robot peut conduire 5600 unités en 93 minutes de jeu sur une charge standard de 100, s'il ne coupe pas de minéraux ou ne tourne pas.

NOUVEAU: Le robot mesure 11 pixels de large, il coupera donc jusqu'à 11 pixels à chaque pixel de mouvement. S'il reste moins de 11 pixels à couper, le robot prendra moins de temps pour se déplacer et causera moins d'usure sur le couteau. Si une couleur de pixel n'est pas spécifiée dans le fichier de données minérales, il s'agit d'un espace libre de dureté zéro et de valeur nulle.

L'exécution est terminée lorsque le temps est écoulé, la batterie du robot est épuisée, une partie du robot dépasse la limite de l'image, une commande illégale est envoyée ou la communication du robot expire.

Votre score est la valeur finale de la cargaison du robot. Le contrôleur affichera votre score et l'image finale de la carte. La sortie stderr de votre programme est enregistrée dans le fichier robot.log. Si votre robot meurt, l'erreur fatale peut être dans le journal.

Les données de la mine

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

mon image:

tester le mien

L'image de la mine peut avoir un canal alpha, mais celui-ci n'est pas utilisé.

Le controlle

Le contrôleur doit fonctionner avec Python 2.7 et nécessite la bibliothèque PIL. J'ai été informé que l'oreiller Python est un téléchargement compatible avec Windows pour obtenir le module d'image PIL.

Démarrez le contrôleur avec le programme robot, cfg.py, les fichiers image et données dans le répertoire courant. La ligne de commande suggérée est:

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

Par exemple: python controller.py java underminer.class

Le contrôleur écrira un fichier robot.log et un fichier finalmine.png à la fin de l'exécution.

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

Le fichier de configuration lié (à ne pas modifier):

# 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 de réponse

Les réponses doivent avoir un titre comprenant le langage de programmation, le nom du robot et le score final (comme Python 3 , Tunnel Terror , 1352 ). Le corps de la réponse doit avoir votre code et l'image finale de la carte de la mine. D'autres images ou animations sont également les bienvenues. Le gagnant sera le robot avec le meilleur score.

Autres règles

  • Les failles communes sont interdites.
  • Si vous utilisez un générateur de nombres aléatoires, vous devez coder en dur une graine dans votre programme, afin que votre exécution de programme soit reproductible. Quelqu'un d'autre doit pouvoir exécuter votre programme et obtenir la même image et le score final de la mine.
  • Votre programme doit être programmé pour n'importe quelle image de mine. Vous ne devez pas coder votre programme pour ces fichiers de données ou pour cette taille d'image, la disposition des minéraux, la disposition du tunnel, etc. Si je soupçonne qu'un robot enfreint cette règle, je me réserve le droit de modifier l'image de la mine et / ou les fichiers de données.

Modifications

  • Explication de la règle de réponse de 0,1 seconde.
  • Développé sur les options et les fichiers de la ligne de commande de démarrage du robot.
  • Ajout d'une nouvelle version du contrôleur avec une meilleure capture des erreurs.
  • Ajout d'une note robot.log.
  • Explication de la dureté et de la valeur par défaut des minéraux.
  • Explication de la batterie par rapport à l'équipement de coupe.
  • Rendu la taille du robot 11 explicite.
  • Ajout de calculs pour le temps, l'usure des couteaux et la batterie.
Logic Knight
la source
2
@TApicella 1. Les robots obtiennent le nom du fichier image comme argument, et peuvent le lire et le traiter comme ils le souhaitent. L'image des contrôleurs changera au fur et à mesure que le robot se déplace et le robot ne pourra pas voir cela. Les robots peuvent utiliser PIL ou d'autres bibliothèques tierces OSS. 2. Les robots ont 2 secondes pour s'initialiser, puis 0,1 seconde par réponse de commande.
Logic Knight
1
Vous devez documenter la réponse de 0,1 seconde par commande dans la question.
Peter Taylor
1
@KeithRandall Non. Vous devez lire l'image et 2 fichiers de données à partir des noms de fichiers donnés sur la ligne de commande. Ils peuvent être modifiés.
Logic Knight
1
@TApicella J'ai ajouté une autre réponse avec un framework Python qui pourrait aider.
Logic Knight
2
C'est une fonctionnalité. Utilisez-le à votre avantage si vous le pouvez :)
Logic Knight

Réponses:

3

Python 2, Sample Miner, 350

Ceci est un exemple du code minimum pour un robot d'exploration de données. Il creuse simplement jusqu'à ce que sa batterie se décharge (tous les robots commencent à pointer vers le bas). Il ne gagne qu'un score de 350. N'oubliez pas de vider la sortie standard, sinon le contrôleur se bloquera.

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

exemple de chemin de mineur

Logic Knight
la source
2

Python 2, modèle Robot Miner Python, 410

Il s'agit d'un modèle de robot d'exploration de données pour montrer comment fonctionne un robot et fournir un cadre pour la construction de vos propres robots. Il y a une section pour analyser les données minérales et une section pour répondre avec des actions. Les algorithmes d'espace réservé ne fonctionnent pas bien. Le robot trouve des minéraux précieux, mais pas assez pour acheter suffisamment de piles et de couteaux de rechange. Il s'arrête avec une batterie morte sur le chemin de la surface pour une deuxième fois.

Un meilleur plan consiste à utiliser les tunnels existants pour se rapprocher des minéraux précieux et minimiser le creusement.

Notez que ce robot écrit un fichier journal de chaque message d'état qu'il reçoit afin que vous puissiez vérifier ses décisions après une exécution.

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

carte finale de la mine

Logic Knight
la source
Merci beaucoup, exposer la boucle qui stimule l'interaction entre le contrôleur et le programme du robot est vraiment utile.
TApicella