Your program will control a mining robot searching underground for valuable minerals. Your robot will tell the controller where you want to move and dig, and the controller will provide feedback on your robot status.
Initially your robot will be given an image map of the mine with some mining shafts already present, and a data file specifying the value and hardness of the minerals in the mine. Your robot will then move through the shafts looking for valuable minerals to mine. Your robot can dig through the earth, but is slowed down by hard rock.
The robot that returns with the most valuable cargo after a 24 hour shift will be the winner. It may seem to be a complicated challenge but it is simple to make a basic mining robot (see the Sample Mining Robot answer below).
Operation
Your program will be started by the controller with the mine image, mineral data, and equipment filenames. Robots can use the mine image and minerals data to find valuable ore and avoid hard rock. The robot may also want to buy equipment from the equipment list.
eg: python driller.py mineimage.png minerals.txt equipmentlist.txt
After a 2 second initialisation period, the controller communicates with the robot program through stdin and stdout. Robots must respond with an action within 0.1 seconds after receiving a status message.
Each turn, the controller sends the robot a status line:
timeleft cargo battery cutter x y direction
eg: 1087 4505 34.65 88.04 261 355 right
The integer timeleft
is the game seconds left before the end of shift. The
cargo
is the integer value of the minerals you have mined so far less what you
have paid for equipment. The battery
level is an integer percentage of your
remaining battery charge. The cutter
integer level is the current sharpness of the
cutter as a percentage of the standard value. The x
and y
values are positive
integers with the robot position referenced from the top left corner at (0, 0).
The direction is the current direction the robot is facing (left, right, up,
down).
When your robot receives the 'endshift' or 'failed' input, your program will soon be terminated. You might want your robot to write debugging/performance data to a file first.
There are 4 possible commands the controller will accept. direction
left|right|up|down
will point your robot in that direction, and require 15
game-seconds. move <integer>
will instruct your robot to move or dig that many
units forward which takes time depending on the hardness of minerals cut and
sharpness of your cutter (see below). buy <equipment>
will install specified
equipment and deduct the cost from your cargo value, but only if the robot is at
the surface (y value <= starting y value). Equipment installation takes 300
game-seconds. The special command snapshot
writes the current mine image to
disk and takes no game time. You can use snapshots to debug your robot or create
animations.
Your robot will start with 100 battery and 100 cutter sharpness. Moving and turning use a small amount of battery power. Digging uses much more and is a function of the hardness of the minerals and current sharpness of the cutter. As your robot digs into minerals, the cutter will lose its sharpness, depending on the time taken and hardness of the minerals. If your robot has enough cargo value, it may return to the surface to buy a new battery or cutter. Note that high quality equipment has an initial effectiveness of over 100%. Batteries have the string "battery" in the name and (surprise) cutters have "cutter" in the name.
The following relationships define moving and cutting:
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
Note that moving 1 unit without cutting any minerals takes 1 game second and uses 0.0178 of the battery. So the robot can drive 5600 units in 93 game minutes on a standard 100 charge, if it is not cutting minerals or turning.
NEW: The robot is 11 pixels wide so will cut up to 11 pixels with each pixel of movement. If there are less than 11 pixels to cut, the robot will take less time to move, and cause less wear on the cutter. If a pixel color is not specified in the mineral data file, it is free space of zero hardness and zero value.
The run is terminated when time runs out, the robot battery is exhausted, a part of the robot exceeds the image boundary, an illegal command is sent, or robot communication times out.
Your score is the final value of the robot cargo. The controller will output your score and the final map image. The stderr output of your program is logged in the robot.log file. If your robot dies, the fatal error may be in the log.
The Mine Data
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
mine image:
The mine image may have an alpha channel, but this is not used.
The Controller
The controller should work with Python 2.7 and requires the PIL library. I have been informed that the Python Pillow is a Windows friendly download to get the PIL image module.
Start the controller with the robot program, cfg.py, image and data files in the current directory. The suggested command line is:
python controller.py [<interpreter>] {<switches>} <robotprogram>
E.g.: python controller.py java underminer.class
The controller will write a robot.log file and a finalmine.png file at the end of the run.
#!/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()
The linked config file (not to be changed):
# 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'
Answer Format
The answers should have a title including programming language, robot name, and final score (such as Python 3, Tunnel Terror, 1352). The answer body should have your code and the final mine map image. Other images or animations are welcome too. The winner will be the robot with the best score.
Other Rules
- The common loopholes are prohibited.
- If you use a random number generator, you must hardcode a seed in your program, so that your program run is reproducable. Someone else must be able to run your program and get the same final mine image and score.
- Your program must be programmed for any mine image. You must not code your program for these data files or this image size, mineral layout, tunnel layout, etc. If I suspect a robot is breaking this rule, I reserve the right to change the mine image and/or data files.
Edits
- Explained 0.1 second response rule.
- Expanded on robot starting command line options and files.
- Added new controller version with better error catching.
- Added robot.log note.
- Explained default mineral hardness and value.
- Explained battery vs cutter equipment.
- Made robot size 11 explicit.
- Added calculations for time, cutter wear, and battery.