Compare commits

...

8 Commits

7 changed files with 895 additions and 0 deletions

View File

@ -0,0 +1,27 @@
# movescript.lua
A more convenient way to move a robot.
## Pastebin
[4c2AN8Jw](https://pastebin.com/4c2AN8Jw)
## Module Requirements
*None*
## Instructions
Simply run `pastebin get 4c2AN8Jw /lib/movescript.lua` to install movescript to the local computer. Then you can begin writing movescripts to tell your robots how to move. For example:
```
local ms = require("movescript")
local my_script = "FFURFFL"
ms.execute(my_script)
```
The above code will make the robot move forward twice, then up once, then turn right, then move forward twice, and finally turn left.
It is also possible to specify the amount of times to perform an action, by giving an integer value before the character for the movement you want. For example:
```
local my_long_script = "10D2R5B3U"
ms.execute(my_long_script)
```

View File

@ -0,0 +1,129 @@
--[[
Author: Andrew Lalis
File: movescript.lua
Version: 1.0
Last Modified: 27-09-2018
Description:
This library enables string representation of robot movement, for easier
robotic control without repeating functions many times.
Begin a script with "d_" to tell the robot to attempt to destroy blocks in the
way of the path of movement.
--]]
local r = require("robot")
local movescript = {}
local destructive = true
local function doUntilSuccess(f)
local success = f()
while (not success) do
success = f()
end
end
local function up()
while (destructive and r.detectUp()) do
r.swingUp()
end
doUntilSuccess(r.up)
end
local function down()
while (destructive and r.detectDown()) do
r.swingDown()
end
doUntilSuccess(r.down)
end
local function forward()
while (destructive and r.detect()) do
r.swing()
end
doUntilSuccess(r.forward)
end
local function back()
if (destructive) then
r.turnAround()
while (r.detect()) do
r.swing()
end
r.turnAround()
end
doUntilSuccess(r.back)
end
local functionMap = {
["U"] = up,
["D"] = down,
["L"] = r.turnLeft,
["R"] = r.turnRight,
["F"] = forward,
["B"] = back,
["P"] = r.place,
["S"] = r.swing
}
--[[
Determines if a string starts with a certain string.
str - string: The string to check the prefix of.
start - string: The prefix to look for.
--]]
local function starts_with(str, start)
return str:sub(1, #start) == start
end
--[[
Executes a single instruction once.
c - character: One uppercase character to translate into movement.
--]]
local function executeChar(c)
local f = functionMap[c]
if (f == nil) then
return
end
f()
end
--[[
Executes a single instruction, such as '15D'
instruction - string: An integer followed by an uppercase character.
--]]
local function executeInstruction(instruction)
local count = string.match(instruction, "%d+")
local char = string.match(instruction, "%u")
if (count == nil) then
count = 1
end
if (char == nil) then
return
end
for i=1,count do
executeChar(char)
end
end
--[[
Executes a given script.
script - string: The script to execute.
--]]
function movescript.execute(script)
if (starts_with(script, "d_")) then
destructive = true
script = string.sub(script, 3)
else
destructive = false
end
while (script ~= nil and script ~= "") do
-- Matches the next instruction, possibly prefixed by an integer value.
local next_instruction = string.match(script, "%d*%u")
executeInstruction(next_instruction)
script = string.sub(script, string.len(next_instruction) + 1)
end
end
return movescript

41
scripts/harvest/README.md Normal file
View File

@ -0,0 +1,41 @@
# harvest.lua
Script for smart harvesting of rectangular farms of multiple crops.
## Pastebin
[ytYCVGsc](https://pastebin.com/ytYCVGsc)
## Module Requirements
* geolyzer
* inventory_controller
* *equipped hoe*
## Instructions
To operate the program, you simply need to run the program. If no `harvest.conf` config file exists, the program will guide you through the creation of it. For it, you'll need the following information:
* On what side does the robot start harvesting (left or right)?
* How many rows are in the field?
* How many columns are in the field?
* What crops will be grown?
For each crop that will be grown, you will need the following pieces of information:
1. The crop's block name (can be found using the `geolyzer` component.
2. The floating point value at which the crop is ready to be harvested. Can also be found with the `geolyzer`.
3. The name of the item used to replant the crop. This can be found in the minecraft inventory after pressing `F3 + H` to enable more detailed information for displayed items.
Once all this information is entered, running the program will harvest the defined area, and drop all gathered items into a chest below the robot's resting point.
### Diagram of setup
The below diagram shows how farms should be set up: the robot faces into the first row, and has a charger behind it to replenish energy after each harvest. A chest or hopper can be placed below the robot for item collection.
```
CR---------
---------
---------
---------
---------
---------
---------
---------
---------
```

261
scripts/harvest/harvest.lua Normal file
View File

@ -0,0 +1,261 @@
-- Harvest Program for robots. Uses a hoe and geolyzer for optimal harvesting.
--[[
Author: Andrew Lalis
File: harvest.lua
Version: 1.0
Last Modified: 27-09-2018
Description:
This script enables a robot to harvest fields of crops quickly and efficiently.
The robot will traverse the field and only harvest crops considered 'done' by
their crop definition.
--]]
local robot = require("robot")
local component = require("component")
local fs = component.filesystem
local serial = require("serialization")
local geolyzer = component.geolyzer
local ic = component.inventory_controller
local sides = require("sides")
local CONFIG_FILE = "harvest.conf"
local LEFT = 1
local RIGHT = 0
-- List of crops which will be harvested.
local crop_definitions = {}
-- Repeats the given function until it returns true.
local function doUntilSuccess(func)
local success = func()
while (not success) do
success = func()
end
end
-- Pre-defined path from turtle docking bay to start of harvest area (first crop).
local function goToStart(rows, columns)
doUntilSuccess(robot.forward)
end
-- Pre-defined path back to the turtle docking bay.
local function goBack(rows, columns)
for i=1,(columns-1) do
doUntilSuccess(robot.back)
end
robot.turnRight()
for i=1,(rows-1) do
doUntilSuccess(robot.back)
end
robot.turnLeft()
doUntilSuccess(robot.back)
end
--[[
Select an item, given its name and damage value.
item_name - string: The id string for an item.
item_data - number: The damage value, or variation of an item. Defaults to zero.
return - boolean: True if at least one slot contains the item. That slot is now
selected.
--]]
local function selectItemByName(item_name, item_data)
for i=1,16 do
local stack = ic.getStackInInternalSlot(i)
if (stack ~= nil and stack.name == item_name and stack.damage == item_data) then
robot.select(i)
return true
end
end
return false
end
--[[
Checks if the hoe is equipped. Meant to be done before starting a harvest.
return - boolean: True if a hoe is equipped, or false if not.
--]]
local function isHoeEquipped()
for i=1,16 do
local item_stack = ic.getStackInInternalSlot(i)
if (item_stack == nil) then
robot.select(i)
ic.equip()
new_item_stack = ic.getStackInInternalSlot(i)
if (new_item_stack ~= nil and string.match(new_item_stack.name, "_hoe")) then
return true
end
return false
end
end
return false
end
--[[
Tries to harvest a plant, if it is one of the crops defined in the crop
definitions table above.
return - boolean: True if a plant was harvested, false otherwise.
--]]
local function harvestPlant()
local plant_data = geolyzer.analyze(sides.bottom)
local crop_definition = crop_definitions[plant_data.name]
if (crop_definition == nil) then
return false
end
if (plant_data.growth >= crop_definition.growth_limit) then
robot.swingDown()
selectItemByName(crop_definition.item_name, 0)
robot.placeDown()
return true
else
return false
end
end
--[[
Harvests one row of crops.
length - int: The number of plants in this row.
return - int: The number of crops that were harvested.
--]]
local function harvestRow(length)
local harvests = 0
for i=1,length do
if (i > 1) then
doUntilSuccess(robot.forward)
end
if (harvestPlant()) then
harvests = harvests + 1
end
end
return harvests
end
--[[
At the end of the row, the robot must rotate into the next row, and this is
dependent on where the start location is.
current_row_index - int: The row the robot is on prior to turning.
start_location - int: Whether the robot starts at the left or right.
--]]
local function turnToNextRow(current_row_index, start_location)
if (current_row_index % 2 == start_location) then
robot.turnRight()
else
robot.turnLeft()
end
doUntilSuccess(robot.forward)
if (current_row_index % 2 == start_location) then
robot.turnRight()
else
robot.turnLeft()
end
end
--[[
Harvests a two dimensional area defined by rows and columns. The robot starts
by moving forward down the first row.
rows - int: The number of rows to harvest.
columns - int: The number of columns to harvest.
start_location - int: 1 for LEFT, 0 for RIGHT.
return - int: The total number of crops harvested.
--]]
local function harvestField(rows, columns, start_location)
goToStart(rows, columns)
-- Begin harvesting.
robot.select(1)
local harvests = 0
for i=1,rows do
harvests = harvests + harvestRow(columns)
-- Do not turn to the next row on the last row.
if (i < rows) then
turnToNextRow(i, start_location)
end
end
goBack(rows, columns)
return harvests
end
--[[
Drops all carried items into an inventory below the robot.
return - int: The number of items dropped.
--]]
local function dropItems()
local item_count = 0
for i=1,16 do
robot.select(i)
local stack = ic.getStackInInternalSlot(i)
if (stack ~= nil) then
doUntilSuccess(robot.dropDown)
item_count = item_count + stack.size
end
end
return item_count
end
--[[
Reads config from a file.
filename - string: The string path/filename.
return - table|nil: The table defined in config, or nil if the file does not
exist or another error occurs.
--]]
local function loadConfig(filename)
-- Config file exists.
local f = io.open(filename, "r")
if (f == nil) then
print("No config file " .. filename .. " exists. Please create it before continuing.")
return nil
end
local t = serial.unserialize(f:read())
f:close()
return t
end
--[[
Guides the user in creating a new config.
return - table: The config created.
--]]
local function createConfig(filename)
local config = {}
print("Does your robot start on the left or right of the field?")
local input = io.read()
if (input == "left") then
config.START_LOCATION_RELATIVE = LEFT
elseif (input == "right") then
config.START_LOCATION_RELATIVE = RIGHT
else
print("Invalid choice. Should be either left or right.")
return nil
end
print("Enter number of rows.")
config.ROWS = tonumber(io.read())
print("Enter number of columns.")
config.COLS = tonumber(io.read())
print("How many crops are being harvested?")
config.crop_definitions = {}
for i=1,tonumber(io.read()) do
print("Crop "..i..": What is the block name? (Use geolyzer to analyze it)")
local name = io.read()
config.crop_definitions[name] = {}
print(" What is the growth threshold for harvesting?")
config.crop_definitions[name].growth_limit = tonumber(io.read())
print(" What is the item name of this crop?")
config.crop_definitions[name].item_name = io.read()
end
file = io.open(filename, "w")
file:write(serial.serialize(config))
file:close()
return config
end
local function main()
local config = loadConfig(CONFIG_FILE)
if (config == nil) then
config = createConfig(CONFIG_FILE)
end
crop_definitions = config.crop_definitions
local harvest_count = harvestField(config.ROWS, config.COLS, config.START_LOCATION_RELATIVE)
local drop_count = dropItems()
print(harvest_count..", "..drop_count)
end
main()

View File

@ -0,0 +1,41 @@
# lumber_farm.lua
Automatically chop an array of spruce trees.
## Pastebin
[dB0XwcAY](https://pastebin.com/dB0XwcAY)
## Module Requirements
* tractor_beam
* inventory_controller
* movescript library [4c2AN8Jw](https://pastebin.com/4c2AN8Jw)
* A lumber axe of obscenely high durability, or unbreakable.
## Instructions
First, install *movescript* to `/lib/movescript.lua`.
Then, download this script, and `edit` the downloaded file to set some constants.
* `ROWS`: The number of rows in the farm.
* `COLS`: The number of columns in the farm.
* `TREE_SPACING`: The number of blocks between trees.
* `DELAY`: The time, in tens of seconds, to wait between chopping and picking up items.
* `move_to_start`: A *movescript* describing how to get from the robot's base station to the first tree.
* `return_from_start`: A *movescript* describing how to get back to the robot's base station from the first tree. Should usually be the opposite of `move_to_start`.
Make sure you have a very powerful lumber axe, or one which is unbreakable, and give it to the robot.
### Farm Setup
The construction of the farm should be as follows:
```
R-1 R-2 R-3
| [T] | [T] | [T] | Column 1
| [T] | [T] | [T] | Column 2
| [T] | [T] | [T] | Column 3
| [T] | [T] | [T] | Column 4
X
```
Where `[T]` denotes a 2x2 tree, `X` denotes the starting location for the robot.
Each tree should be separated from those adjacent to it by `TREE_SPACING` blocks.

View File

@ -0,0 +1,180 @@
--[[
Author: Andrew Lalis
File: TreeFarm.lua
Version: 1.0
Last Modified: 12-06-2018
Description:
This script lets a robot, equipped with a tractor_beam and inventory controller
module, chop trees with a Tinker's Construct lumber axe. It will automatically
stop when it runs out of saplings or bonemeal, or when its axe is close to
breaking. It also can either chop a certain number of trees, or simply chop
until its resources are depleted.
--]]
--Require statements and componenent definitions.
local robot = require("robot")
local component = require("component")
local tractor_beam = component.tractor_beam
local ic = component.inventory_controller
--Runtime Constants defined for this robot.
local SAPLING_NAME = "minecraft:sapling"
local SAPLING_DATA = 0
local BONEMEAL_NAME = "minecraft:dye"
local BONEMEAL_DATA = 15
--Global configuration variables.
--Flag for if program should run until out of resources.
local continuous = false
--[[
Exits the program.
--]]
local function quit()
print("#--------------------------------#")
print("# Tree Chopping Program exited. #")
os.exit()
end
--[[
Select an item, given its name and damage value.
item_name - string: The id string for an item.
item_data - number: The damage value, or variation of an item. Defaults to zero.
return - boolean: True if at least one slot contains the item. That slot is now
selected.
--]]
local function selectItemByName(item_name, item_data)
for i=1,16 do
local stack = ic.getStackInInternalSlot(i)
if (stack ~= nil and stack.name == item_name and stack.damage == item_data) then
robot.select(i)
return true
end
end
return false
end
--[[
Select an item, similar to selectItemByName, but if the item cannot be found,
the user will be prompted to add it to the robot's inventory and press enter to
continue.
item_name - string: The id string for an item.
item_data - number: The damage value, or variation of an item. Defaults to zero.
return - nil: If set to be continuous, then if the item cannot be found, then
the program will exit. If not, it will loop until the item is provided by the
user.
--]]
local function selectSafely(item_name, item_data)
local success = selectItemByName(item_name, item_data)
if continuous and not success then
print("Out of "..item_name..", exiting.")
quit()
end
while not success do
print("Cannot find "..item_name.." in inventory. Please add some, and press enter.")
io.read()
success = selectItemByName(item_name, item_data)
end
end
--[[
Plants a sapling, and if it can't place one at first, loops until it is
possible.
--]]
local function plantSapling()
selectSafely(SAPLING_NAME, SAPLING_DATA)
local success = robot.place()
while not success do
print("Unable to place the sapling. Please remove any blocks in front of the robot, and press enter.")
io.read()
success = robot.place()
end
end
--[[
Repeatedly applies bonemeal to the sapling until either the sapling has grown,
or the robot runs out of bonemeal.
--]]
local function applyBonemeal()
local success, block_type = robot.detect()
while block_type ~= "solid" do
selectSafely(BONEMEAL_NAME, BONEMEAL_DATA)
robot.place()
success, block_type = robot.detect()
end
end
--[[
Uses the robot's axe to chop a tree, and quits if the lumber axe provided has
less than 10% durability.
--]]
local function chopTree()
local durability = robot.durability()
if continuous and (durability == nil or durability < 0.1) then
print("Inadequate tool to chop trees, exiting.")
quit()
end
while (durability == nil) or (durability < 0.1) do
print("Please ensure that a lumber axe with at least 10% durability is equipped in the tool slot, and press enter.")
io.read()
durability = robot.durability()
end
robot.swing()
end
--[[
Uses the tractor_beam module to repeatedly pick up items until there are no
more to pick up.
--]]
local function pickupItems()
local success = tractor_beam.suck()
while success do
success = tractor_beam.suck()
end
end
--[[
Grows a tree by planting a sapling and applying bonemeal until it is grown.
--]]
local function growTree()
plantSapling()
applyBonemeal()
end
--[[
The entire cycle of the farm. Grows a tree, harvests the wood, and picks up
the items.
--]]
local function farmTree()
growTree()
chopTree()
os.sleep(2)
pickupItems()
end
--[[
Main function in which the iterations of the cycle are performed.
--]]
local function main()
print("# Andrew's Tree Chopping Program #")
print("# Copyright 2018 Andrew Lalis #")
print("#--------------------------------#")
print("Please enter the number of trees to chop, or -1 to chop until out of resources.")
local choice = tonumber(io.read())
if (choice == nil or choice == -1) then
continuous = true
print(" Chopping trees until out of resources.")
while continuous do
farmTree()
end
else
print(" Chopping "..choice.." trees.")
for i=1,choice do
farmTree()
end
end
quit()
end
main()

View File

@ -0,0 +1,216 @@
--[[
Author: Andrew Lalis
File: lumber_farm.lua
Version: 2.0
Last Modified: 04-11-2018
Description:
This script will automate the farming of large spruce trees, and will chop and
replant them, but not pick up items, since there are many mod items available
which can do this more efficiently.
The robot should be given an 'unbreakable' tool with 5 reinforced upgrades.
--]]
--Require statements and componenent definitions.
local robot = require("robot")
local component = require("component")
local ms = require("movescript")
local ic = component.inventory_controller
local move_to_start = "5F"
local return_from_start = "5B"
local ROWS = 3
local COLS = 2
local TREE_SPACING = 3
-- Global counter.
local TREES_CHOPPED = 0
--Runtime Constants defined for this robot.
local SAPLING_NAME = "minecraft:sapling"
local SAPLING_DATA = 1
--[[
Select an item, given its name and damage value.
item_name - string: The id string for an item.
item_data - number: The damage value, or variation of an item. Defaults to zero.
min_count - number: The minimum number of items to have.
return - boolean: True if at least one slot contains the item. That slot is now
selected.
--]]
local function selectItemByName(item_name, item_data, min_count)
for i=1,16 do
local stack = ic.getStackInInternalSlot(i)
if (stack ~= nil and stack.name == item_name and stack.damage == item_data and stack.size >= min_count) then
robot.select(i)
return true
end
end
return false
end
--[[
Select an item, similar to selectItemByName, but if the item cannot be found,
the user will be prompted to add it to the robot's inventory and press enter to
continue.
item_name - string: The id string for an item.
item_data - number: The damage value, or variation of an item. Defaults to zero.
min_count - number: The minimum number of items to have.
--]]
local function selectSafely(item_name, item_data, min_count)
local success = selectItemByName(item_name, item_data, min_count)
while not success do
print("Cannot find "..min_count.."x "..item_name.." in inventory. Please add some, and press enter.")
io.read()
success = selectItemByName(item_name, item_data, min_count)
end
end
--[[
Gets the total number of items with the given data.
item_name - string: The id string for an item.
item_data - number: The damage value, or variation of an item. Defaults to zero.
--]]
local function getItemCount(item_name, item_data)
local count = 0
for i=1,16 do
local stack = ic.getStackInInternalSlot(i)
if (stack ~= nil and stack.name == item_name and stack.damage == item_data) then
count = count + stack.size
end
end
return count
end
--[[
return - bool: True if the tree is grown, false otherwise.
--]]
local function isTreeGrown()
local success, str = robot.detect()
return (success and str == "solid")
end
--[[
Plants a sapling, and if it can't place one at first, loops until it is
possible. Assumes the robot is at the starting position:
OO
OO
R
Where O=dirt, R=robot.
--]]
local function plantTree()
local success, str = robot.detect()
if (success and (str == "passable" or str == "solid" or str == "replaceable")) then
return
end
local saplings_needed = 4
if (getItemCount(SAPLING_NAME, SAPLING_DATA) < saplings_needed) then
print("Not enough saplings. Needed: "..saplings_needed..". Add some and press ENTER.")
io.read()
end
selectSafely(SAPLING_NAME, SAPLING_DATA, 1)
ms.execute("2FRP")
selectSafely(SAPLING_NAME, SAPLING_DATA, 1)
ms.execute("LBP")
selectSafely(SAPLING_NAME, SAPLING_DATA, 1)
ms.execute("RP")
selectSafely(SAPLING_NAME, SAPLING_DATA, 1)
ms.execute("LBP")
end
--[[
Uses the robot's axe to chop a tree, and quits if the lumber axe provided has
less than 10% durability.
return - integer: 1 if the tree was chopped, 0 otherwise.
--]]
local function chopTree()
if (isTreeGrown()) then
ms.execute("S")
plantTree()
return 1
end
return 0
end
--[[
Moves to the next tree in a row.
current_col - integer: The current column.
col_count - integer: The total number of columns.
--]]
local function moveToNextTree(current_col, col_count)
if (current_col < col_count) then
ms.execute("d_LFR3FRFL"..(TREE_SPACING - 1).."F")
end
end
--[[
Moves to the next row.
current_row - integer: The row that was just finished.
row_count - integer: The total number of rows.
col_count - integer: The total number of columns.
--]]
local function moveToNextRow(current_row, row_count, col_count)
local script = "d_LFL"..((TREE_SPACING + 2) * (col_count - 1)).."FLF"
if (current_row < row_count) then
script = script..(TREE_SPACING + 2).."FL"
else
script = script.."L"
end
ms.execute(script)
end
--[[
Moves back to the start of the orchard.
row_count - integer: The total number of rows.
--]]
local function moveToOrchardStart(row_count)
ms.execute("d_L"..((TREE_SPACING + 2) * (row_count - 1)).."FR")
end
--[[
Performs a function at each tree in the orchard.
rows - integer: The total number of rows.
cols - integer: The total number of columns.
func - function: The function to execute at each position.
--]]
local function doForEachTree(rows, cols, func)
ms.execute(move_to_start)
for i=1,rows do
for k=1,cols do
func()
moveToNextTree(k, cols)
end
moveToNextRow(i, rows, cols)
end
moveToOrchardStart(rows)
ms.execute(return_from_start)
end
--[[
Chops an array of trees. The robot starts facing the first row.
--]]
local function chopOrchard(rows, cols)
doForEachTree(rows, cols, chopTree)
end
--[[
Reads any given arguments and uses them for program constants instead of
default values.
args - table: the arguments passed to the program.
--]]
local function getSettingsFromArgs(args)
ROWS = tonumber(args[1])
COLS = tonumber(args[2])
TREE_SPACING = tonumber(args[3])
move_to_start = args[4]
return_from_start = args[5]
end
local args = {...}
if (#args == 5) then
getSettingsFromArgs(args)
end
chopOrchard(ROWS, COLS)