This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
tele_iz_dva4a's profile picture

[Turtle] Replacement for `go` utility

Started by tele_iz_dva4a, 02 August 2012 - 08:28 PM
tele_iz_dva4a #1
Posted 02 August 2012 - 10:28 PM
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.

CODE v1 (canonical pastebin at http://pastebin.com/6aFnnJtx):
Spoiler
--   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
  • better help, more examples
  • code cleanup
  • bugs elimination
  • position tracking
  • pauseresume mode
  • support new CC version (attacks, drops)
xuma202 #2
Posted 03 August 2012 - 07:47 AM
This is awsome what about l5 will turn left and move forward 5 blocks
tele_iz_dva4a #3
Posted 03 August 2012 - 08:56 AM
This is awsome what about l5 will turn left and move forward 5 blocks
Maybe, but now it is matter of just one letter, you can do lf5 for your needs. Or do you mean lf5r ?
xuma202 #4
Posted 03 August 2012 - 10:53 AM
no l5 as a replacement for lf5
tele_iz_dva4a #5
Posted 03 August 2012 - 11:53 AM
no l5 as a replacement for lf5
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&amp;go. Just turn is usefull when you are going to mine, eg `go lm`
xuma202 #6
Posted 03 August 2012 - 01:08 PM
Why would it be hard to distinguish?

l => turn left
r => turn right
r3 => turn right and go 3 forward
l4 => turn left and go 4 forward
maybe even
l-3 => turn left and go 3 backwards
tele_iz_dva4a #7
Posted 03 August 2012 - 01:43 PM
Why would it be hard to distinguish?

l => turn left
r => turn right
r3 => turn right and go 3 forward
l4 => turn left and go 4 forward
maybe even
l-3 => turn left and go 3 backwards
And what about l1? It must do the same as just l.
Anyway, I'll think about it.
xuma202 #8
Posted 03 August 2012 - 05:43 PM
Why must it? If you really need so then make it like turning left and moving n-1 blocks forward so that l2 replaces lf1
Xhisor #9
Posted 06 August 2012 - 09:23 AM
Isn't this just a
["f"] = turtle.forward()
(But with more lines, of course!)
Cyclonit #10
Posted 07 August 2012 - 05:28 PM
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.
xuma202 #11
Posted 07 August 2012 - 06:03 PM
Thus "l2" would turn the turtle twice.

That would be useless because it can be replaced by "b"
tele_iz_dva4a #12
Posted 08 August 2012 - 04:00 PM
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!


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.

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().
TNAgent #13
Posted 11 August 2012 - 12:53 AM
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.
Shazz #14
Posted 11 August 2012 - 01:58 AM
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).
tele_iz_dva4a #15
Posted 12 August 2012 - 06:15 AM
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 == 'l' then turtle.turnLeft()
elseif ch == 'r' then turtle.turnRight()
elseif ch == 'a' then go("ll")

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


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.
ChunLing #16
Posted 11 October 2012 - 05:27 AM
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.
Sebra #17
Posted 11 October 2012 - 03:36 PM
Nice function :P/>/>
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.

Agree?
ChunLing #18
Posted 11 October 2012 - 04:58 PM
The revised version I posted allows pretty near all of that by using a character indexed table for the commands.
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, }

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).
tele_iz_dva4a #19
Posted 26 October 2012 - 04:17 PM
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.
ChunLing #20
Posted 26 October 2012 - 07:36 PM
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.