Posted 15 July 2013 - 11:35 PM
Hi!
Did you ever wonder, how the hell the go program does function, or some enhancements of them (like the Inturtlpler , muTurtle, and many others)? How do they loop ? At the end of this tutorial, you will be able to make your very own turtle program, which, when launching such as follow :
selects the 5th slot, and make a 10-blocks-long wall, by placing up and down then going backward and placing forward, 10 times.
or the functional version :
When I say your very own, that means your very own, so if you want it to perform infinite loops, logical operations, or anything, I will give you the tools to do it yourself, and quite simply. I saw many topics about that kind of stuff among the programs, but haven't found any in tutorial, so I made my own.
Final code to understand
Getting started:
You can also base your actions on already existing tables, like APIs :
Then, execute your actions in a row :
The first line creates a tables with your inputs, indexed by numbers, the ipair loop will go through it, and call the corresponding actions.
I want to make it shorter.
Then just do it. Here is what I currently use
but you would be silly to copy and paste that, because the point of this tutorial is to make your OWN program with your very OWN shortcuts, and after using it a bit, I can tell you I could have done better.
Note that, at this point, we can only call simple functions with no arguments. It will soon change.
Getting looped
Now, the magic appears.. With metatables.
Metatables are actually tables that wrap other tables in order to allow a lot more functionalities, to make things both complex and simpler, like defining addition for tables, or making proxy…
Take a look at the following code to see what happens :
There still are the actions and the orders. I replaced the "for … in ipairs" loop by a while loop in order to modify i by external means. I created a metatable, and assiociated it to actions. Now, when something strange happens on actions, the metatable will be checked and will eventually tell what to do. Here, when we call action[3] for example, the computer see that 3 is not a field, and, before returning nil, it searchs the field __index in meta. If there is any, and meta.__index is a function, it will return meta.__index(actions,3) (or metatable.__index ( table_called_with_a_inexistant_field, inexistant_field ) in the general case).
Take a look at ( http://www.lua.org/pil/13.html ) or here to know more about metatables.
What happens next ?
As I said, it calls meta.__index(actions,k), it checks whether k is a number, and if it does, it calls the next element k times. The i = i + 1 ensures that this element will be skipped in the main loop, once the secondary is finished.
Note that because of the '()' in the 'actions[orders]()' of the main loop, we have to return a function with no argument. For both short scripting and speed computing, we can define it at the beginning :
Well, what happen if we run "execute 3 3 forward" ? We could expect that it's equivalent to "execute 9 forward", but it actually travel only 5 blocks! In fact, it enter in the first loop, and "i=i+1" sets i to 2. then it enters in the second loop and sets i to 3. After moving thrice, the second loops stops, and we come back to the first. But now i=3, so we do not call the second loop again. Instead, it call twice the 3rd instruction, go forward.
Well, the thing is, we want to loop always on the same i, and the functions in the loop can change its value. A simple fix is to store the position for each loop :
Going further
You want to raise an error if the user type something wrong ? Then just add it to the metatable!
Or you maybe want to go ahead anyway ?
And the possibilities are infinite !!
How about calling instruction with arguments ?
with :
WARNING : when 'code' isn't provided, all your arguments will be string. In my example, with such a simple code, they all will be numbers.
note :
It's your turn !!
"execute a or b c", where a, b, c are elementary instructions, execute a (anyway, a is before or, so must be executed), and if a returns true, skip b and goto c (or ends, if b were the last instruction); else, compute and return b.
Similarly, "execute a or b c", execute a and if a returns false, skip b and goto c (or ends, if b were the last instruction); else, compute and return b.
First, we will need to catch and remember the returned value of an instruction. We can add the line
You also have to let the loop return and others, otherwise ans._ will be erased. A simple way to do that is to achieve that is to replace fnil with
Well, I gave you all the tools you needed, you can code the rest.
If you really can't, here are the solution.
Some harder exercises are : goto, code the brackets, in order to achieve what is proposed at the very beginning of the topic… Or anything you want, though the loop and the argument calling is already a lot.
A very personal and very last enhancement :
So if I want to add keywords, I just have to add
to the table of keywords.
The brackets :
Note that this coding of brackets make them incoherent with or, and and 0 (zero loop). We can either have an ugly patch, or try to consider { } as a single argument. It also require an ugly patch, unless we have a more functional point of view. That will motive the second part of this tutorial, but what does less motive motive me to do it is the lack of feedback..
Coming soon :
- making a similar but more powerful function , for function can handle several type of inputs.
- Using tables and functables, as arguments of this function.
- achieving macros saving, and "dynamic" macros saving
Well, thank you guys for reading, it's my first tuto so I'd be glad to have comment, if you have any enhancement/correction to suggest, don't hesitate.
Did you ever wonder, how the hell the go program does function, or some enhancements of them (like the Inturtlpler , muTurtle, and many others)? How do they loop ? At the end of this tutorial, you will be able to make your very own turtle program, which, when launching such as follow :
execute call sl 5 10 { plU plD bk plF }
selects the 5th slot, and make a 10-blocks-long wall, by placing up and down then going backward and placing forward, 10 times.
or the functional version :
execute( "sl", 5, 10, {"plU","plD","bk","plF"} )
When I say your very own, that means your very own, so if you want it to perform infinite loops, logical operations, or anything, I will give you the tools to do it yourself, and quite simply. I saw many topics about that kind of stuff among the programs, but haven't found any in tutorial, so I made my own.
Final code to understand
Spoiler
local actions = turtle
local ans = {}
local fnil = function() end
local fconst = function(constant)
return function()
return constant
end
end
local fans = function ()
return ans._
end
local orders = {...}
local i = 1
local keywords = {
call = function ()
i = i + 1
local nbarg = tonumber(orders[i])
if nbarg then
i = i + 1
else
nbarg = 1
end
actions[orders[i]](myunpack(orders, i + 1, i + nbarg, tonumber))
i = i + nbarg + 1
return(ans._)
end,
}
keywords["or"] = function ()
if ans._ then
i = i + 1
end
return ans._
end
keywords["and"] = function ()
if not ans._ then
i = i + 1
end
return ans._
end
keywords["{"] = function ()
i = i + 1
while orders[i] ~= "}" do
ans._ = actions[orders[i]]()
i=i+1
end
return ans._
end
keywords["dowh"] = function ()
local _i = i + 1
ans._ = true
while ans._ do
i = _i
ans._ = actions[orders[i]]()
end
return ans.
end
local metakw ={
__index = function ()
error "That's not how we use execute !!"
end,
}
setmetatable(keywords, metakw)
local meta = {
__index = function (t,k)
if tonumber(k) then
local _i = i + 1
for j = 1,tonumber(k) do
i = _i
ans._ = actions[orders[i]]()
end
return fans
else
return keywords[k]
end
end,
}
setmetatable(actions, meta)
while i <= #orders do
ans._ = actions[orders[i]]()
i = i+1
end
Getting started:
Spoiler
Well, after typing "edit execute" in the shell, the first thing to do is to define what elementary actions we will be able to use. Why not putting them in a table?!
local actions = {
stuff1 = function (...)
--do any thing you want
end,
dance = function (...)
end,
hi = function (...)
print("hello")
end,
}
You can also base your actions on already existing tables, like APIs :
local actions = turtle
Then, execute your actions in a row :
local orders = {...}
for i,v in ipairs(orders) do
actions[v]()
end
The first line creates a tables with your inputs, indexed by numbers, the ipair loop will go through it, and call the corresponding actions.
I want to make it shorter.
Then just do it. Here is what I currently use
Spoiler
local shortCuts = {
fd = "forward",
bk = "back",
up = "up",
dn = "down",
tL = "turnLeft",
lt = "turnLeft",
tR = "turnRight",
rt = "turnRight",
sl = "select",
gIC = "getItemCount",
gIS = "getItemSpace",
--m like mine
m = "dig",
mUp = "digUp",
mDn = "digDown",
a = "attack",
aUp = "attackUp",
aDn = "attackDown",
p = "place",
pUp = "placeUp",
pDn = "placeDown",
d = "detect",
dUp = "detectUp",
dDn = "detectDown",
c = "compare",
cUp = "compareUp",
cDn = "compareDn",
cTo = "compareTo",
tTo = "transferTo",
--t like transfer/throw
t = "drop",
tUp = "dropUp",
tDn = "dropDown",
s = "suck",
sUp = "suckUp",
sDn = "suckDown",
gFL = "getFuelLevel",
ref = "refuel",
craft = "crf"
}
for k,v in pairs(shortCuts) do
actions[k] = actions[v]
end
but you would be silly to copy and paste that, because the point of this tutorial is to make your OWN program with your very OWN shortcuts, and after using it a bit, I can tell you I could have done better.
Note that, at this point, we can only call simple functions with no arguments. It will soon change.
Getting looped
Spoiler
As I find more logic to do things when they come, I find more logic to say "go 100 meters forward" than "go forward 100 meters", but its mainly a personal choice. We do not want to alter thoroughly our code, but we can't put a function in the actions table for each number, and that function would have to look at the table.Now, the magic appears.. With metatables.
Metatables are actually tables that wrap other tables in order to allow a lot more functionalities, to make things both complex and simpler, like defining addition for tables, or making proxy…
Take a look at the following code to see what happens :
local actions = turtle
local orders = {...}
local i = 1
local meta = {
__index = function (t,k)
if tonumber(k) then
i = i + 1
for j = 1,tonumber(k) do
actions[orders[i]]()
end
return function ()
end
end
end,
}
setmetatable(actions, meta)
while i <= #orders do
actions[orders[i]]()
i = i+1
end
There still are the actions and the orders. I replaced the "for … in ipairs" loop by a while loop in order to modify i by external means. I created a metatable, and assiociated it to actions. Now, when something strange happens on actions, the metatable will be checked and will eventually tell what to do. Here, when we call action[3] for example, the computer see that 3 is not a field, and, before returning nil, it searchs the field __index in meta. If there is any, and meta.__index is a function, it will return meta.__index(actions,3) (or metatable.__index ( table_called_with_a_inexistant_field, inexistant_field ) in the general case).
Take a look at ( http://www.lua.org/pil/13.html ) or here to know more about metatables.
What happens next ?
As I said, it calls meta.__index(actions,k), it checks whether k is a number, and if it does, it calls the next element k times. The i = i + 1 ensures that this element will be skipped in the main loop, once the secondary is finished.
Note that because of the '()' in the 'actions[orders]()' of the main loop, we have to return a function with no argument. For both short scripting and speed computing, we can define it at the beginning :
local fnil = function() end
Well, what happen if we run "execute 3 3 forward" ? We could expect that it's equivalent to "execute 9 forward", but it actually travel only 5 blocks! In fact, it enter in the first loop, and "i=i+1" sets i to 2. then it enters in the second loop and sets i to 3. After moving thrice, the second loops stops, and we come back to the first. But now i=3, so we do not call the second loop again. Instead, it call twice the 3rd instruction, go forward.
Well, the thing is, we want to loop always on the same i, and the functions in the loop can change its value. A simple fix is to store the position for each loop :
local meta = {
__index = function (t,k)
if tonumber(k) then
local _i = i + 1
for j = 1,tonumber(k) do
i = _i
actions[orders[i]]()
end
return fnil
else
return fnil
end
end,
}
Going further
Spoiler
At this point, we can do almost the same things as the go function. But the go program loops when encountering an object ? No problem, set
actions.up/down/forward/back = function (...)
while turtle.up/down/forward/back() do
end
You want to raise an error if the user type something wrong ? Then just add it to the metatable!
local meta = {
__index = function (t,k)
if tonumber(k) then
i = i + 1
return function ()
for j = 1,tonumber(k) do
actions[orders[i]]()
end
end
else
error( "NOOO! that's not how we use execute" )
end
end,
}
Or you maybe want to go ahead anyway ?
local meta = {
__index = function (t,k)
if tonumber(k) then
i = i + 1
for j = 1,tonumber(k) do
actions[orders[i]]()
end
return fnil
else
return fnil
end
end,
}
And the possibilities are infinite !!
How about calling instruction with arguments ?
Spoiler
Wait, have you tried to code it yourself ? at least for a fixed number of arguments (let's say one) ?Spoiler
local meta = {
__index = function (t,k)
if tonumber(k) then
local _i = i + 1
for j = 1,tonumber(k) do
i = _i
actions[orders[i]]()
end
return fnil
elseif k == "call"
i = i + 1
local nbarg = tonumber(orders[i])
if nbarg then
i = i + 1
else
nbarg = 1
end
actions[orders[i]](myunpack(orders, i + 1, i + nbarg, tonumber))
i = i + nbarg + 1
return fnil
else
error( "NOOO! thats not how we use execute" )
end
end,
}
with :
Spoiler
myunpack = function (tab, s, e, code)
local s = s or 1
local e = e or #tab
code = code or function (...)
return (...)
end
frec = function (_s)
if _s <= e then
return code(tab[_s]), frec(_s + 1)
end
end
return frec(s)
end
WARNING : when 'code' isn't provided, all your arguments will be string. In my example, with such a simple code, they all will be numbers.
note :
Spoiler
As far as we could have put anything in actions, we could have achieve the same thing without metatables, but it would have been a lot more confusing. We assume that the functions in action, have access to none of the variables the execute program, but the ones we give them as input. We also could put the functions with arguments in keywords, we don't, for the same reasons. Doing it bye the way we do allows us to reuse the meta, which correspond to a rough language, with other actions.It's your turn !!
Spoiler
Now, if you want, you will attempt to code 'or' and 'and' such as follow :"execute a or b c", where a, b, c are elementary instructions, execute a (anyway, a is before or, so must be executed), and if a returns true, skip b and goto c (or ends, if b were the last instruction); else, compute and return b.
Similarly, "execute a or b c", execute a and if a returns false, skip b and goto c (or ends, if b were the last instruction); else, compute and return b.
First, we will need to catch and remember the returned value of an instruction. We can add the line
local ans = 0
at the beginning of the file, but if ans is ever set to nil, and reassigned, it could become a polluting global variable, so I put
local ans = {}
at the beginning, and replace each occurrence of that :
actions[orders[i]]()
with that :
ans._ = actions[orders[i]]()
so the returned value of the very last executed task is always available in ans._You also have to let the loop return and others, otherwise ans._ will be erased. A simple way to do that is to achieve that is to replace fnil with
local fans = function ()
return ans._
end
Well, I gave you all the tools you needed, you can code the rest.
If you really can't, here are the solution.
Spoiler
[...]
elseif k == "or"
if ans._ then
i = i + 1
end
return fans
elseif k == "or"
if not ans._ then
i = i + 1
end
return fans
else
[...]
Some harder exercises are : goto, code the brackets, in order to achieve what is proposed at the very beginning of the topic… Or anything you want, though the loop and the argument calling is already a lot.
A very personal and very last enhancement :
Spoiler
I prefer put all my keywords (that means interpreted terms but not elementary instruction, like "call", "or" and "and"), in one table. Therefore, I replace the metatable with :
local keywords = {
call = function ()
i = i + 1
local nbarg = tonumber(orders[i])
if nbarg then
i = i + 1
else
nbarg = 1
end
actions[orders[i]](myunpack(orders, i + 1, i + nbarg, tonumber))
i = i + nbarg + 1
return(ans._)
end,
}
local metakw ={
__index = function ()
error "That's not how we use execute !!"
end,
}
setmetatable(keywords, metakw)
local meta = {
__index = function (t,k)
if tonumber(k) then
local _i = i + 1
for j = 1,tonumber(k) do
i = _i
ans._ = actions[orders[i]]()
end
return fans
else
return keywords[k]
end
end,
}
setmetatable(actions, meta)
So if I want to add keywords, I just have to add
Spoiler
keyword = function ()
-- do stuff, dealing with i, orders and actions
return ans._
end,
to the table of keywords.
The brackets :
Spoiler
keywords["{"] = function ()
i = i + 1
while orders[i] ~= "}" do
ans._ = actions[orders[i]]()
i=i+1
end
return ans._
end
Note that this coding of brackets make them incoherent with or, and and 0 (zero loop). We can either have an ugly patch, or try to consider { } as a single argument. It also require an ugly patch, unless we have a more functional point of view. That will motive the second part of this tutorial, but what does less motive motive me to do it is the lack of feedback..
Coming soon :
- making a similar but more powerful function , for function can handle several type of inputs.
- Using tables and functables, as arguments of this function.
- achieving macros saving, and "dynamic" macros saving
Well, thank you guys for reading, it's my first tuto so I'd be glad to have comment, if you have any enhancement/correction to suggest, don't hesitate.
Edited on 17 July 2013 - 06:17 PM