Construye un robot minero

12

Su programa controlará un robot minero que busca minerales subterráneos valiosos. Su robot le dirá al controlador dónde desea moverse y excavar, y el controlador le proporcionará información sobre el estado de su robot.

Inicialmente, su robot recibirá un mapa de imagen de la mina con algunos pozos de minería ya presentes, y un archivo de datos que especifica el valor y la dureza de los minerales en la mina. Su robot se moverá a través de los ejes en busca de minerales valiosos para extraer. Su robot puede cavar a través de la tierra, pero es ralentizado por el rock duro.

pequeña imagen de mina

El robot que regrese con la carga más valiosa después de un turno de 24 horas será el ganador. Puede parecer un desafío complicado, pero es simple hacer un robot de minería básico (vea la respuesta del Robot de minería de muestra a continuación).

Operación

El programa iniciará su programa con la imagen de la mina, los datos minerales y los nombres de los archivos del equipo. Los robots pueden usar la imagen de la mina y los datos de minerales para encontrar minerales valiosos y evitar rocas duras. El robot también puede querer comprar equipos de la lista de equipos.

p.ej: python driller.py mineimage.png minerals.txt equipmentlist.txt

Después de un período de inicialización de 2 segundos, el controlador se comunica con el programa del robot a través de stdin y stdout. Los robots deben responder con una acción dentro de 0.1 segundos después de recibir un mensaje de estado.

Cada turno, el controlador envía al robot una línea de estado:

timeleft cargo battery cutter x y direction

p.ej: 1087 4505 34.65 88.04 261 355 right

El número entero timeleftes el juego que quedan segundos antes del final del turno. El cargoes el valor entero de los minerales que ha extraído hasta ahora menos de lo que ha pagado por el equipo. El batterynivel es un porcentaje entero de la carga restante de la batería. El cutternivel entero es la nitidez actual del cortador como un porcentaje del valor estándar. Los valores xy yson enteros positivos con la posición del robot referenciada desde la esquina superior izquierda en (0, 0). La dirección es la dirección actual que enfrenta el robot (izquierda, derecha, arriba, abajo).

Cuando su robot recibe la entrada 'desplazamiento final' o 'fallido', su programa pronto terminará. Es posible que desee que su robot escriba primero los datos de depuración / rendimiento en un archivo.

Hay 4 comandos posibles que el controlador aceptará. direction left|right|up|downapuntará a su robot en esa dirección y requerirá 15 segundos de juego. move <integer>le indicará a su robot que mueva o excave tantas unidades hacia adelante, lo que lleva tiempo dependiendo de la dureza del corte de minerales y la nitidez de su cortador (ver más abajo). buy <equipment>instalará el equipo especificado y deducirá el costo del valor de su carga, pero solo si el robot está en la superficie (valor y <= valor y inicial). La instalación del equipo lleva 300 segundos de juego. El comando especial snapshotescribe la imagen de mina actual en el disco y no requiere tiempo de juego. Puede usar instantáneas para depurar su robot o crear animaciones.

Su robot comenzará con 100 baterías y 100 nitidez de corte. Mover y girar utiliza una pequeña cantidad de batería. La excavación usa mucho más y es una función de la dureza de los minerales y la nitidez actual del cortador. A medida que su robot cava en minerales, el cortador perderá su agudeza, dependiendo del tiempo necesario y la dureza de los minerales. Si su robot tiene suficiente valor de carga, puede volver a la superficie para comprar una nueva batería o cortador. Tenga en cuenta que los equipos de alta calidad tienen una efectividad inicial de más del 100%. Las baterías tienen la cadena "batería" en el nombre y los cortadores (sorpresa) tienen "cortador" en el nombre.

Las siguientes relaciones definen movimiento y corte:

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

Tenga en cuenta que mover 1 unidad sin cortar minerales requiere 1 segundo de juego y usa 0.0178 de la batería. Por lo tanto, el robot puede conducir 5600 unidades en 93 minutos de juego con una carga estándar de 100, si no está cortando minerales o girando.

NUEVO: El robot tiene 11 píxeles de ancho, por lo que cortará hasta 11 píxeles con cada píxel de movimiento. Si hay menos de 11 píxeles para cortar, el robot tardará menos en moverse y causará menos desgaste en el cortador. Si no se especifica un color de píxel en el archivo de datos minerales, es un espacio libre de dureza cero y valor cero.

La ejecución finaliza cuando se agota el tiempo, la batería del robot se agota, una parte del robot excede el límite de la imagen, se envía un comando ilegal o se agota el tiempo de comunicación del robot.

