Added simple schematic builder program.

This commit is contained in:
Florian Nücke 2014-06-02 14:10:46 +02:00
parent 3546e4a846
commit 09cabc902d
4 changed files with 339 additions and 0 deletions

View File

@ -0,0 +1,274 @@
local computer = require("computer")
local event = require("event")
local fs = require("filesystem")
local process = require("process")
local robot = require("robot")
local shell = require("shell")
local args = {...}
if #args < 1 then
print("Please enter a file name with the schematic to build.")
return
end
local file, reason = io.open(shell.resolve(args[1], "plan"))
if not file then
io.stderr:write(reason .. "\n")
return
end
print("Analyzing schematic...")
local counts = {}
local width, height, depth = 0, 1, 0
do
local lx, lz = 0, 0
while true do
local line = file:read()
if line == nil then
width = math.max(width, lx)
depth = math.max(depth, lz)
file:seek("set")
break
end
line = line:gsub("\r", "")
if line:sub(1, 1) == "#" then
height = height + 1
width = math.max(width, lx)
depth = math.max(depth, lz)
lx, lz = 0, 0
else
lz = lz + 1
lx = math.max(lx, #line)
for i = 1, #line do
if line:sub(i, i) == "1" then
counts[lz] = (counts[lz] or 0) + 1
end
end
end
end
end
print("Model bounds: " .. width .. "x" .. height .. "x" .. depth)
local SLOT_DIRT = 1
local SLOT_STONE = 2
local y, z = 0, 0
local offset, count = 0, depth
if #args > 1 then
offset = tonumber(args[2]) or 0
count = tonumber(args[3]) or (depth - offset)
end
local cost = count * height -- the "wall"
for z = offset + 1, offset + count do
cost = cost + (counts[z] or 0)
end
do
local path = fs.path(process.running())
local stateFile = io.open(fs.concat(path, ".state"))
if stateFile then
print("State file found, loading...")
file:seek("set", tonumber(stateFile:read()))
y = tonumber(stateFile:read())
z = tonumber(stateFile:read())
offset = tonumber(stateFile:read())
count = tonumber(stateFile:read())
cost = tonumber(stateFile:read())
stateFile:close()
print("Resuming work on slabs " .. (offset + 1) .. " through " .. (offset + count) .. ".")
print("Continuing at height " .. y .. ", depth " .. z .. ".")
print("There's a total number of " .. cost .. " blocks left to place.")
else
robot.select(SLOT_DIRT)
print("I'll be working on slabs " .. (offset + 1) .. " through " .. (offset + count) .. ".")
print("That'll take a total of " .. cost .. " blocks. Put them in a chest in front of me.")
print("Also put a stack of dirt into my first inventory slot (the selected one).")
end
end
print("Press any key when ready.")
os.sleep(0.5)
event.pull("key")
print("Starting in 3 seconds...")
os.sleep(3)
local function save()
local path = fs.path(process.running())
local stateFile, reason = io.open(fs.concat(path, ".state"), "w")
if not stateFile then
io.stderr:write("Failed saving state: " .. tostring(reason))
return
end
stateFile:write(tostring(file:seek("cur")) .. "\n")
stateFile:write(tostring(y) .. "\n")
stateFile:write(tostring(z) .. "\n")
stateFile:write(tostring(offset) .. "\n")
stateFile:write(tostring(count) .. "\n")
stateFile:write(tostring(cost) .. "\n")
stateFile:close()
print("State saved.")
end
function restock()
local slot = SLOT_STONE
robot.select(slot)
while cost > 0 and slot <= 16 do
-- fill up this slot, stop if we have all we need.
while true do
local count = robot.count(slot)
local space = robot.space(slot)
local wantCount = math.min(cost, space) -- avoid overflow
if wantCount <= 0 then
break
end
robot.suck(wantCount)
local suckCount = robot.count(slot) - count
cost = cost - suckCount
if suckCount == 0 then
-- not enough materials in chest
os.sleep(5)
end
end
slot = slot + 1
end
local energy = computer.energy()
if energy < computer.maxEnergy() * 0.5 then
repeat
os.sleep(1)
if computer.energy() < energy and computer.energy() < computer.maxEnergy() * 0.05 then
print("Emergency shutdown - out of energy!")
save()
print("Shutting down in 3 seconds...")
os.sleep(3)
computer.shutdown()
end
until computer.energy() > computer.maxEnergy() * 0.95
end
end
local function selectStone()
local didPrint = false
while true do
for slot = SLOT_STONE, 16 do
if robot.count(slot) > 0 then
robot.select(slot)
return
end
end
if not didPrint then
print("I need more materials!")
didPrint = true
end
os.sleep(5)
end
end
local function gotoWork()
-- PRE: looks at chest
robot.turnLeft()
repeat until robot.back()
robot.turnRight()
for _ = 1, z do
repeat until robot.back()
end
for _ = 1, y do
repeat until robot.up()
end
robot.turnLeft()
repeat until robot.back()
repeat until robot.back()
-- POST: stands at job pos, facing -x
end
local function moveToChest(force)
-- PRE: stands at job pos, facing -x
local stones = 0
for slot = SLOT_STONE, 16 do
stones = stones + robot.count(slot)
end
if force or (stones < width and cost > 0) or (computer.energy() > 0 and computer.energy() < computer.maxEnergy() * 0.1) then
repeat until robot.forward()
repeat until robot.forward()
robot.turnRight()
for _ = 1, y do
repeat until robot.down()
end
for _ = 1, z do
repeat until robot.forward()
end
robot.turnLeft()
repeat until robot.forward()
robot.turnRight()
restock()
return true
end
end
restock()
gotoWork()
for _ = 1, offset do
file:read()
end
while true do
local row = file:read()
if not row then
moveToChest(true)
return
end
if string.sub(row, 1, 1) == "#" then
-- skip, handled in z == depth below
for _ = 1, offset do
file:read()
end
else
-- PRE: at job pos, facing -x
selectStone()
repeat until robot.place()
local placed = 0
for x = 1, width do
if not string.find(row, "1", x, true) then
break
end
repeat until robot.back()
if string.sub(row, x, x) == "0" then
robot.select(SLOT_DIRT)
else
selectStone()
end
repeat until robot.place()
placed = placed + 1
end
repeat until robot.up()
robot.select(SLOT_DIRT)
for _ = 1, placed do
repeat until robot.forward()
robot.swingDown()
end
-- POST: above job pos, facing -x
if z + 1 < count then
robot.turnLeft()
repeat until robot.forward()
robot.turnRight()
repeat until robot.down()
z = z + 1
if moveToChest() then
gotoWork()
end
else
for _ = 1, depth - offset - count do
file:read()
end
y = y + 1
moveToChest(true)
z = 0
gotoWork()
end
end
end
file:close()
fs.remove(fs.concat(fs.path(process.running()), ".state"))
print("Done!")

View File

@ -0,0 +1,44 @@
NAME
build - build a structure
SYNOPSIS
build PLAN
build PLAN FROM TO
DESCRIPTION
Makes the robot it is run on build a structure based on a schematic in a file.
The schematic file has to be of the following format:
- Each line represents a row to be built.
- Each character must be either a 0 for air, or a 1 for solid.
- A line starting with '#' declares the start of a new layer.
Structures are built bottom-up. Robots are facing 'north' when considering the contents of the schematic a top-down view. Robots must start facing a chest with building materials and with one stack of dirt in their first (top-left) inventory slot. A charger should also be placed next to the robot, to allow it to recharge when at the home position, since if it drops below a certain amount of energy it will remain there until charged again.
The example schematic on the disk containing this program can be used to build a small 'pyramid'. The robot will face the 'entrance' of this pyramid when starting. In other words it will build to its right:
C
ER X110011
X100001
X100001
X100001
X100001
X111111
Where C is the chest, E is the charger and R is the robot. X is a solid wall of the used building material. See the limitations section.
When starting the program the user is prompted for confirmation of the parameters. It will also inform the user of the number of required building blocks.
Robots will return for materials and power every so often. They should - in theory - never run out of energy while not at the docking station (where they start), and even if they do, they save their state and are capable of continuing where they stopped when resumed (just run `build used.plan` again).
LIMITATIONS
This program was written before the Angel Upgrade was introduced, so the dirt is required to fill up air-blocks and is removed again in a second pass. This means a single row can contain at most 64 air blocks. Furthermore, to avoid unnecessary seeking in the schematics file, while moving back the robot tries to remove *every* block, meaning it must *not* have a tool equipped, and the building materials *must not* be removable without a tool - for example, it is impossible to build a 'dirt house' with this program.
Lastly, a solid wall of building material will be raised to guarantee a point of refernce to build on. Since the robot must not have a tool, it cannot remove that wall when done. It has to be removed by the player, or another program after the robot was equipped with an appropriate tool.
EXAMPLES
build example
Builds the schematic defined in file `example` or `example.plan`.
build some.plan 0 2
Builds the vertical slabs 0 through 2 of the schematic defined in file `some.plan`.

View File

@ -0,0 +1,20 @@
110011
100001
100001
100001
100001
111111
#newlayer 2
000000
010010
010010
010010
011110
000000
#newlayer 3
000000
000000
001100
001100
000000
000000

View File

@ -1,6 +1,7 @@
# This file contains the list of floppy disks that can be found as loot.
# Each entry is a mapping of folder name to disk label.
BetterShell=besh
Builder=build
OpenIRC=irc
OpenOS=openos
# Higher chance to find the dig program, because it has the most immediate