Hello. I was tired of doing turtle micropositioning (through turtle.dig() or go back 1), so I made up far more usable alternative.
So
Usage: go <list of commands>, where commands are mostly 1-char wide and sometimes have prefixes.
For example, `go f100` will move turtle forward for 100 blocks. And `go mflmam` will: mine block (dig), go forward, turn left, mine block, turn around and mine one more block, having 3 blocks mined in total. This utility is extremely handy for turtle micropositioning and it command language can be used in user programs.
Okay, let's look at command language.
1) Language is concatenative, commands are executed from left to right, unknown commands are ignored.
2) Basic moves:
- f b u d -- move forward, backward, up and down respectivly
- l r a -- turn left, turn right, turn around
3) Advanced moves
- F B U D -- similar to f b u d, but will loop while block not detected. So `go F` will simply send turtle to the wall in front.
4) Digging (mining)
- m -- mine block
- M -- mine block until mined. This command is designed to mine gravel and sand, but it has 0.4s delay time.
- _m -- mine block down
- ^m ^M -- mine block up
You can also use >m for mining block in front or _M for mining down block, but there is little sense, in my opinion.
5) Placing
- >NUMBER -- place block from slot NUMBER in front
- _NUMBER -- place block from slot NUMBER down
- ^NUMBER -- place block from slot NUMBER up
6) Loops
- commandNUMBER -- repeat command NUMBER times. eg f100, r4.
- [commands]NUMBER -- repeat list of commands NUMBER times. Eg [M^Mf]15 will dig 2-height tunnel of length 15
- [commands] -- repeat commands while last command returns `true` (success). Command `go [fm]` will dig until emptyspace reached and then stop. Use this feature carefully, because of possible infinite loops
Loops support nesting. So, you can build block tower 2x2x10 with this command:`go [u[_1fl]4]10bD`. Last two chars will send turtle back to ground.
-- Block actions combinators v1 (go)
--
-- f b u d l r -- Move forward, backward, up, down, turn left, turn right
-- F B U D -- same moves, but move until block detected
-- m M -- mine block, mine blocks while block present (usefull for gravel mining)
-- [commands lastcommand] -- loop while lastcommand returns success
-- commandNUMBER -- loop command NUMBER times
-- [commands]NUMBER -- loop commands list NUMBER times
-- >DIGIT -- place block from DIGIT slot. When DIGIT == 0, place first available
-- ^DIGIT -- place block up
-- _DIGIT -- place block down
local tArgs = { ... }
if #tArgs ~= 1 then
print("Usage: go <list of commands>")
return
end
-- CAPS-letter commands evaluator, move-only
-- Remove code duplication by encapsulating common pattern in function
local function mdMove(command, detector)
while detector() == false do
command()
end
end
-- CAPS-letter commands evaluator, dig-only
-- Remove code duplication by encapsulating common pattern in function
local function mdDig(command, detector)
while detector() do
command()
sleep(0.4)
end
end
-- Evaluate prefix commands
local function runPrefixCmd(ch, digCmd, detectCmd, placeCmd)
local chCode = string.byte(ch, 1) - 48
if false then
elseif ch == 'm' then return true, digCmd()
elseif ch == 'M' then mdDig(digCmd, detectCmd); return true, false -- todo: better result return
elseif ch == '0' then return true, placeCmd()
elseif chCode > 0 and chCode < 10 then
turtle.select(chCode)
res = placeCmd()
return true, res
end
return false, false
end
-- Lua specific array length
local function lngth(lst)
if lst == nil then return 0 else return #lst end
end
local function parseNumber(st)
_, _, num, rest = string.find(st, "^(%d+)(.*)$")
return num, lngth(st) - lngth(rest)
end
local function parseLoop(st)
_, _, loop, rest = string.find(st, "^(%b[])(.*)$")
if loop ~= nil and loop ~= '' then
return loop:sub(2,-2), lngth(st) - lngth(rest)
elseif st:sub(1,1) == '[' then return nil, 1
else return nil, 0
end
end
function go(command)
local i = 1
local prevCommand = ''
local result = false
while i <= #command do
local ch = command:sub(i, i)
local nextCh = i == #command and '' or command:sub(i+1, i+1)
local number, nLen = parseNumber(command:sub(i))
local loop, lLen = parseLoop(command:sub(i))
-- if we found number, repeat last command number times
if number ~= nil then
number = tonumber(number)
while number > 1 do
go(prevCommand)
number = number - 1
end
i = i + nLen - 1
-- if we found loop, parse possible number and evaluate expression
elseif loop ~= nil then
local number, nLen = parseNumber(command:sub(i + lLen))
if number ~= nil then
number = tonumber(number)
while number > 0 do
go(loop)
number = number - 1
end
i = i + nLen
else
while go(loop) do end
end
i = i + lLen - 1
elseif ch == 'f' then result = turtle.forward()
elseif ch == 'b' then result = turtle.back()
elseif ch == 'u' then result = turtle.up()
elseif ch == 'd' then result = turtle.down()
elseif ch == 'F' then mdMove(turtle.forward, turtle.detect)
elseif ch == 'B' then go("aFa") -- this is not primitive
elseif ch == 'U' then mdMove(turtle.up, turtle.detectUp)
elseif ch == 'D' then mdMove(turtle.down, turtle.detectDown)
elseif ch == 'l' then turtle.turnLeft()
elseif ch == 'r' then turtle.turnRight()
elseif ch == 'a' then go("ll")
elseif ch == 'm' then result = turtle.dig()
elseif ch == 'M' then mdDig(turtle.dig, turtle.detect)
elseif ch == '>' then
local result1,result2 = runPrefixCmd(nextCh, turtle.dig, turtle.detect, turtle.place)
if result1 then
i = i + 1 -- todo: better prefix parsing
end
result = result2
elseif ch == '^' then
local result1,result2 = runPrefixCmd(nextCh, turtle.digUp, turtle.detectUp, turtle.placeUp)
if result1 then
i = i + 1
end
result = result2
elseif ch == '_' then
local result1,result2 = runPrefixCmd(nextCh, turtle.digDown, turtle.detectDown, turtle.placeDown)
if result1 then
i = i + 1
end
result = result2
end
prevCommand = ch
i = i + 1
end
return result
end
go(tArgs[1])
TODOs (todos will be done only on requests):
Ctrl-C support
Wireless support
More usefull commands (what commands?)
print runtime errors and warnings, print current iteration
As I understand, it not very good idea. I suppose `go l` and `go l1` must do the same, but with your variant it will be hard to distinguish when just turn and when turn&go. Just turn is usefull when you are going to mine, eg `go lm`
I think the easiest and cleanest way to implement this (I did not read your code (yet />)) is to define the syntax as a string of letters. A number right behind a letter would count as a parameter for that letter. Thus "l2" would turn the turtle twice.
I think the easiest and cleanest way to implement this (I did not read your code (yet />)) is to define the syntax as a string of letters. A number right behind a letter would count as a parameter for that letter. Thus "l2" would turn the turtle twice.
That is done now!
Xhisor, on 06 August 2012 - 08:23 AM, said:
Isn't this just a
["f"] = turtle.forward()
(But with more lines, of course!)
As I understand, yes, it is. But it allows you to simplify your code. For example, here is "snake"-style mine digging program:
local tArgs = { ... }
if #tArgs ~= 2 then
print ("Usage: sptunnel <from> <to>")
return
end
from = tonumber(tArgs[1])
to = tonumber(tArgs[2])
dofile("/go_api") -- load API
tunnel6 = "[[M^Mf_2]6a>1a]"
tunnel3 = "[[M^Mf_2]3]"
go(tunnel3..from)
if from % 2 == 0 then
go("l"..tunnel6..(from/2).."r")
else
go("r"..tunnel6..((from+1)/2).."l")
end
for i=(from+1),to do
go(tunnel3..1)
if i % 2 == 0 then
go("l"..tunnel6..i.."r")
else
go("r"..tunnel6..i.."l")
end
end
Which result to mines like this
I bet you cannot write smaller program to snake-dig, place torches and floor without using such APIs like mine one.
xuma202, on 07 August 2012 - 05:03 PM, said:
Cyclonit, on 07 August 2012 - 04:28 PM, said:
Thus "l2" would turn the turtle twice.
That would be useless because it can be replaced by "b"
There is `a` shortcut (turn around). Besides, if you REALLY want `l` to act as `lf`, you can add new shortcut:
...
elseif ch == 'l' then turtle.turnLeft()
elseif ch == 'r' then turtle.turnRight()
elseif ch == 'a' then go("ll")
elseif ch == 'L' then go("l"); ch = "f" -- hack to add `lf` shortcut
...
Find this code in my source and add so much shortcuts as there are symbols on keyboard! Anyway I think, that `l` is much clearer for use with just turnLeft(), and not turnLeft()+forward().
I like your script very much. I think it needs one more command though.. could you add a shortcut for the command to empty the turtles inventory? Since the turtle has a 16 slot inventory the best it could mine would be a 4x4 area (assuming no empty blocks) before it would be full so it needs some way to eject that inventory near a gatherer obsidian pipe or chest for it to continue mining.
I like your script very much. I think it needs one more command though.. could you add a shortcut for the command to empty the turtles inventory? Since the turtle has a 16 slot inventory the best it could mine would be a 4x4 area (assuming no empty blocks) before it would be full so it needs some way to eject that inventory near a gatherer obsidian pipe or chest for it to continue mining.
It can mine more than a 4x4 area. It can hold up to 1024 blocks if all the blocks are stackable. Therefore it could mine 512x2 area (assuming all blocks are stackable, there is no air) before the inventory would be completely full.
Anyways, very nice API, I think I might actually use it (I don't normally use user-made APIs).
I like your script very much. I think it needs one more command though.. could you add a shortcut for the command to empty the turtles inventory? Since the turtle has a 16 slot inventory the best it could mine would be a 4x4 area (assuming no empty blocks) before it would be full so it needs some way to eject that inventory near a gatherer obsidian pipe or chest for it to continue mining.
I have thinked about this. And I think, that dropping code is very related to "is turtle full" check and so is used at most once in any script. So it's better to do "turtle.drop()" by hands, without API.
But there are also "shortcutting" reasons, write go("f") in code is much faster than turtle.forward(), so you can add new shortcut for dropping yourself:
elseif ch =='.'then result = turtle.drop() -- drop current slot ...
As I see it's usage, it is like this:
for i = 1, 16 do go(">"..i..".") -- heh, hacky-hacky.
-- first we place block, but sure it will fail (we are in front of chest)
-- so it will only do it first part - select slot
-- then dot-command (Drop command) will empty the slot
end
Shazz, on 11 August 2012 - 12:58 AM, said:
Anyways, very nice API, I think I might actually use it (I don't normally use user-made APIs).
It is not nice API, it is ver-very-nice application and API =) Writing own tunnel-brakers is MUCH faster and easier with it.
This is a very good program, which deserves a bump because it can do pretty much all the simple things like excavating or tunneling.
I did a rewrite of it to better fit my own needs (mainly, I made it part of the program that I'm currently using for pretty much all my turtle control needs). One thing I did, I changed the multiple elseif then sections to a single elseif and a table look-up (this also avoids calling the more complicated loop parsing and execution functions, since I put them after the simple command look-up). This also allowed eliminating some of the less useful functions.
I feel like the use of a look-up table rather than a long series of elseif then statements not only improves performance (though this is hardly an issue) but also makes it easier to extend/customize the function to given needs, like supporting extended API functions.
Spoiler
cmdlst = {f=rctfncs.dfd,b=turtle.back,u=rctfncs.dup,d=rctfncs.ddn,l=turtle.turnLeft,r=turtle.turnRight,t= rctfncs.tnl,
p=turtle.place,P=turtle.placeUp,o=turtle.placeDown,
x=turtle.drop,X=turtle.dropUp,z=turtle.dropDown,
e=turtle.dig,E=turtle.digUp,w=turtle.digDown,
v=rctfncs.pckup,V=turtle.suckUp,c=turtle.suckDown,
R=rctfncs.refuel,S=rctfncs.slct,B=rctfncs.bmb,
}
-- non-nil array length
function lngth(lst)
if lst then return #lst else return 0 end
end
function gtNmbr(strg)
_,_,nmbr,rmndr = string.find(strg,"^(%d+)(.*)$")
return nmbr,lngth(strg)-lngth(rmndr)
end
function gtLoop(strg)
_,_,loop,rmndr = string.find(strg,"^(%b[])(.*)$")
if loop then return loop:sub(2,-2),lngth(strg)-lngth(rmndr)
else return nil,0 end
end
function gox(cmnds)
if not #cmnds then return false end
local ndx,prvCmnd,result = 1,""
while ndx <= #cmnds do
local char = cmnds:sub(ndx,ndx)
local nxtCh = cmnds:sub(ndx+1, ndx+1)
if cmdlst[char] then
ndx,result = ndx+1,cmdlst[char]()
else
local nmbr, nLen = gtNmbr(cmnds:sub(ndx))
local loop, lpLen = gtLoop(cmnds:sub(ndx))
if nmbr then
for i = 2,tonumber(nmbr) do gox(prvCmnd) end
ndx = ndx+nLen
elseif loop then
local nmbr, nLen = gtNmbr(cmnds:sub(ndx+lpLen))
if nmbr then
for i = 1,tonumber(nmbr) do gox(loop) end
ndx = ndx+nLen
else while gox(loop) do end end
ndx = ndx+lpLen
end end
prvCmnd = char
end
return result
end
print([[enter a string of commands/loops
f b u d l r to move, t to tunnel
p P o to place[U/Dn], x X z to drop
e E w to dig [U/Dn], v V c to suck
R to refuel, S to select next slot
[...] loop while last command succeeds
number for fixed loop]])
gox(io.read())
You can see that the table of indexed functions I use up top contains functions other than the standard turtle API functions. Having a section like that means that the program can be extended/customized easily without anyone having to mess with the core of your program.
Nice function />
I think it should be able to do anything from turtle api. Definitely suck functions. Detect and compare too. Most turtle functions return boolean. Others can be "corrected".
In my humble opinion selection deserves to be separate command.
Also tiny program to pass it's arguments to this function would be good.
rctfncs functions repack the basic turtle functions with a bit of improved functionality (for my purposes). You can edit all the available functions (and what character represents them) easily, just add "x=yrfunc" to the table (x represents the character you want to use to call the function, yrfunc is the name of the function).
Because the program itself has limited support for conditionals, I didn't see a need to put detect/compare functions. The rctfncs dfd, dup, and ddn are designed with making sure a turtle moves in the indicated direction if at all possible, destroying/killing anything in the way (note, that includes players). If you're going to load it into the api and call it from another program the way tele_iz_dva4a did in the above example, then using tArgs rather than doing what I did makes sense (it's easy to shift the usage back, I just like being able to see a reference for how to compose the command string while I'm composing it--I guess I could use tArgs and just "if not tArgs[1] then" display the input message if not tArgs[1] then). That way you could use it as an api load for another function (which would support the conditionals you suggest).
Hi ChunLing, realy 10x for improving code. I am quite novice to Lua, so...
IMO your code is too condensed. Cuttin' length to lngth is escpecially funny =)
Also, making API is possible, I often import this (slightly modified) script and mix standard code and go() shortcuts.
Yes, I originally thought about making an API of the rctfncs I use. I decided against it because for now I want to have a single program, both for use by people who just download one and for myself to be able to edit the program "on the fly" without having to change everything.
When the functions are all in a settled state and I'm using them in more than just one program, I'll probably put them into an API. But I'm still tuning this functions and still only use them in the one turtle program.