PonyKuu

module API

Mar 15th, 2013
314
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 20.25 KB | None | 0 0
  1. ----------------------------------------------------------------------
  2. --                              Module API                          --
  3. ----------------------------------------------------------------------
  4.  
  5. -- Version 0.1a
  6.  
  7. -- Module is an API that allows to interact with master and perform next functions:
  8. --    Communication to the master.
  9. --    Navigation to the said point.
  10. --    Request tasks and execute functions using a response table.
  11. -- Usage:
  12. -- 1) Set up a task table using the module.addTasks(taskTable) function
  13. --    taskTable is a table with tasks as keys (tasks are recived from Master)
  14. --    and functions as values. Function should accept a response as an argument,
  15. --    so master can pass some additional data to module.
  16. --    Those functions are executed when module recieved an associated task after module has navigated to
  17. --    the position sent in response (if any)
  18. -- 2) Set the type of your module using the setType (newType) function
  19. --    Type is used by Master to determine what can this module do.
  20. -- 3) Run the module.init () function
  21. -- 4) Run the module.operate() function to start
  22. -- 5) If you move the module in your tasks, please use module.move(direction) or module.moveEx (f) to move it
  23. --    and use turn(side) or turnTo(f) to turn it, because those functions are keep tracking the module's position
  24. --    or you can find its new position by yourself and use module.setPosition (x,y,z,f) to set it
  25.  
  26.  
  27. --[[
  28. *********************************************************************************************
  29. *                                    Navigation Part                                        *
  30. *********************************************************************************************
  31. ]]--
  32.  
  33. -- Location is a global table containing all the coordinates and facing direction of the turtle
  34. -- And here is the function to set the position of the turtle and another one to get it.
  35. location = {x = 0, y = 0, z = 0, f = 0}
  36.  
  37. function setPosition (x, y, z, f)
  38.     location.x = x
  39.     location.y = y
  40.     location.z = z
  41.     location.f = f
  42. end
  43.  
  44. function getPosition ()
  45.     return { x = location.x,
  46.              y = location.y,
  47.              z = location.z,
  48.              f = location.f, } -- return a copy of the location table.
  49. end
  50.  
  51. -- And a small function to print the position. For debug purposes
  52. function printPosition ()
  53.     print ("Currrent turtle position:")
  54.     print ("    x = "..location.x)
  55.     print ("    y = "..location.y)
  56.     print ("    z = "..location.z)
  57.     print ("    f = "..location.f)
  58. end
  59.  
  60. -- A turning function
  61. function turn (direction)
  62.     if direction == "left" then
  63.         turtle.turnLeft()
  64.         location.f = location.f - 1
  65.     elseif direction == "right" then
  66.         turtle.turnRight()
  67.         location.f = location.f + 1
  68.     elseif direction == "around" then
  69.         turtle.turnLeft()
  70.         turtle.turnLeft()
  71.         location.f = location.f - 2
  72.     else
  73.         print ("No such direction: "..direction)
  74.         return false
  75.     end
  76.     location.f = (location.f + 4) % 4 -- This is used to handle the f < 0 and f > 3 situations
  77.     return true
  78. end
  79.  
  80. -- This one is used to turn the turtle to specified f direction.
  81. -- A little bit of pony magic is used to calculate how should it turn
  82. function turnTo (face)
  83.     local diff = face - location.f
  84.     if math.abs(diff) == 2 then --this is true if the difference between f and face is 2 or -2, so it should turn around
  85.         return turn ("around")
  86.     elseif math.fmod(diff + 4, 4) ==  1 then    --this is true if the difference between f and face is 1
  87.             return turn ("right")                   --f = 0 and face = 3 is also satisfies the condition (-3 + 4 == 1)
  88.     elseif math.fmod(diff - 4, 4) == -1 then    --this is true if the difference between f and face is -1
  89.             return turn ("left")                    --f = 3 and face = 1 is also satisfies the condition ( 3 - 4 == 1)
  90.     end
  91.     return false    --returned if turtle is already faced the specified direction
  92. end
  93.  
  94. -- This function is used to return actual x and z shifts
  95. -- These shifts are the numbers which you should add to the coordinates when turtle moves forward.
  96. -- I use a table to decode it since it is more compact and easier to read than lots of conditions
  97. local tShifts = {
  98.     [0] = { 1,  0},
  99.     [1] = { 0,  1},
  100.     [2] = {-1,  0},
  101.     [3] = { 0, -1},
  102. }
  103. function fDirection ()
  104.     return unpack (tShifts[location.f])
  105. end
  106.  
  107. -- This function is used to update the turtle location when it has moved
  108. function updateLocation (direction)
  109.     if direction == "up" then
  110.         location.y = location.y + 1
  111.     elseif direction == "down" then
  112.         location.y = location.y - 1        
  113.     elseif direction == "forward" then
  114.         local xShift, zShift = fDirection ()
  115.         location.x = location.x + xShift        
  116.         location.z = location.z + zShift
  117.     elseif direction == "back" then
  118.         local xShift, yShift = fDirection ()
  119.         location.x = location.x - xShift
  120.         location.z = location.z - zShift
  121.     end
  122. end
  123.  
  124. -- This function helps us with our EnderChests if there is something that obstructs placement
  125. -- It checks what's going on on top of it, and if there is a turtle, it waits before digging.
  126. function forcePlaceUp ()
  127.     while not turtle.placeUp() do
  128.         if turtle.detectUp() then
  129.             if peripheral.isPresent ("top") and peripheral.getType ("top") == "turtle" then
  130.                 print "Other turtle detected. Waiting..."
  131.                 while peripheral.isPresent ("top") do
  132.                     sleep (5)
  133.                 end
  134.             end
  135.             turtle.digUp()
  136.         elseif turtle.attackUp() then
  137.             print "Attacking obstructing entity"
  138.         end
  139.     end
  140. end
  141. -- This function tries to refuel the turtle to make it have an <amount> of fuel
  142. -- It assumes that turtle has a refuel ender chest in slot 1
  143. function refuel (amount)
  144.     if turtle.getFuelLevel () > amount then
  145.             return true
  146.     else
  147.         turtle.select (1)
  148.         forcePlaceUp ()
  149.         turtle.suckUp ()
  150.         while turtle.refuel (1) do
  151.             if turtle.getFuelLevel() >= amount then
  152.                 turtle.dropUp ()
  153.                 turtle.digUp ()
  154.                 return true
  155.             end
  156.             if turtle.getItemCount (1) == 0 then
  157.                 turtle.suckUp ()
  158.             end
  159.         end
  160.     end
  161.     turtle.dropUp ()
  162.     turtle.digUp ()
  163.     return false
  164. end
  165.  
  166. -- This function checks if there are required amount of fuel,
  167. -- and if there's not, it tries to refuel it,
  168. -- if it fails, it hangs until refueling sucseeds
  169. function checkFuel (amount)
  170.     if turtle.getFuelLevel () > amount then
  171.         return true
  172.     else
  173.         if not refuel (amount) then
  174.             print "No fuel found! Please, add more fuel!"
  175.             while not refuel (amount) do
  176.                 sleep (2)
  177.             end
  178.         end
  179.         return true
  180.     end
  181. end
  182.  
  183. -- This function used to empty the turtle's inventory into the ender chest in the second slot
  184. -- Warning! Required a good sorting system able to empty the chest as quickly as possible!
  185. -- It will hang if chest is full, but it's not recommended
  186. function dumpStuff ()
  187.     turtle.select (2)
  188.     forcePlaceUp ()
  189.     for i = 3,16 do
  190.         while turtle.getItemCount (i) > 0 do
  191.             turtle.select (i)
  192.             turtle.dropUp ()
  193.             end
  194.     end
  195.     turtle.select (2)
  196.     turtle.digUp ()
  197.     turtle.select (1)
  198. end
  199.  
  200. function checkSpace ()
  201.     if turtle.getItemCount (16) > 0 then
  202.         dumpStuff ()
  203.     end
  204. end
  205.  
  206. -- Advanced movement function.
  207. -- Again, I use a set of tables with all the movements and other "directional" turtle actions
  208. -- The fuction detects if there is a turtle in the direction it wants to move, and if there is one, it waits while it moves away.
  209. -- If it waits too long (60 seconds), however, it just eats that turtle
  210. tMove = {
  211.     forward = turtle.forward,
  212.     up = turtle.up,
  213.     down = turtle.down
  214. }
  215. tDetect = {
  216.     forward = turtle.detect,
  217.     up = turtle.detectUp,
  218.     down = turtle.detectDown
  219. }
  220. tDig = {
  221.     forward = turtle.dig,
  222.     up = turtle.digUp,
  223.     down = turtle.digDown
  224. }
  225. tAttack = {
  226.     forward = turtle.attack,
  227.     up = turtle.attackUp,
  228.     down = turtle.attackDown
  229. }
  230. tIsTurtle = {
  231.     forward = function ()
  232.         if peripheral.isPresent ("front") and peripheral.getType ("front") == "turtle" then
  233.             return true
  234.         end
  235.         return false
  236.     end,
  237.     up = function ()
  238.         if peripheral.isPresent ("top") and peripheral.getType ("top") == "turtle" then
  239.             return true
  240.         end
  241.         return false
  242.     end,
  243.     down = function ()
  244.         if peripheral.isPresent ("bottom") and peripheral.getType ("bottom") == "turtle" then
  245.             return true
  246.         end
  247.         return false
  248.     end
  249. }
  250. function move (direction)
  251.     if not checkFuel (1) then
  252.         return false
  253.     end
  254.     while not tMove[direction]() do
  255.         if tDetect[direction]() then
  256.             if tIsTurtle[direction]() then
  257.                 print "Other turtle detected. Waiting..."
  258.                 while tIsTurtle[direction]() do
  259.                     sleep (5)
  260.                 end
  261.             else
  262.                 if tDig[direction]() then
  263.                     checkSpace ()
  264.                 else
  265.                     print "Can't pass the obstruction!"
  266.                     return false
  267.                 end
  268.             end
  269.         elseif tAttack[direction]() then
  270.             checkSpace ()
  271.         end
  272.     end
  273.     updateLocation (direction)
  274.     return true
  275. end
  276.  
  277. -- This one is used to move turtle to specified f direction OR up/down <count times>
  278. function moveEx (direction, count)
  279.     if type(direction) == "number" then
  280.         turnTo (direction)
  281.         for i = 1, count do
  282.             move ("forward")
  283.         end
  284.     else
  285.         for i = 1, count do
  286.             move (direction)
  287.         end
  288.     end
  289. end
  290.  
  291. -- A height function. For convinience
  292. function height (value)
  293.     local diff = value - location.y
  294.     refuel(math.abs(diff))
  295.     if diff > 0 then
  296.         moveEx ("up", diff)
  297.     elseif diff < 0 then
  298.         moveEx ("down", -diff)
  299.     end
  300. end
  301.  
  302. -- Small goto function. It moves turtle in one axis.
  303. -- Example: module.gotoOne ("x", 10) moves the turtle to x = 10
  304. function gotoOne (coordinate, value)
  305.     if coordinate == "y" then
  306.         height (value)
  307.     elseif coordinate == "x" then
  308.         local diff = value - location.x
  309.         refuel(math.abs(diff))
  310.         if diff > 0 then
  311.             moveEx (0, diff)
  312.         elseif diff < 0 then
  313.             moveEx (2, -diff)
  314.         end
  315.     elseif coordinate == "z" then
  316.         local diff = value - location.z
  317.         refuel(math.abs(diff))
  318.         if diff > 0 then
  319.             moveEx (1, diff)
  320.         elseif diff < 0 then
  321.             moveEx (3, -diff)
  322.         end
  323.     end
  324. end
  325.  
  326. -- Let's organize our movement a bit to avoid movement deadlocks. Thanks to Doyle3694 for the idea
  327. -- It is 2D navigation, so height is not taken into account. It will allow turtles to relocate themselves to their mining positions
  328. -- without stucking.
  329. -- I'll divide all the 2D area to four zones. In each zone turtle can move only in two directions.
  330. -- Zones are determinated by the pair of coordinates. Turtles will move clockwise relative to the point with that coordinates
  331. -- height is the heighat at which all navigation is done
  332. -- There is a function to set the zones
  333. local naviZones = {x = 0, z = 0, height = 0}
  334. function setZones (x, z, height)
  335.     naviZones.x = x
  336.     naviZones.z = z
  337.     naviZones.height = height
  338. end
  339.  
  340. -- There is a table used to determine to which directions turtle is allowed to move.
  341. -- Direction "first" is the direction turtle will move first ^_^ This one can't lead out of the zone.
  342. -- Direction "second" is leading to the next zone.
  343. -- Directions are the tables of coordinate and direction (+1 - positive, -1 - negative)
  344. local tZones = {
  345.     z1 = {first = {"x",  1}, second = {"z",  1}},
  346.     z2 = {first = {"z",  1}, second = {"x", -1}},
  347.     z3 = {first = {"x", -1}, second = {"z", -1}},
  348.     z4 = {first = {"z", -1}, second = {"x",  1}}
  349. }
  350.  
  351. -- Here is a function to determine in which zone turtle is
  352. function currentZone ()
  353.     if location.x > naviZones.x then
  354.         if location.z > naviZones.z then
  355.             return "z2"
  356.         else
  357.             return "z1"
  358.         end
  359.     else
  360.         if location.z > naviZones.z then
  361.             return "z3"
  362.         else
  363.             return "z4"
  364.         end
  365.     end
  366. end
  367.  
  368. -- And there is one used to navigate turtle, taking into account zones.
  369. -- All the navigation is done at zones.height and then turtle changes it's height
  370. function navigateTo (destination)
  371.     -- go to zones.height only if we need to move
  372.     if destination.x ~= location.x or destination.z ~= location.z then
  373.         height (naviZones.height)
  374.     end
  375.     while location.x ~= destination.x or location.z ~= destination.z do -- navigate between zones until we are in the place
  376.         local zone = currentZone ()
  377.         -- Let's unpack all the coordinates
  378.         local coordinateFirst, directionFirst = unpack (tZones[zone].first)  
  379.         local coordinateSecond, directionSecond = unpack (tZones[zone].second)
  380.         -- And check some things
  381.         -- if we are allowed to move at both directions - just do it!
  382.         if (destination[coordinateFirst]-location[coordinateFirst]) * directionFirst >= 0
  383.             and (destination[coordinateSecond]-location[coordinateSecond]) * directionSecond >= 0 then
  384.             -- BTW, we just check there that coordinate difference and destination has the same sign
  385.             -- Otherwise turtle that is already went to "second" coordinate won't be able to change it's "first" coordinate
  386.             -- Then just use gotoOne to go to the desired location
  387.             gotoOne (coordinateFirst, destination[coordinateFirst])            
  388.             gotoOne (coordinateSecond, destination[coordinateSecond])            
  389.         else
  390.             -- If we are not allowed to move by any of those coordinates in current zone
  391.             -- Just head up to the second zone
  392.             local nextPosition = naviZones[coordinateSecond]
  393.             -- We need to do that for actually leave z1 and z4 since they are one block bigger
  394.             if zone == "z4" or zone == "z1" then
  395.                 nextPosition = nextPosition + 1
  396.             end
  397.             -- go to the next zone!                
  398.             gotoOne (coordinateSecond, nextPosition)
  399.         end        
  400.     end
  401.     -- if y is set - move to y.
  402.     if destination.y ~= nil then
  403.         height(destination.y)
  404.     end
  405.     -- same for f
  406.     if destination.f ~= nil then
  407.         turnTo(destination.f)
  408.     end
  409. end
  410.  
  411. --[[
  412. *********************************************************************************************
  413. *                                    Communication Part                                     *
  414. *********************************************************************************************
  415. ]]--
  416. -- There are some variables used for communication
  417. -- ID is the turtle's unique identifier
  418. -- Master is the ID of the master
  419. -- Channel is the channel used to send messages to Master
  420. -- Master should respond on channel+1
  421. -- Type is the type of the module. Used by Master to determine what it can do
  422. local ID = 0
  423. local Master = 0
  424. local channel = 100
  425. local Type = "default"
  426. function setType (newType)
  427.     Type = newType
  428. end
  429.  
  430. -- Wee need a modem to communicate.
  431. local modem = peripheral.wrap ("right")
  432.  
  433. -- The first function is used to parse a message received on modem
  434. -- It determines whether the message is valid or not
  435. -- Message should be a table with fields
  436. --  1) Protocol - must be equal to "KuuNet"
  437. --  2) ID - that must be equal to turtle's ID
  438. --  3) Master - that must be equal to Master variable.
  439. --  Some other fiels
  440. function isValid (message)
  441.     if message ~= nil then
  442.         if message.Protocol ~= "KuuNet" then
  443.             return false
  444.         end
  445.         if message.ID ~= ID then
  446.             return false
  447.         end
  448.         if message.Master ~= Master then
  449.             return false
  450.         end
  451.         return true
  452.     else
  453.         return false
  454.     end
  455. end
  456.  
  457. -- This function sends a request to Master and waits for a response.
  458. -- Request is just a simple string
  459. -- It returns response as message or nil
  460. function request (message, timeout)
  461.     -- open a channel to listen for response
  462.     modem.open (channel+1)
  463.     local request = {
  464.         Protocol = "KuuNet",
  465.         ID = ID,
  466.         Type = Type,
  467.         Master = Master,
  468.         Request = message
  469.     }
  470.     modem.transmit (channel, channel+1, textutils.serialize(request))
  471.     local response = nil
  472.     parallel.waitForAny (
  473.         function ()
  474.             sleep (timeout)
  475.         end,
  476.         function ()
  477.             while true do
  478.                 local event, modemSide, sndChan, rplyChan, text_msg, senderDistance = os.pullEvent("modem_message")
  479.                 local msg = textutils.unserialize (text_msg)
  480.                 if isValid(msg) then
  481.                     response = msg
  482.                     break
  483.                 end
  484.             end
  485.         end
  486.     )
  487.     modem.close (channel+1)
  488.     return response
  489. end
  490.  
  491. --[[
  492. *********************************************************************************************
  493. *                                     Operation part                                        *
  494. *********************************************************************************************
  495. ]]--
  496. -- Fallback location is the point where module goes if it can't get a task from Master
  497. local fallbackLocation = {}
  498.  
  499. -- We need to read all the data we need to move around - turtle's position and navigation zones
  500. -- "initdata" is the file created by Master and containing all the data that's similar for each module placed by that Master
  501. function init ()
  502.     local file = fs.open ("/disk/initdata", "r")
  503.     local communicationData = textutils.unserialize (file.readLine ())
  504.     Master = communicationData.MasterID
  505.     channel = communicationData.channel
  506.     -- set module's location
  507.     location = textutils.unserialize (file.readLine ())
  508.     -- set navigation data
  509.     naviZones = textutils.unserialize (file.readLine ())
  510.    
  511.     -- set fallbackLocation to the point where we are, but at navigation height.
  512.     fallbackLocation = {x = location.x, y = naviZones.height, z = location.z, f = location.f}
  513.    
  514.     -- Then send a request to Master, to get our ID and to let Master remember us in its state table for modules
  515.     local response = request ("Master", 5)
  516.     ID = response.NewID
  517.    
  518.     -- move forward to be ready to go down. Because of the placement rules, master should place modules on some block, so the block under the module won't be empty.
  519.     move("forward")
  520. end
  521.  
  522. -- Here is the task table. It is the table, that contains all the task module can get form Master and associated functions
  523. -- The table should be filled by user of this API ^_~
  524. -- Module operation is
  525. --  1) Requesting a task from master
  526. --  2) Moving to the location specified in response (if present)
  527. --  3) Executing the associated function
  528. -- There is always a default task "return" which returns module to Master.
  529. -- response from Master is always passed to the function, so, if it contains additional data, you can use it
  530. -- Return is a task to return. Master should pass a xet of coordinates as well as "f" direction to make module face the Master.
  531. tTasks = {
  532.     Return = function (response)
  533.         move ("forward")
  534.         request ("Returned", 3)
  535.         sleep (30)
  536.     end
  537. }
  538.  
  539. -- Function to add tasks to the task table
  540. function addTasks (taskTable)
  541.     for key, value in pairs (taskTable) do
  542.         tTasks[key] = value
  543.     end
  544. end
  545.  
  546. -- And one to remove a task ^_^
  547. function rmTask (task)
  548.     tTasks [task] = nil
  549. end
  550.  
  551. -- And a main operation function
  552. function operate ()
  553.     while true do
  554.         local task
  555.         -- failedRequests indicates how many times Master didn't answered to request
  556.         local failedRequests = 0
  557.         while task == nil do
  558.             task = request ("Task", 5)
  559.             if task == nil then
  560.                 failedRequests = failedRequests + 1
  561.                 -- if there are 5 requests failed, then change position to the point of last communication
  562.                 if failedRequests >= 6 then
  563.                     print "No response from Master after 6 attempts. Moving to fallback position..."
  564.                     navigateTo (fallbackLocation)
  565.                 end
  566.             else
  567.                 lastCommunication = getPosition()
  568.             end
  569.         end
  570.         if task.x ~= nil and task.z ~= nil then
  571.             navigateTo (task)
  572.         end
  573.         tTasks [task.Task](task) -- Task. tttTask. Tasktask. taskataskatask. HerpDerp.
  574.     end
  575. end
Advertisement
Add Comment
Please, Sign In to add comment