Mods/basic robot: Difference between revisions
>Ghoti m (→Farming101: speling) |
>Ghoti m (→Farming101: speling) |
(No difference)
|
Revision as of 09:08, 19 March 2022
basic_robot | |
---|---|
A mod | |
Mod Type | Learning |
Author | rnd |
Latest version | 2017-08-03 |
Forum topic | 15850 |
Source code | GitHub |
ContentDB | N/A |
Technical name | basic_robot |
BASIC_ROBOT is lightweight robot mod for multiplayer.
Those robots can do almost everything (with proper programming, and may need privs).
This is the user-documentation about the mod basic_robot (Forum, github) by rnd.
This page is also intended as an introduction to programming, using the programming-language lua with the robot.
Also, there is a 'live' tutorial-parcour about robots-programs, at the old ROBOTS-server, 46.150.38.198:30000, and now at the new ROBOTS_SKYBLOCK, 46.150.38.60:30000.
Intro
basic_robot is a lightweight programmable robot for minetest, usable in multiplayer.
- 'lightweight': the mod only has 2 main objects: robot and remotecontrol,
- plus 'keyboard'-buttons: blocks in different colors, or with numbers/letters/symbols.
The robot can move around, sense the blocks, dig, build, and much more.
- Since version 12/21a, there is also a remote control.
Users can write programs for the bot in Lua.
The system uses a spawner ("cpu-box") that the player has to place on the ground.
This spawner presents a form for controlling, programming and running the robot ("worker").
Only this worker moves around, and it has a limited range (about 23 nodes from player and spawner).
A keypad from the mod basic_machines
can be set up to start a robot
with a button-push by other players,
and for keyboard-entry.
- This is useful for robots that perform a task for other players, e.g. as a shop, mailbox, chatbot, game, etc.
How to get
Creative:
To obtain a spawner in creative mode, use the command "/giveme basic_robot:spawner".
For a remote control, use the command "/giveme basic_robot:control".
Crafting:
Name | Ingredients | Input → Output | Description | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Robot spawner | Mese Crystals + Steel Ingot + Stone |
|
Used to spawn a robot-worker. | ||||||||||||
Robot remote control | Stick + Mese Crystal |
|
Used to control a robot. |
Controlling Form
Rightclicking the spawner opens a form with buttons and a textbox for writing a program.
- Button "Save" saves the program. This must be done before starting a program.
- Note: Saving is not permanent - there is no file to save to, just a quick syntax-check.
- Also, picking up the spawner clears the textarea with the code.
- So, use cut&paste to save your code to a texteditor, such as notepad.
- Button "Start" creates a robot-worker (on top of the spawner) that starts executing the program.
- The robot looks like a simple box, with a face on its frontside, and an arrow on top.
- Button "Stop" stops the program and removes the robot-worker.
- Entryfield id for controlling several robots.
- Players without the priv 'robot' can only use the ids 1 and 2.
- This means they are only allowed to run 2 robots at the same time.
- Button "Inventory" opens the inventory of the bot.
- The inventory has 8*4 slots, same size as a chest.
- Button "Library" opens a "bookshelf". The robot can read and write books here.
- This can be used for programcode, input and output.
- Button "Help" shows some helptext.
- Press ESC to exit the form.
Notes
Each spawner can only run one bot at a time.
To pick up the spawner, its inventory and library must be empty (like a chest).
- While the robots is running, rightclicking it will also open the form
- (without the Start-button), so you can stop it, and access its inventory.
The code of a program is executed repeatedly, about once every second.
...
Server
There is a server dedicated to robots:
Address[:Port] | MaxPlayers | Version/Game/Mapgen | Name | Description | Flags |
---|---|---|---|---|---|
46.150.38.198:30000 | 15 | 0.4.15/minetest/v7 | ROBOTS | program your own robot | Ded Dmg PvP |
Flags
- Cre - Creative mode
- Ded - Dedicated Server
- Dmg - Damage enabled
- Far - Players can see far away names
- Liq - Finite Liquids
- Pvp - Players can damage other players
- Pwd - Players need to set a password at first connect
- Rol - Rollback enabled
See also: Serverlist and Servers
Robot-commands - Part 1
List of simple commands, to start learning how to use the robot:
- say("Hi") - output text to chat.
- self.label("Hugo") - assign a name to the robot.
- turn.left() - rotates bot 90°.
- turn.right()
- move.forward() - move one step in a given direction, if the path is clear.
- move.backward()
- move.up()
- move.down()
- move.left()
- move.right()
- dig.forward() - get/mine the block in the given direction.
- !! Caution, the robot can dig stuff like its own spawner, chests owned by the player etc. !!
- place.backward("default:dirt") - build a block of the specifed stuff in a given direction.
- p = self.pos() - returns the position of the bot, as a table p with entries for x, y, z.
- Note: in an earlier version of basic_robot, this command was "selfpos()"
- n = read_node.forward() - returns a string with the name of the block in a given direction.
...
Example programs - simple
First, some simple programs with explanations, as an introduction to show how the robot and programming works.
HelloWorld
say("Hi")
turn.left()
The robot will repeatedly say "Hi", while spinning atop the spawner.
To stop it, rightclick the robot or spawner, and press the Stop-button.
- Changing the program while the robot is running has no effect.
HelloWorld1
if (i == nil) then
say("Hi !")
self.label("Hugo")
i = 0
end
i = i+1
turn.left()
say(i)
Outputs "Hi !" (only once), then start counting and turning.
- == is the check to compare the values on both sides, i.e. "if i is-equal-to nil".
- = is an assignment, i.e. "i=0" means "put the value 0 into the variable i".
To calculate i+1, the variable i must have a value assigned first.
And because all the code is executed again every second,
this first assignment must be done only once.
- (Otherwise, we couldn't count to 2)
At the start, no variables exist yet, so we can test for nil ("no-value").
- Note: nil is different from any string or number !
HelloWorld2
if i==nil then i="Hi !"; say(i); i=0 end
i=i+1 say(i)
Same as above, but with multiple statements in one line,
and some variations (e.g. braces and ";" are optional).
- In lua, the value of a variables can be of any type.
- Here, i first is assigned the string "Hi !", and later a number.
A quick way to clear the code in the textarea is control-a, then delete or backspace.
Also, picking up the spawner, and placing it again will clear the code.
CatchMe1
This shows some program that works perfectly well, but still shoots the player's foot :-)
dig.down()
move.down()
The robot keeps digging down, until stopped or until it finds a cave, or an unloaded map-block.
With the first dig, it will also destroy its spawner.
So, to get at the stop-button, the user has to jump into the hole to get at the robot.
(Quickly, otherwise the player also gets falling damage !)
- Leaving the game, and entering again probably also stops the bot...
(Maybe a bug: without the spawner, the inventory of the robot is not accessible)
CatchMe2
dig.up()
move.up()
place.down("default:dirt")
p = self.pos() -- returns a table, with elements x, y, and z
say("Position: " .. p.x .. "," .. p.y .. "," .. p.z)
Now we go up, instead of down.
After stopping the robot, and pressing start again,
there will be a block of dirt above the bot from the first go.
We need to dig away this block so that the bot can move up again.
- ! Caution, check the correct spelling of the blocks you want to build/place !
The command "say()" outputs a single string, but several strings can be concatenated with '..'.
- Numbers are automatically converted to strings for output (e.g. the number in p.x).
With the robot announcing its position, the player can use a command like "/teleport 11,12,13" to get at the top of the column that was built.
- Note: y is used as the height here in minetest.
Clouds fly at a height of about 120.
Use left-shift to sneak to the edge of the block to look down without falling.
Use the command "/home" to get down again.
- Make sure to "/sethome" before teleporting up.
Also, read the documentation, i.e. about "fly", "noclip", and "bones".
Run1
if (i==nil) then say("Demo1"); i=0 end
if read_node.forward()=="air" then
move.forward()
else
turn.right()
end
If nothing ("air" mean nothing solid) is in front of it, the robot moves forward, otherwise it turns.
The robot can "fly" one block above the ground, like the player can reach by jumping up.
The bot just doesn't need to bounce up & down.
That means, if the robot reaches a place where the ground one step ahead is more then one block lower, he cannot move forward.
With the above program, he just stops moving, because there are no instructions in the code to handle that case.
Run2
local function report_pos(msg)
p = self.pos()
say(msg .. " position x=" .. p.x .. " y=" .. p.y .. " z=" .. p.z)
end
--
if (i==nil) then say("\nDemo2"); i=0 end
--
n = read_node.forward()
if n~="air" then
report_pos("Now at")
say("found "..n..", turning")
turn.right()
else
if move.forward() then
i=i+1 -- move was ok
else
report_pos("cannot move at")
--self.remove() -- stop program, and remove robot
end
end
Notes
- ".." is the lua-command for concatenating strings.
- "\n" is newline.
- "~=" is the check for not-equal.
- "--" starts a comment. Text after this until the end of the line is ignored.
- Here, we have also commented-out the statement "self.remove()",
- so that it doesn't get executed.
This is essentially the same program as 'Run1' above, but with more checking and reporting:
now there is a check if the move was successful, and output to report the position.
Because we want to report the current position of the bot at more then one place in the code,
the common instructions for that have been moved into a function.
When called, the function gets a string as a parameter, that is used in the message that is generated.
There is no return-statement at the end of the function,
because we don't need a result.
New pages
...
...
- Mods/basic_robot/Programs - Programs for support/maintainance/admins
...
Remote-Control
Intro
A remote control (rc for short) can be used to give orders to a robot.
It has 2 modes:
- manual (leftclick) - without any programming, it shows a form with buttons for moving, turning and digging.
- programming (rightclick) - shows a small textarea for entering a few lines of code, and a SAVE-button.
Those remote controller are very useful, see the extra page Mods/basic_robot/RC for more.
But now, for a short demonstration, get one spawner ("/giveme basic_robot:spawner" )
and 2-3 remotes ("/giveme basic_robot:control 2").
Place the spawner on the ground, and the remotes on the hotbar.
Rightclick the spawner, and enter this program:
RC0
--Spawner
if (i==nil) then say("RC-demo"); i=0; talk=1 end
i = i+1
if talk>0 then say(i) end
Press SAVE, then press START.
The robot appears on top of the spawner, greets, and starts to count.
Now select the first rc and leftclick: the form for 'manual'-mode appears,
with buttons to steer the robot around.
Try it. To stop, press ESC.
- Note: "Left" means move-left, "TLeft" means turn-left. "Go" means move-forward.
Now rightclick the rc, without pointing at some node.
- The programming form pops up, with a textarea and a SAVE-button.
Enter a simple program, like this line of code:
RC1
say("Hi")
Then press SAVE, and (with this rc still selected) leftclick.
Instead of the manual-mode-form with buttons popping up as before,
the robot executes the line of code entered into the form, once.
In the chat-text a line with "Hi" should appear between all that counting.
Rightclick the rc again, and change the code in the textarea to:
RC2 - ReportPosition
p = self.pos() say("Step="..i.." Position x="..p.x.." y=".. p.y.." z=".. p.z)
Save, then leftclick - the robot should report its position,
as well as the running counter from the program RC0 above.
(... Todo: rightclick with remote on a node ...)
With another 'empty' rc you can steer the robot via the manual-buttons,
and by selecting and clicking the rc with the code, you get the report.
Now we use another rc to to deactivate the "say"-instruction in RC0, to stop cluttering the chat:
RC3 - Talk OnOff
if talk>0 then talk=0 else talk=1 end --say("talk="..talk)
This switches the contents of variable talk with each leftclick,
and so controls the output from RC0.
RC4 - Paving/bridgebuilding
Paving one step in front of the robot:
s="default:dirt"
--s="default:cobble"
dig.forward_down()
place.forward_down(s)
move.forward()
This also works on water, or thru the air, to build bridges.
The robot needs building-material in its inventory.
Example programs - not-so-simple
Now, we show some programs that might be actually useful.
Build1 - Fence
This program builds a small, simple fences around the spawner, with a gate on one side.
Here, the idea is to divide the whole work ("build a complete fence")
into a single, simpler task ("build one corner") that just needs to be repeated.
local function init(progname)
if (stat==nil) then
say(progname)
i=0 stat="ok" w=0
s1="default:fence_wood"
s2="doors:gate_wood_closed"
--turn.right()
move.forward()
turn.left()
end
end
local function work()
w=w+1 say("working on part #"..w)
move.forward()
place.down(s1)
turn.left()
move.forward()
place.down(s1)
end
init("Build-a-fence")
if w==3 then
dig.down()
place.down(s2)
end
if w>=4 then
stat="Done." say(stat) self.remove()
else
work()
end
The instructions in work() do one corner of the fence.
After executing it four times, the fence is done.
The fence-gate is placed after the 3rd corner, replacing one of the fenceposts.
- The move&turn-instructions in init() get the bot to a proper starting position.
Build2 - Fence2
More fences, and a program more organized.
local function init(progname)
if (stat==nil) then
say("\n"..progname)
i=0 stat="ok" w=0 debug=0
s1=0 stuff1="default:stone"
s2=0 stuff2="default:wood"
s3=0 stuff3="doors:door_wood"
s1=0 stuff1="default:fence_wood"
s2=0 stuff2="doors:gate_wood_closed"
s3=0 stuff3="flowers:rose"
--turn.right()
move.forward()
turn.left()
end
end
local function report_pos(msg)
p = self.pos() -- table p with entries for x,y,z
say(msg.." Position x="..p.x.." y=".. p.y.." z=".. p.z)
end
local function _say(msg)
if debug>0 then say(msg) end
end
local function work(s)
if (stat~="ok") then return end
--
w=w+1
_say("work#"..w.." : "..s)
s=string.lower(s)
i=0
while #s>0 do
c1= string.sub(s,1,1) -- get first char from workstring
i=i+1
if (c1=="f") then move.forward() end
if (c1=="u") then move.up() end
if (c1=="d") then move.down() end
if (c1=="r") then turn.right() end
if (c1=="l") then turn.left() end
if (c1=="-") then
n = read_node.down() _say("dig: "..n)
dig.down()
end
n = read_node.down() _say(i.." : "..n)
if n == "air" then
if (c1=="1") then place.down(stuff1) s1=s1+1 end
if (c1=="2") then place.down(stuff2) s2=s2+1 end
if (c1=="3") then place.down(stuff3) s3=s3+1 end
else
if c1=="3" then _say("! cannot place : "..c1.." over "..n) end
end
s = string.sub(s,2) -- remove first char from workstring
end --while
end
init("UniBuilder v0.4")
--work("F-LF-") --cleanup
work("F1LF1 ") --build
if w==2 then work("-2") end
--if w==2 then work("-") end --cleanup
if w>4 then work("FRF") w=0 end
_say("w="..w.. " s1="..s1.." s2="..s2)
if s1>=28 and s2>=4 then
work("RF3")
say("Done.") self.remove()
end
Notes
This needs at least 7x7 clear spaces of flat terrain.
The program builds some fences and gates.
It is a variation of the above program "Build1",
building the same fence 4 times at different places.
The code is organized into several functions:
- init() - set up variables, assign values, announce the program-name
- report_pos() - report the position of the bot
- _say() - a wrapper around say(), for easy switching on/off printing messages while testing
- work() - defines one-letter-shortcuts for moving, digging, building etc.
- Note that this work()-function has no fixed workload as in the previous Build1,
- and gets a string with commands as a parameter.
After the functions follows the actual logic for building the fences.
- There are some commented-out "cleanup"-statements to undo the built, and remove the fence.
- (You need to stop the robot by hand when running a cleanup)
Fence-gates don't align themselves with the nearby fenceposts,
and the robot cannot change that.
So the player has to use a screwdriver to finish this
(or dig & rebuild the mis-aligned gates).
Build3 - Instant shelter
The robot builds a shelter around the spawner, like a phone-booth.
if not pn then pn="Instant shelter v1.7" say(pn)
--pickup(8)
i=0
m0="default:dirt"; m1="default:glass"; m2="default:cobble"
m8="default:sand"; m9="default:torch"
mS=m0; mC=m2 -- material for sides and corners
end
i=i+1; --say(i)
if i<100 then
dig.forward(); move.forward()
place.right(mC)
move.backward()
place.forward(mS)
turn.left()
end
if i== 4 then move.up() place.down(m8) mS=m1 end
if i== 8 then move.up() place.down(m8) mS=m0 end
if i==12 then
place.up(m1);
i=100
end
if i==101 then
dig.down(); move.down()
place.up(m9);
end
if i>=102 then
dig.down(); -- !! needs check for spawner
say(pn .." done."); self.remove()
end
--
Usage: if you need some protection NOW, set the spawner on the ground,
stand on it, put the building-materials into the spawner, start the program
and the robot builds the shelter around you and the spawner.
In the picture, the side are built with dirt, to make it easier to dig out.
- But you could also use the robot for digging, if you had no pickaxe.
Build4 - House1
Now we try to build a house.
A hut. Ok, it's a really small hut :-)
local function init(progname)
if (i==nil) then
say(progname)
self.label(progname)
i=0 stat="ok"
draw=0
stuff="default:stone"
stuff="default:dirt"
end
end
local function work(s)
say("work: "..s)
s=string.lower(s)
i=0
while #s>0 do
c1= string.sub(s,1,1) i=i+1
say(c1)
if (c1==".") then say("Stop.") self.remove() end
if (c1=="-") then draw=0 end
if (c1=="+") then draw=1 end
if (c1=="u") then move.up() end
if (c1=="d") then move.down() end
if (c1=="l") then move.left() end
if (c1=="r") then move.right() end
if (c1=="<") then turn.left() end
if (c1==">") then turn.right() end
if (c1=="f") then
move.forward()
if draw>0 then place.backward(stuff) say(stuff) end
end
s = string.sub(s,2)
end
end
init("UniBuilder v0.7")
work("fd<F")
work("+F>ffff>FFFF>ffff>ff-uF")
work("+F>ffff>FFFF>ffff>ff-uF")
-- ?? execution limit
BTW, to see some 'happy accidents', replace the last lines with this:
work("fd<F>") work("+fff>FF>fff>f-uF")
Notes
Essentially, we use the functions from the above program "Build2",
and write some new logic for building a house instead of a fence.
The procedure is quite similar: the robot moves around like the shape of the walls,
and builds a block after each move.
But now it also needs to move up, and repeat this for each layer of the wall.
Finally, some extra processing to close the roof.
Build5 - another housebuilder
Code
local function init()
if i~=nil then return end
--
progname="Test"
prg="!>>>>."
--
progname="Build hut 1x3, v0.1"
prg = "!<fd>"
prg = prg .. "+fff>FF>fff>uFF>"
prg = prg .. "+fff>FF>ffuf>F,F>"
prg = prg .. "+f&f&f>FF>fu-."
stuff="default:dirt"
--
progname="Hut-Builder 3x4, v0.2"
prg = "!<fd+f>"
prg = prg .. "FFFFF>ffff>"
prg = prg .. "FFFFF>fuf-F+f>"
prg = prg .. "FFFFF>ffff>"
prg = prg .. "FFFFF>"
prg = prg .. "uffff>" .. "F&F&F&F&F>"
prg = prg .. "ff&f&f>" .. "F&F&F&F&u"
prg = prg .. "->f_f>" .. "_f_."
stuff="default:stone"
i=0 cs=0 stat="ok"
draw=0 raze=0
end
local function report_pos(msg)
p = self.pos() -- table p with entries for x,y,z
say(msg.." Position x="..p.x.." y=".. p.y.." z=".. p.z)
end
-- get first char from prg, and do 1 step
local function work1()
s=string.lower(prg)
if #s>0 then
c1= string.sub(s,1,1)
i=i+1
say(i.." work1: "..s.." : "..c1)
if (c1==".") then say("Stop.") self.remove() end
if (c1=="!") then
--say(progname)
self.label(progname)
report_pos(progname.." now at")
end
if (c1=="+") then draw=1 end
if (c1=="-") then draw=0 end
if (c1=="(") then raze=1 end
if (c1==")") then raze=0 end
--$%& ^|_
if (c1=="$") then place.front(stuff) cs=cs+1 end
if (c1=="%") then place.left( stuff) cs=cs+1 end
if (c1=="&") then place.right(stuff) cs=cs+1 end
if (c1=="^") then place.up( stuff) cs=cs+1 end
if (c1=="|") then place.down( stuff) cs=cs+1 end
if (c1=="_") then place.forward_down(stuff) cs=cs+1 end
-- /() =? * #' ,;.:
if (c1=="'") then dig.up() end
if (c1==",") then dig.down() end
if (c1==":") then dig.right() end
if (c1=="*") then dig.forward() end
if (c1==";") then dig.forward_down() end
if (c1=="l") then move.left() end
if (c1=="r") then move.right() end
if (c1=="<") then turn.left() end
if (c1==">") then turn.right() end
if (c1=="f") then
if raze>0 then dig.forward() end
move.forward()
if draw>0 then place.backward(stuff) cs=cs+1 end
end
if (c1=="u") then
if raze>0 then dig.up() end
move.up()
if draw>0 then place.down(stuff) cs=cs+1 end
end
if (c1=="d") then
if raze>0 then dig.down() end
move.down()
if draw>0 then place.up(stuff) cs=cs+1 end
end
prg = string.sub(s,2)
end
end
init()
work1()
Notes
Running Build3 revealed some problems with the sandbox of basic_robot.
So, this is an alternative approach, with a simplified, restructured program:
- init() -- only runs once, to do all the setup.
- work1() -- runs once every tick, and does one step of the program in prg.
We still use the basic idea of a 'turtle' / l-system, that is used as a pen/brush for drawing,
that is controlled with a program consisting of 1-character-commands.
But now, we process only one command per 'tick'.
This could be optimized a bit, e.g. some commands don't need time,
so a second command could be done during the same tick.
Command-Overview
Upper- and lowercase are considered the same,
- so both "ffffffffff" and "fffFFfffFF" do the same.
- F,B, U,D, R,L - Move forward,backward, up,down, right,left
- <,> - Turn left, right
- +,- - Switch building on/off
- (,) - Switch digging on/off
- ! - Announce position
- . - End program
- ' , * ; - Dig up, down, forward, forward-down
- $ % & ^ | _ - Build forward,left,right, up,down,forward-down
- 0,1,2, etc. - Select material to build (not used yet)
- :,[,],/ - reserved for future use (e.g. subroutines)
Building the house
The entrance will be at the position of the spawner,
and the house/hut will be built in the direction the robot spawns.
Building the program
The easy way to develop a building-program is by using a remote, and copy/paste.
We start with a short program in init(), like
prg = "!<fd+"
that lets the bot just climb down from the spawner, and turns on 'drawing' = building.
As this program is run, chars are removed from the front,
and the corresponding commands executed.
When all chars have been used (and the program has not ended with "."),
the bot just sits there, waiting.
Now we use a remote to give the bot the next few instructions to build:
Rightclick the remote (without pointing at the bot or spawner),
and enter into the textbox
prg="f>FFFFF>"
and click the SAVE-button.
Then leftclick the bot with the remote,
to let him execute the commands from the textbox.
Watch the bot, correct any errors using an 'empty' remote.
Continue with the next few building-instructions, e.g.
prg="ffff>"
Etc. until the building is complete.
Copy&paste all the snippets into an editor, make a single string from it, e.g.
prg = "!<fd+f>" prg = prg .. "FFFFF>ffff>" .. "FFFFF>fuf-F+f>" prg = prg .. "FFFFF>ffff>" .. "FFFFF>" .. "uffff>" prg = prg .. "F&F&F&F&F>" .. "ff&f&f>" .. "F&F&F&F&u" prg = prg .. "->f_f>" .. "_f_."
and finally put that finished building-string into init().
Example programs - useful
Now some programs that might be actually useful during play.
Farming
Programs that help with farming.
Sapling-Mill
When a player picks up leaves, or a leave decays,
there is a small chance (about 5%) that it drops a sapling.
So, when harvesting a tree, a player can normally expect to find 1-2 saplings.
This program just repeats placing and picking up leaves,
until it has no more leaves in its inventory.
-- Mike's sapling-mill v1.0
-- Turn leaves into saplings.
leaves = "default:jungleleaves"
leaves = "default:pine_needles";
leaves = "default:leaves"
dig.forward() -- first dig & collect
pickup(8) -- collect saplings that dropped on the ground
if not place.forward(leaves) then say("empty !") self.remove() end
turn.left()
The mill stops when the place-command fails, i.e. it has run out of leaves.
- But that command can also fail for other reasons.
Note: For proper operation, put some leaves into the robot's inventory before starting.
and set/uncomment the matching statement 'leaves="..."'.
- (When running as admin or in singleplayer, it might work even without leaves:)
Sapling-Mill 2
A more advanced version of the sapling-mill:
if not s then s="Sapling-mill v1.2c" -- say(s)
fail=0
--leaves = "default:grass" --> wheat-seeds / server-error ??
--leaves = "default:grass_1" --> wheat-seeds
--leaves = "default:junglegrass" --> cotton-seeds
--leaves = "default:acacia_leaves" --> acacia-tree
--leaves = "default:aspen_leaves" --> aspen-tree
--leaves = "default:pine_needles" --> pine-tree
--leaves = "default:jungleleaves" --> jungle-trees
leaves = "default:leaves" --> sappling for apple-trees
end
dig.forward() -- first dig & collect, then place
pickup(8) -- collect saplings on ground that dropped after dig
if place.forward(leaves) then
fail=0
else
fail=fail+1
end
turn.left()
if fail>4 then
say(s.." done with "..leaves) self.remove()
end
Here we have a more complete list of leave-types,
and the mill keeps running until all placed leaves
have been collected again,
and tried again, until they all have been used up.
...
Farming101
Harvesting and re-planting wheat, cotton etc. gets tedious pretty fast, so let's automate that.
First, we try a simple 'remote lawnmover'-mode.
This uses one robot, with no program, and two remote-controllers.
- RC#1 is 'empty', and is used to steer the robot around.
- RC#2 gets programmed (rightclick) with this code :
ripe="farming:wheat_8"; seed="farming:seed_wheat"
if read_node.forward() == ripe then dig.forward() end
if read_node.forward() == "air" then place.forward(seed) end
move.forward()
- Note: on the ROBOTS-server, farming also needs fertilizer.
- Placing fertilizer is not included in this program !
After manoeuvring the robot to the first ripe fruit with RC#1,
we use RC#2 (with leftclick) to move the bot forward while harvesting & planting.
This will only harvest ripe fruits, and only plant seeds on empty spaces.
But it does not check if that space has wet farming soil.
The advantage is, harvesting is now somewhat automated, we can use any terrain,
and we don't need to build walls or fences around the field.
The disadvantage is, this is still a lot of work, and gets tedious again.
Farming102
For a more 'hands-off' mode of harvesting, we need to automate the movement of the robot.
One way is to build a wall or fence around the field, and the robot turns around if it hits that wall.
...todo...
...
Farming103
Harvesting wheat, cotton etc. all works the same,
because the plants grow directly on the ground,
and can be harvested with just one action.
Harvesting trees is harder, because the robot has to move
in the 3rd dimension to get at all parts.
Also, different trees grow in different forms.
...todo...
See also Tutorial - Tree-Harvester,
Mining
Programs that help with mining.
Mining101 - simple digger1x1
This program digs a 1x1 tunnel straight ahead.
The drops of the mined nodes (i.e. the resulting ore) go to the storage of the robot.
Place the spawner at the place where you want to start digging, and press the start-button.
-- simple digger1x1
if not i then -- Init:
i=0
turn.left() -- initital positioning: aim robot in a different direction
--move.up()
end
-- Work:
i=i+1 say(i)
if i<=20 then
dig.forward()
move.forward()
end
The robot spawns facing in a fixed direction,
to have him dig in some other direction
we need to turn him in the init-part of the program.
- Or use a remote-control for that.
The counter is just to ensure that the bot doesn't run away too far.
To get a walkable tunnel, just run the program a 2nd time,
with the command "move.up()" uncommented.
Mining102
-- simple digger2x1
if not i then -- Init:
i=0
turn.left() -- initital positioning: aim robot in a different direction
--move.up()
end
-- Work:
i=i+1 say(i)
if i<=20 then
dig.forward()
-- ??
dig.up() -- !! doesn't work with setting 'maxdig=1'
move.forward()
else
say("stop") self.remove()
end
--
Same as above, but now we dig a walkable 2x1 tunnel in one pass.
...
Mining103 - tunnel 2x1
Digging a 2x1 tunnel forward.
This is another variant with turtle-like movement, using a string of 1-character-commands.
Code
-- rnd 2017-01-23 / hajo 2017-01-26
if not commands then
--turn.left() -- aim robot in a different direction
--move.up()
script = "DuDdf" -- Variant 1 - uses only dig-forward
script = "^Df" -- Variant 2: DigUp, DigForward, MoveForward
commands = {
["f"] = function() move.forward() end,
["b"] = function() move.backward() end,
["l"] = function() move.left() end,
["r"] = function() move.right() end,
["u"] = function() move.up() end,
["d"] = function() move.down() end,
["<"] = function() turn.left() end,
[">"] = function() turn.right() end,
["D"] = function() dig.forward() end,
["^"] = function() dig.up() end,
}
i=1; n=string.len(script)
end
c=string.sub(script,i,i); commands[c]();
if i>=n then i=1 else i=i+1 end
Notes
commands is an array containing functions, indexed by characters.
These functions contain the lua-code to execute for that (turtlecommand-)character.
script is a string that contains the (turtle-)instructions for the bot.
With each tick, one char from the script is extracted,
and the corresponding function from commands is executed.
Building
Programs that build something.
BoxHouse - BasicHousing
This is a house-builder that uses the keyboard-blocks of the robot.
The advantage is, the house doesn't need any materials.
The disadvantage is, the result looks like a plastic toyhouse :)
if not pn then pn="BoxHouse 0.5b" -- hajo, 2017-03-03
say(pn)
pos = self.spawnpos();
n=2; h=2 -- !! set size here !!
c=2 -- 1=white 2=grey 3=red 4=green 5=blue 6=yellow
y= h; c=3; -- roof
for z=-n,n do for x=-n,n do
keyboard.set({x=pos.x+x,y=pos.y+y,z=pos.z+z}, c)
end end
y= -1; c=2; -- base/floor
for z=-n,n do for x=-n,n do keyboard.set({x=pos.x+x,y=pos.y+y,z=pos.z+z}, c) end end
function wall(x,y,z)
w=0
if x==0 and y==0 and z==0 then return -1 end -- don't overwrite spawner !!
if y<0 then return 4 end -- 2:grey, base
if y>=h then return 3 end -- 3:red, roof
a=math.abs(x); b=math.abs(z)
if y==1 and (a==0 or b==0) then return 0 end -- door/window
if a==n and b==n then return 5 end -- 5:blue, corners
if a==n then return 6 end -- 6:yellow, E,W walls
if b==n then return 1 end -- 1:white, N,S walls
return w
end
for y=0,h do for z=-n,n do for x=-n,n do
w=wall(x,y,z); c=w; --say(x.."/"..y.."/"..z.. " --> "..w)
--c=0 -- 0:air, for cleanup
if w>0 then keyboard.set({x=pos.x+x,y=pos.y+y,z=pos.z+z}, c) end
end end end
say("done"); self.remove()
end
Notes
Usage:
Put the spawner at the intended center of the house,
set the size (n=1 .. n=4), and start.
- Any material at the building-site gets overwritten with the key-blocks,
- so if you need some of that dirt, dig it out before starting.
If you uncomment the 'cleanup'-line, a (misbuild) house will be removed.
Note: this works correctly in singleplayer, and for admin.
Otherwise, it stops after some blocks with 'execution limit exceeded'.
Settings:
h is the height of the roof. The spawner is at y=0, the floor at y=-1.
n is the size, and is used as -n .. +n, with the spawner at x=0, z=0.
n | inner size | outer size | remarks |
---|---|---|---|
1 | 1x1 | 3x3 | see also 'Instant shelter' |
2 | 3x3 | 5x5 | 'Basic housing' |
3 | 5x5 | 7x7 | 'Decent housing' |
4 | 7x7 | 9x9 | 'Roomy housing' |
There is a big x/y/z - loop over the whole size of the house,
and the function wall(x,y,z) returns a code that decides
what type/color of block to build at every location.
Each wall has one hole/window at the center,
and you can decide yourself
where to dig another block, to make it into a door.
Misc
Misc. useful programs.
Simple Shop
This is a simple shop, to be used on a server in multiplayer.
The shop would be set up in a secured area (e.g. in range of a protector),
and we also need a normal chest and a keypad (from the mod basic_machines).
Also, some lights, and placing a sign for announcing the shop should be considered.
--say("Shop out of order ... check back soon !"); self.remove()
--
if not pn then pn="SimpleShop v0.2" -- Hajo, 2017-03-03+
say(pn)
p=find_player(4)
if p==nil then s="Hello !" else s="Hello "..p[1].." !" end
say(s)
--
-- m1="default:torch"; m2="default:dirt" --Test
m1="default:sword_steel" -- Example 1: sell 1 sword for 1 gold
m2="default:gold_ingot"
--m1="basic_machines:keypad" -- Example 2: sell 1 keypad for 2 steel
--m2="default:steel_ingot 2"
ok1 = check_inventory.self(m1,"main") -- look at robot's inventory
if not ok1 then say("Out of stock: "..m1); self.remove() end
ok2 = check_inventory.up(m2,"main") -- look at chest above robot
if not ok2 then say("No payment in chest ("..m2..")"); self.remove() end
say("Selling "..m1.." for "..m2)
take.up(m2)
insert.up(m1)
say("Thanks for your purchase !");
end
The robot's inventory stores the items to be sold, as well as the payments.
The chest is for the items to be exchanged,
and the keypad-button activates the shop.
This shop simply exchanges one item from the chest for one other item from the robot's inventory.
- But it would be easy to change the logic to any combination of items.
Robot-commands - Part 2
Date- and time commands:
- os.time() -- returns the current date and time, coded as the number of seconds since the epoch.
- os.date ([format [, time]]) -- Returns a string or a table containing date and time, formatted according to the given string format.
- See manual at Lua.org or Lua-users.org
- os.clock() -- returns cpu-time used
- os.difftime (t2, t1) -- Returns the difference, in seconds, from time t1 to time t2 (where the times are values returned by os.time)
Time, date & clock
s=os.date() -- default-format
say("os.date="..s)
s=os.date("%Y-%m-%d %H:%M") -- Year, month, day, Hour, Minute
say("os.date: "..s)
-- from rosettacode.org:
function duration (secs)
local units, dur = {"wk", "d", "hr", "min"}, ""
for i, v in ipairs({604800, 86400, 3600, 60}) do
if secs >= v then
dur = dur .. math.floor(secs / v) .. " " .. units[i] .. ", "
secs = secs % v
end
end
if secs == 0 then
return dur:sub(1, -3)
else
return dur .. secs .. " sec"
end
end
s=os.time() -- time since jan 1, 1970
say("os.time="..s)
d=duration(s)
say("--> "..d)
s=os.clock() -- time since program started
say("os.clock="..s)
say("==>".. duration(s) )
self.remove()
This program outputs lines like
- os.date=2017-01-26 13:14
- os.time=1485465321
- os.clock=123.456 --> 2 min, 3.456 sec
The function duration() converts these numbers into weeks, days, hour, minutes.
Robot-commands - Part 3
Commands for inventory-access :
- pickup(8) - pick up items from the ground within range
- e.g. saplings that dropped while harvesting trees
- take.forward(item) - take items from a chest in the given direction
- insert.forward(item) - put item into a chest
- check_inventory.forward() - check an inventory for an item:
- ok=check_inventory.forward(item,inventory) - returns true if item exists
- item=check_inventory.self("",inventory,index) - returns item at index-position
Inventory-Lister1
This program can list the robot's own inventory (inside the spawner),
or the contents of a chest.
if not pn then pn="Inventory-Lister1"; say(pn)
move.forward() move.down() --turn.left() -- move into position, to look at chest
i=0
inv="main" -- furnace also has an inventory-slot "fuel"
free=0
end
i=i+1 --say(i)
m=check_inventory.forward("",inv,i) -- look at chest in front of robot
--m=check_inventory.self("",inv,i) -- look at robot's own inventory
if m=="" then
free=free+1
else
say(i..": "..m)
end
if i>=32 then
say(free.." free slots")
self.remove()
end
This version is quite slow, because it checks one slot per tick.
Inventory-Lister2 - Fast
if not pn then pn="Inventory-Lister2"; say(pn)
--move.forward() move.down() --turn.left() -- move into position, to look at chest
inv="main"
free=0
for i=1,32 do
m=check_inventory.self("",inv,i) -- look at robot's own inventory
m=check_inventory.forward("",inv,i) -- look at chest in front
if m=="" then free=free+1 else say(i..": "..m) end
end
say(free.." free slots")
end
self.remove()
This version runs faster, but might crash because of the sandbox-restrictions.
Chest-Mover
Get all items from a chest, and put them into another chest.
For reorganizing, when you have many chests filled with stuff.
Chest-Mover1
Part1: move to chest#1, and take out all items.
- That is, move them into the robot's own inventory.
if not pn then pn="Chest mover1"; say(pn)
--get robot next to chest#1:
turn.left()
move.forward() move.down()
move.forward() turn.right()
inv="main" free=0 c=0 i=0
end
i=i+1 --say(i)
--m=check_inventory.self(s,i)
m=check_inventory.forward("",inv,i)
if m=="" then
free=free+1
else
take.forward(m); c=c+1
--say(i..": "..m)
end
if i>32 then
say("Taken:"..c..", free:"..free)
--self.remove()
end
Chest-Mover2
Part2: move to chest#2, and put all items from the robot's inventory into that chest.
if not pn then pn="Chest mover2"; say(pn)
--get robot next to chest#2:
--turn.left()
move.forward() move.down()
move.forward() turn.left() -- move.forward()
inv="main"; free=0; c=0; i=0
end
i=i+1 --say(i)
m=check_inventory.self("",inv,i)
--m=check_inventory.forward("",inv,i)
if m=="" then
free=free+1
else
ok=insert.forward(m)
c=c+1
--[[
if ok then
say(i..": "..m)
else
say(i.." error: "..m); self.remove()
end
--]]
end
if i>32 then
say("Done:"..c..", free:"..free)
--self.remove()
end
Obviously, the move-commands need to be adjusted for the locations of the chests.
Chest-Mover3 / DropBox
Swap contents of box with robot's inventory
if not pn then pn="DropBox v0.3"; say(pn)
pickup(8)
inv="main"
free=0
m=check_inventory.forward("",inv,1) -- look at slot1 of chest in front
if m=="" then
say("chest is empty") -- move robot's inventory to chest
for i=1,32 do
m=check_inventory.self("",inv,i)
if m=="" then
free=free+1
else
-- say(i..": "..m)
insert.forward(m)
end
end
else
say("1: "..m)
say("getting chest")
for i=1,32 do
m=check_inventory.forward("",inv,i) -- look at chest in front
if m=="" then
free=free+1
else
--say(i..": "..m)
take.forward(m)
end
end
end
end
self.remove()
--
This could be used as a shared chest in multiplayer, to exchange stuff between groupmembers.
The robot would be activated by a password-protected keypad.
Todo
- write a book of the chest/inventory-contents.
- make a version of the mover that uses the remote control.
- filter while moving
- count total / stats (e.g. "default:stone : 3000 items in 31 stacks")
Robot-commands - Part 4
Commands about books :
- title, text = book.read(i) -- returns the title and contents of the book at i-th position in the library,
- with i in the range 1..32
- book.write(i,text) -- writes text into the book at i-th position in the library.
- Those books get the title "program book".
- display_text(text,linelen,size) -- show text on the "face" of the robot
- size=1 means the robot stays its normal size of 1 cube.
- With size=2, the robot shows the display at double size.
- read_text.forward(s) -- reads text of signs, chests and other blocks.
- s is optional, defaults to "infotext", to be used for other meta.
- It is possible to use the text read as code to execute (see next chapter).
Book-lister
Lists the books in the library of the spawner.
if i==nil then
i=0
say("Books:")
t="title T"
c="contents C"
book.write(9,t,c) -- creates/overwrites the book in slot 9 of the library
t="(unknown title)"
end
i=i+1
if i>32 then say("done.") self.remove() end
t, c = book.read(i)
if c==nil then c="(empty)" end
--say("Book #"..i.." : "..c)
say("Book #"..i.." : "..t.." - "..c)
To access books in a spawner/bookshelf at "home", you need to put in the coordinates.
Big news-display
Show text from the book in slot#1 on a big screen.
- "\n" is a newline.
--[[
if not msg then -- Write news-text to book
msg="*** NEW ***\n\n"
msg=msg.."Really big news-\n display !\n"
msg=msg.."\n"
msg=msg.."Never seen before !\n"
msg=msg.."Now available !\n"
msg=msg.."Low cost !\n"
msg=msg.."Big display !\n"
msg=msg.."High quality !\n"
msg=msg.."\n"
msg=msg.."Unbelievable !\n"
msg=msg.."Come and see it !\n"
msg=msg.."\n"
msg=msg.."<Your ad here>\n"
msg=msg.."...\n"
text = "News:".."\n"..os.date("%Y-%m-%d %H:%M") .. "\n\n" .. msg;
book.write(1,"news",text)
end
--]]
if not pn then pn="BigDisplay" -- show news on big display
title,text = book.read(1);
if not text then say("no text"); self.remove(); goto ex end
self.name("")
self.label(title)
self.display_text(text,20,3) --triple-size, 20 char per line
--turn.left() --turn into the direction of the player
turn.right()
end
::ex::
Robot-commands - Part 5
Commands to look at the world :
- f = find_nodes("default:stone", 3) -- returns true/false if a block of the specified type is found within the given distance.
- The maximum range for this search is 8 blocks.
Lookout
if rg==nil then
rg=9
s="default:stone"
--s="default:gravel"
--s="default:stone_with_coal"
--s="default:water_source"
s="default:torch_wall" -- also: torch_ceiling
s="default:torch"
say("Looking for "..s)
end
rg=rg-1
f = find_nodes(s, rg)
if f then
msg="Found "
else
msg="No "
end
say(msg..s.." within "..rg.." blocks")
if not f then say("done.") self.remove() end
The program starts with the maximum allowed range, counts down, and stops when nothing is found.
OreDetector1
This program checks if blocks of ore are in range:
if (rg==nil) then s="Ore-detector v0.3"
say(s)
self.label(s)
rg=9 i=1 next=0
ore={'diamond', 'mese', 'gold', 'copper', 'iron', 'coal', '-'} --Table of valueable stuff
end
if (ore[i]=="-") then next=1 i=0 end
if next>0 then
i=i+1 rg=9 next=0
say("Seaching: "..ore[i])
end
if (rg>8) then rg=8 end
f = find_nodes("default:stone_with_"..ore[i], rg) -- returns true/false
if f then
say("Found "..ore[i].." within range "..rg)
rg=rg-1
else
--say("no "..i.." @ rg"..rg)
next=1
end
Run this detector on the robot, and use an 'empty' remote controller to move the bot around to find the good stuff.
OreDetector2
This program is a faster, optimized version of the above OreDetector
giving only one output-line for each type of ore:
if not s then s="Ore-detector2 v0.4f"
say(s)
self.label(s)
rg=9 i=1 next=0 msg=""
--Table of stuff to search:
ore={'@diamond', '@mese', '@gold',
'@copper', '@iron', '@coal',
--'!torch', '!torch_wall',
'!lava_source', '!lava_flowing',
'!water_source', '!water_flowing',
'-'} --todo: moreores, mobs
say(#ore) --
end
if (ore[i]=="-") then next=1 i=0
--say("done.") self.remove()
end
if next>0 then
i=i+1 rg=9 next=0
--msg="Seaching: "..ore[i] say(msg) msg=""
end
if (rg>8) then rg=8 end
s = ore[i]
c1 = string.sub(s,1,1)
s = string.sub(s,2)
if c1=="@" then s="default:stone_with_"..s end
if c1=="!" then s="default:" ..s end
f = find_nodes(s, rg) -- returns true/false
if f then
msg="Found "..s.." within range "..rg
rg=rg-1
else
--say(i.." no "..s.." @ rg"..rg) --debug
if #msg>0 then say("--> "..msg) end
msg=""
next=1
end
It also searches for water and lava, to prevent surprises during mining.