Su puntaje es el valor final de la carga del robot. El controlador generará su puntaje y la imagen del mapa final. La salida stderr de su programa se registra en el archivo robot.log. Si su robot muere, el error fatal puede estar en el registro.

Los datos de la mina

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

imagen mía:

prueba mía

La imagen de la mina puede tener un canal alfa, pero esto no se usa.

El controlador

El controlador debería funcionar con Python 2.7 y requiere la biblioteca PIL. Me han informado que Python Pillow es una descarga amigable de Windows para obtener el módulo de imagen PIL.

Inicie el controlador con el programa del robot, cfg.py, archivos de imagen y datos en el directorio actual. La línea de comando sugerida es:

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

P.ej: python controller.py java underminer.class

El controlador escribirá un archivo robot.log y un archivo finalmine.png al final de la ejecución.

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

El archivo de configuración vinculado (no se debe cambiar):

# 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 de respuesta

Las respuestas deben tener un título que incluya lenguaje de programación, nombre del robot y puntaje final (como Python 3 , Tunnel Terror , 1352 ). El cuerpo de la respuesta debe tener su código y la imagen final del mapa de la mina. Otras imágenes o animaciones son bienvenidas también. El ganador será el robot con la mejor puntuación.

Otras reglas

  • Las lagunas comunes están prohibidas.
  • Si usa un generador de números aleatorios, debe codificar una semilla en su programa, para que la ejecución de su programa sea reproducible. Alguien más debe poder ejecutar su programa y obtener la misma imagen y puntaje final de la mina.
  • Su programa debe estar programado para cualquier imagen de mina. No debe codificar su programa para estos archivos de datos o este tamaño de imagen, diseño mineral, diseño de túnel, etc. Si sospecho que un robot está infringiendo esta regla, me reservo el derecho de cambiar los archivos de imagen y / o datos de la mina.

Ediciones

  • Explicó la regla de respuesta de 0.1 segundos.
  • Ampliado en las opciones y archivos de la línea de comandos de inicio del robot.
  • Se agregó una nueva versión del controlador con una mejor captura de errores.
  • Se agregó la nota robot.log.
  • Explicación de la dureza y valor mineral predeterminados.
  • Batería explicada vs equipo de corte.
  • Hecho robot tamaño 11 explícito.
  • Se agregaron cálculos de tiempo, desgaste del cortador y batería.
Caballero Lógico
fuente
2
@TApicella 1. Los robots obtienen el nombre de archivo de la imagen como argumento, y pueden leerlo y procesarlo como quieran. La imagen de los controladores cambiará a medida que el robot se mueva y el robot no podrá ver eso. Los robots pueden usar PIL u otras bibliotecas de terceros de OSS. 2. Los robots tienen 2 segundos para inicializar y luego 0.1 segundos por respuesta de comando.
Logic Knight
1
Debe documentar la respuesta de 0.1 segundos por comando en la pregunta.
Peter Taylor
1
@KeithRandall No. Debe leer en la imagen y 2 archivos de datos de los nombres de archivo que figuran en la línea de comandos. Pueden ser cambiados.
Logic Knight
1
@TApicella He agregado otra respuesta con un marco de Python que podría ayudar.
Caballero lógico
2
Es una característica Úsalo a tu favor si puedes :)
Logic Knight

Respuestas:

3

Python 2, Sample Miner, 350

Este es un ejemplo del código mínimo para un robot minero. Simplemente cava hacia abajo hasta que se agota la batería (todos los robots comienzan a apuntar hacia abajo). Solo gana un puntaje de 350. Recuerde eliminar stdout o el controlador se bloqueará.

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

muestra de ruta minera

Caballero Lógico
fuente
2

Python 2, plantilla de Python Robot Miner, 410

Esta es una plantilla de robot de minería para mostrar cómo funciona un robot y proporcionar un marco para construir sus propios robots. Hay una sección para analizar los datos minerales y una sección para responder con acciones. Los algoritmos de marcador de posición no funcionan bien. El robot encuentra algunos minerales valiosos, pero no lo suficiente como para comprar suficientes baterías y cortadores de repuesto. Se detiene con una batería descargada en su camino a la superficie por segunda vez.

Un mejor plan es utilizar los túneles existentes para acercarse a minerales valiosos y minimizar la excavación.

Tenga en cuenta que este robot escribe un archivo de registro de cada mensaje de estado que recibe para que pueda verificar sus decisiones después de una ejecución.

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

mapa final de la mina

Caballero Lógico
fuente
Muchas gracias, exponer el bucle que impulsa la interacción entre el controlador y el programa del robot es realmente útil.
TApicella