From 4f1db3116485a460f033be21789f69410cff4c5e Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 27 Sep 2018 08:26:24 +0200 Subject: [PATCH] Added harvest script. --- scripts/harvest/README.md | 41 ++++++ scripts/harvest/harvest.lua | 265 ++++++++++++++++++++++++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 scripts/harvest/README.md create mode 100644 scripts/harvest/harvest.lua diff --git a/scripts/harvest/README.md b/scripts/harvest/README.md new file mode 100644 index 0000000..4a41d12 --- /dev/null +++ b/scripts/harvest/README.md @@ -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--------- + --------- + --------- + --------- + --------- + --------- + --------- + --------- + --------- +``` \ No newline at end of file diff --git a/scripts/harvest/harvest.lua b/scripts/harvest/harvest.lua new file mode 100644 index 0000000..de6d383 --- /dev/null +++ b/scripts/harvest/harvest.lua @@ -0,0 +1,265 @@ +-- 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 = { + ["harvestcraft:pamsoybeancrop"] = { + growth_limit = 0.42, + item_name = "harvestcraft:soybeanitem" + }, + ["harvestcraft:pamspiceleafcrop"] = { + growth_limit = 0.42, + item_name = "harvestcraft:spiceleafitem" + } +} + +-- 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. + 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) + if (fs.exists(filename) and not fs.isDirectory(filename)) then + -- Config file exists. + file = io.open(filename, "r") + local t = serial.unserialize(file:read()) + file:close() + return t + else + print("No config file " .. filename .. "exists. Please create it before continuing.") + return nil + end +end + +--[[ +Guides the user in creating a new config. +--]] +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?") + 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() +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