Mods/basic robot: Difference between revisions

From Minetest
>Ghoti
m (→‎Farming101: speling)
>Ghoti
m (→‎Farming101: speling)
 
(No difference)

Latest revision as of 10:08, 19 March 2022


basic_robot
Robot (basic robot).png
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

Subpages

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
Mese Crystal.png
Mese Crystal.png
Mese Crystal.png
Mese Crystal.png
Mese Crystal.png
Mese Crystal.png
Stone.png
Steel Ingot.png
Stone.png
Robot spawner (basic robot).png
Used to spawn a robot-worker.
Robot remote control Stick
+ Mese Crystal
Stick.png
Mese Crystal.png
Robot remote control (basic robot).png
Used to control a robot.

Controlling Form

Rightclicking the spawner opens a form with buttons and a textbox for writing a program.

controlling form for basic_robot
  • 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

...

...

...

Remote-Control

Intro

Spawner and basic_robot, remote control with colored buttons

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

A small, safe place.

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

'Hut-Builder 3x4, v0.2' at work

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

Lookout - we found a torch
 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.


Todo / Debug

see Mods/basic_robot/Todo