Construa um robô de mineração

12

Seu programa controlará um robô de mineração que procura no subsolo minerais valiosos. Seu robô informará ao controlador onde você deseja mover e cavar, e fornecerá feedback sobre o status do robô.

Inicialmente, seu robô receberá um mapa de imagem da mina com alguns eixos de mineração já presentes e um arquivo de dados especificando o valor e a dureza dos minerais na mina. Seu robô passará pelos eixos procurando minerais valiosos para os meus. Seu robô pode cavar a terra, mas é desacelerado pelo hard rock.

pequena imagem minha

O robô que retornar com a carga mais valiosa após um turno de 24 horas será o vencedor. Pode parecer um desafio complicado, mas é simples criar um robô de mineração básico (veja a resposta do robô de mineração de amostra abaixo).

Operação

Seu programa será iniciado pelo controlador com a imagem da mina, dados minerais e nomes de arquivos do equipamento. Os robôs podem usar os dados de imagem e minerais da mina para encontrar minério valioso e evitar rochas duras. O robô também pode querer comprar equipamentos da lista de equipamentos.

por exemplo: python driller.py mineimage.png minerals.txt equipmentlist.txt

Após um período de inicialização de 2 segundos, o controlador se comunica com o programa do robô através de stdin e stdout. Os robôs devem responder com uma ação dentro de 0,1 segundos após receber uma mensagem de status.

A cada turno, o controlador envia ao robô uma linha de status:

timeleft cargo battery cutter x y direction

por exemplo: 1087 4505 34.65 88.04 261 355 right

O número inteiro timelefté o segundo de jogo restante antes do final do turno. O cargoé o valor inteiro dos minerais que você extraiu até muito menos o que você pagou pelo equipamento. O batterynível é uma porcentagem inteira da carga restante da bateria. O cutternível inteiro é a nitidez atual do cortador como uma porcentagem do valor padrão. Os valores xe ysão números inteiros positivos com a posição do robô referenciada no canto superior esquerdo em (0, 0). A direção é a direção atual que o robô está enfrentando (esquerda, direita, cima, baixo).

Quando o seu robô receber a entrada 'turno final' ou 'com falha', seu programa será encerrado em breve. Você pode querer que seu robô grave dados de depuração / desempenho em um arquivo primeiro.

Existem 4 comandos possíveis que o controlador aceitará. direction left|right|up|downapontará seu robô nessa direção e precisará de 15 segundos de jogo. move <integer>instruirá seu robô a mover ou cavar tantas unidades para frente, o que leva tempo, dependendo da dureza do corte de minerais e da nitidez do seu cortador (veja abaixo). buy <equipment>instalará o equipamento especificado e deduzirá o custo do valor da carga, mas apenas se o robô estiver na superfície (valor y <= valor y inicial). A instalação do equipamento leva 300 segundos de jogo. O comando especial snapshotgrava a imagem atual da mina no disco e não leva tempo para o jogo. Você pode usar instantâneos para depurar seu robô ou criar animações.

Seu robô começará com 100 baterias e 100 nitidez do cortador. Mover e girar consome uma pequena quantidade de energia da bateria. A escavação usa muito mais e é uma função da dureza dos minerais e da nitidez atual do cortador. À medida que o robô escavar minerais, o cortador perderá a nitidez, dependendo do tempo gasto e da dureza dos minerais. Se o seu robô tiver valor de carga suficiente, ele poderá retornar à superfície para comprar uma nova bateria ou cortador. Observe que equipamentos de alta qualidade têm uma eficácia inicial de mais de 100%. As baterias têm o nome "bateria" no nome e os cortadores (surpresa) têm o nome "cortador".

Os seguintes relacionamentos definem movimento e 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

Observe que mover 1 unidade sem cortar minerais leva 1 segundo de jogo e usa 0,0178 da bateria. Portanto, o robô pode dirigir 5600 unidades em 93 minutos de jogo com uma carga padrão de 100, se não estiver cortando minerais ou girando.

NOVO: o robô tem 11 pixels de largura e, portanto, cortará até 11 pixels a cada pixel de movimento. Se houver menos de 11 pixels para cortar, o robô levará menos tempo para se mover e causará menos desgaste no cortador. Se uma cor de pixel não for especificada no arquivo de dados minerais, será um espaço livre de dureza zero e valor zero.

A execução termina quando o tempo acaba, a bateria do robô está esgotada, uma parte do robô excede o limite da imagem, um comando ilegal é enviado ou a comunicação do robô expira.

Sua pontuação é o valor final da carga do robô. O controlador exibirá sua pontuação e a imagem final do mapa. A saída stderr do seu programa é registrada no arquivo robot.log. Se o seu robô morrer, o erro fatal pode estar no log.

Os dados da 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

minha imagem:

teste o meu

A imagem da mina pode ter um canal alfa, mas isso não é usado.

O controlador

O controlador deve funcionar com o Python 2.7 e requer a biblioteca PIL. Fui informado de que o Python Pillow é um download amigável do Windows para obter o módulo de imagem PIL.

Inicie o controlador com o programa do robô, cfg.py, arquivos de imagem e dados no diretório atual. A linha de comando sugerida é:

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

Por exemplo: python controller.py java underminer.class

O controlador gravará um arquivo robot.log e um arquivo finalmine.png no final da execução.

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

O arquivo de configuração vinculado (não deve ser alterado):

# 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 da resposta

As respostas devem ter um título, incluindo linguagem de programação, nome do robô e pontuação final (como Python 3 , Tunnel Terror , 1352 ). O corpo da resposta deve ter seu código e a imagem final do mapa da mina. Outras imagens ou animações também são bem-vindas. O vencedor será o robô com a melhor pontuação.

Outras regras

  • As brechas comuns são proibidas.
  • Se você usar um gerador de números aleatórios, deverá codificar uma semente no seu programa, para que sua execução seja reproduzível. Outra pessoa deve ser capaz de executar seu programa e obter a mesma imagem e pontuação final da mina.
  • Seu programa deve ser programado para qualquer imagem de mina. Você não deve codificar seu programa para esses arquivos de dados ou para esse tamanho de imagem, layout mineral, layout de túnel, etc. Se eu suspeito que um robô está violando essa regra, reservo-me o direito de alterar a imagem da mina e / ou os arquivos de dados.

Editar% s

  • Explicou a regra de resposta de 0,1 segundos.
  • Expandido no robô, iniciando opções e arquivos de linha de comando.
  • Adicionada nova versão do controlador com melhor captura de erro.
  • Adicionada nota robot.log.
  • Dureza e valor mineral padrão explicados.
  • Bateria explicada vs equipamento cortador.
  • Tornou o tamanho do robô 11 explícito.
  • Cálculos adicionados para tempo, desgaste do cortador e bateria.
Cavaleiro Lógico
fonte
2
@TApicella 1. Os robôs obtêm o nome do arquivo da imagem como argumento e podem lê-lo e processá-lo como quiserem. A imagem dos controladores será alterada conforme o robô se move e o robô não poderá ver isso. Os robôs podem usar PIL ou outras bibliotecas de terceiros do OSS. 2. Os robôs têm 2 segundos para inicializar e, em seguida, 0,1 segundo por resposta do comando.
Logic Knight
1
Você deve documentar a resposta de 0,1 segundo por comando na pergunta.
31515 Peter
1
@KeithRandall No. Você deve ler os arquivos de imagem e 2 dados dos nomes de arquivos fornecidos na linha de comando. Eles podem ser alterados.
Logic Knight
1
@ TApicella Adicionei outra resposta com uma estrutura Python que pode ajudar.
Logic Knight
2
É uma característica. Usá-lo para sua vantagem se você pode :)
Logic Cavaleiro

Respostas:

3

Python 2, Sample Miner, 350

Este é um exemplo do código mínimo para um robô de mineração. Apenas desce até a bateria descarregar (todos os robôs começam a apontar para baixo). Ele ganha apenas uma pontuação de 350. Lembre-se de liberar stdout, caso contrário o controlador travará.

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

caminho do mineiro de amostra

Cavaleiro Lógico
fonte
2

Python 2, Modelo de Python para minerador de robôs, 410

Este é um modelo de robô de mineração para mostrar como um robô opera e fornecer uma estrutura para a criação de seus próprios robôs. Há uma seção para analisar os dados minerais e uma seção para responder com ações. Os algoritmos de espaço reservado não se saem bem. O robô encontra alguns minerais valiosos, mas não o suficiente para comprar baterias e cortadores de reposição suficientes. Ele pára com uma bateria descarregada a caminho da superfície pela segunda vez.

Um plano melhor é usar os túneis existentes para se aproximar de minerais valiosos e minimizar a escavação.

Observe que este robô grava um arquivo de log de cada mensagem de status que recebe para que você possa verificar suas decisões após uma execução.

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 da mina

Cavaleiro Lógico
fonte
Muito obrigado, expor o loop que impulsiona a interação entre o controlador e o programa do robô é realmente útil.
TApicella