diff --git a/ReadMe.md b/ReadMe.md index 76e7031..408511a 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,105 +1,87 @@ # Behave -A simple implementation of behavior trees in MoonScript / Lua. +A simple implementation of behavior trees in MoonScript / Lua. Define behaviors +as functions or small tables, then call `behave.make` on them to get a function +you can call repeatedly to execute behaviors. + +A node should return a truthy or falsy value to indicate success or failure, or +`behave.running` to indicate that the node needs to be called again to continue +running. + +Examples are in MoonScript, but shouldn't be too difficult to understand even if +you are unfamiliar with its syntax. ## Example -(Note: The top example and last example work. The others are from experimenting -with ideas about how to make it easier to write code interfacing with this -library.) +``` +TODO +``` + +## Leaf Nodes + +The easiest node is just a function that will be passed all arguments sent to a +behavior tree it is part of. There is a slightly more complex leaf node for +maintaining an internal state, and optional `start`/`finish` functions only +called at the beginning and end of processing. ``` -bt = require "behave" - -tree = bt.Sequence({ - bt.Node({ - run = function(self, obj, ...) - -- do stuff - end, - finish = function(self, obj, ...) - -- finish up - end - }), - bt.Random({ - bt.Node({ - -- add some functions - }), - bt.Node({ - -- even more functionality! - }) - }) -}) - -tree:addObject(obj) -tree:update(obj) - -node1 = { - -- pretend this has run/finish functions or whatever +WalkPath = { + start: (state, entity) -> + state.path = findPath(entity, entity.target) + return state.path + run: (state, entity) -> + -- this will not run if start fails + state.path.step! + finish: (state, entity) -> + -- this will run only if run returns a truthy value besides behave.running + state.path = nil } -node2 = {} -- same -bt.Make ({ - "Sequence", - node1, node2, { - "Random", - node3, node4 -- these are in 'Random' - } -}) - -tree = bt.Factory({ - "Sequence", - { - "Node", - { - run = function(self) end, - finish = function(self) end - } - } -}) - -node1 = bt.Node() -function node1:run() - -- do stuff! -end ``` -## The Leaf Node +## The Decorator Node -`Node` accepts a table of values to be set on it. It expects a `run` function, -and optionally a `start` and a `finish` function, which will only be run at the -beginning and end of a node being run (whereas `run` can be called multiple -times). +A very basic extension to the result of a leaf node, allowing you to alter the +result it returns. Note: You cannot alter a `behave.running` return. -To run a behavior tree, call `update` on itself, with optional arguments (which -will be passed to their children as they are called). +``` +InvertSomeNode = { + decorator: (result) -> + return not result + SomeNode +} +``` ## Composite Nodes -Pass a table of nodes to these to set their contents. All composite nodes repeat -after the entire tree has been processed. +Selector skips any failures and returns on the first node that returns a truthy +value (or returns `false` if nothing succeeds). Sequence continues until there +is a failure or returns success. Random executes a random node underneath it. -- Composite: Super class of the other composite nodes. -- Sequence: Runs children until a failure or success of all. -- Selector: Runs children until a success or failure of all. -- Random: Runs a random child and returns its result. -- RandomSequence: Randomizes the order of its children, then acts like a - Sequence. -- RandomSelector: Randomizes the order of its children, then acts like a - Selector. +``` +Behaviors = { + type: behave.Random + WalkRandomly, LookAtPhone, LeaveArea +} +``` -## Decorator Nodes +## Custom Nodes -Pass a single node to these, except for `Repeat`, which needs a number followed -by a node. All decorator nodes (except `RunOnce`) repeat after the entire tree -has been processed. +You can create custom nodes and easily use them. Rather than explaining how to, +here's an example: -- Decorator: Superclass. Just returns the result of its child node. -- Repeat: Repeats a node a specified number of times, fails if the node fails. -- Succeed: Runs a node and returns success. -- Fail: Runs a node and returns failure. -- Invert: Runs a node, reporting a success on fail, and failure on success. -- RunOnce: Runs a node, reporting its results, and fail after that. +``` +-- defining the node type: +Invert = (tab) -> -- your fn will be passed a table describing the node + node = behave.make tab[1] -- call make on any sub-nodes that you will be using, save the result + return (...) -> -- use variable arguments, so sub-nodes can + result = node(...) -- get what they need + unless result == behave.running -- running nodes should not be interrupted + result = not result -- and finally we invert the result before + return result -- returning it -## Return Values - -Within the module, `success`, `running`, and `fail` are defined. They are also -present on all nodes (so you can use `self.success` and such). +-- using the node type: +SomeInvertedNode = { + type: Invert -- this is how the library knows what function to call + SomeNode -- this is the node that will be inverted +} +``` diff --git a/behave.lua b/behave.lua index 9b84d5e..a1e7f80 100644 --- a/behave.lua +++ b/behave.lua @@ -1,845 +1,129 @@ -local success = setmetatable({ }, { - __tostring = function() - return "success" - end -}) +local make, Node, Decorate local running = setmetatable({ }, { __tostring = function() return "running" end }) -local fail = setmetatable({ }, { - __tostring = function() - return "fail" +local get_nodes +get_nodes = function(tab) + local nodes = { } + for _index_0 = 1, #tab do + local node = tab[_index_0] + table.insert(nodes, make(node)) end -}) -local Node -do - local _class_0 - local _base_0 = { - addObject = function(self, obj) - obj[self.u] = { - started = false - } - end, - run = function(self) - return success - end, - update = function(self, obj, ...) - local result = success - if not obj[self.u].started and self.start then - result = self:start(obj, ...) - obj[self.u].started = true - end - if result == success then - result = self:run(obj, ...) - end - if result == success then - if self.finish then - result = self:finish(obj, ...) - end - obj[self.u].started = false - end - return result - end - } - _base_0.__index = _base_0 - _class_0 = setmetatable({ - __init = function(self, fns) - if fns == nil then - fns = { } - end - for key, value in pairs(fns) do - self[key] = value - end - self.success = success - self.running = running - self.fail = fail - self.u = { } - end, - __base = _base_0, - __name = "Node" - }, { - __index = _base_0, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - Node = _class_0 + return nodes end -local Composite -do - local _class_0 - local _parent_0 = Node - local _base_0 = { - addObject = function(self, obj) - local _list_0 = self.nodes - for _index_0 = 1, #_list_0 do - local node = _list_0[_index_0] - node:addObject(obj) - end - end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, nodes) - if nodes == nil then - nodes = { } - end - self.nodes = nodes - return _class_0.__parent.__init(self) - end, - __base = _base_0, - __name = "Composite", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) +make = function(tab) + if "function" == type(tab) then + return tab + elseif "function" == type(tab.type) then + return tab.type(tab) + elseif tab.decorate then + return Decorate(tab) + else + return Node(tab) end - Composite = _class_0 end -local Sequence -do - local _class_0 - local _parent_0 = Composite - local _base_0 = { - addObject = function(self, obj) - _class_0.__parent.__base.addObject(self) - obj[self.u] = { - index = 0, - running = 0 - } - end, - update = function(self, obj, ...) - local result = success - if obj[self.u].running then - result = obj[self.u].running:update(obj, ...) - if not (result == running) then - obj[self.u].running = false - end +Node = function(tab) + local state, started = { }, false + return function(...) + local result + if not (started) then + if tab.start then + result = tab.start(state, ...) end - while result == success and obj[self.u].index < #self.nodes do - obj[self.u].index = obj[self.u].index + 1 - result = self.nodes[obj[self.u].index]:update(obj, ...) + if not (result == false) then + started = true end - if result == running then - obj[self.u].running = self.nodes[obj[self.u].index] - else - obj[self.u].index = 0 - end - return result end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "Sequence", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 + if not (result == false) then + result = tab.run(state, ...) end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) + if result ~= running then + started = false + if result and tab.finish then + result = tab.finish(state, ...) + end + end + return result end - Sequence = _class_0 -end -local Selector -do - local _class_0 - local _parent_0 = Composite - local _base_0 = { - addObject = function(self, obj) - _class_0.__parent.__base.addObject(self) - obj[self.u] = { - index = 0, - running = 0 - } - end, - update = function(self, obj, ...) - local result = fail - if obj[self.u].running then - result = obj[self.u].running:update(obj, ...) - if not (result == running) then - obj[self.u].running = false - end - end - while result == fail and obj[self.u].index < #self.nodes do - obj[self.u].index = obj[self.u].index + 1 - result = self.nodes[obj[self.u].index]:update(obj, ...) - end - if result == running then - obj[self.u].running = self.nodes[obj[self.u].index] - else - obj[self.u].index = 0 - end - return result - end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "Selector", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - Selector = _class_0 -end -local Random -do - local _class_0 - local _parent_0 = Composite - local _base_0 = { - update = function(self, obj, ...) - local index = math.floor(math.random() * #self.nodes + 1) - return self.nodes[index]:update(obj, ...) - end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "Random", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - Random = _class_0 -end -local Randomizer -do - local _class_0 - local _parent_0 = Composite - local _base_0 = { - addObject = function(self, obj) - _class_0.__parent.__base.addObject(self) - obj[self.u] = { - running = false - } - return self:shuffle(obj) - end, - shuffle = function(self, obj) - obj[self.u].shuffledNodes = { } - for i = 1, #self.nodes do - local r = math.random(i) - if not (r == i) then - obj[self.u].shuffledNodes[i] = obj[self.u].shuffledNodes[r] - end - obj[self.u].shuffledNodes[r] = self.nodes[i] - end - end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "Randomizer", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - Randomizer = _class_0 -end -local RandomSequence -do - local _class_0 - local _parent_0 = Randomizer - local _base_0 = { - update = function(self, obj, ...) - local result = success - if obj[self.u].running then - result = obj[self.u].running:update(obj, ...) - if not (result == running) then - obj[self.u].running = false - end - end - local tmp - while result == success and #obj[self.u].shuffledNodes > 0 do - result = obj[self.u].shuffledNodes[1]:update(obj, ...) - tmp = table.remove(obj[self.u].shuffledNodes, 1) - end - if result == running then - obj[self.u].running = tmp - else - self:shuffle(obj) - end - return result - end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "RandomSequence", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - RandomSequence = _class_0 -end -local RandomSelector -do - local _class_0 - local _parent_0 = Randomizer - local _base_0 = { - update = function(self, obj, ...) - local result = fail - if obj[self.u].running then - result = obj[self.u].running:update(obj, ...) - if not (result == running) then - obj[self.u].running = false - end - end - local tmp - while result == fail and #obj[self.u].shuffledNodes > 0 do - result = obj[self.u].shuffledNodes[1]:update(obj, ...) - tmp = table.remove(obj[self.u].shuffledNodes, 1) - end - if result == running then - obj[self.u].running = tmp - else - self:shuffle() - end - return result - end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "RandomSelector", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - RandomSelector = _class_0 end local Decorator -do - local _class_0 - local _parent_0 = Node - local _base_0 = { - addObject = function(self, obj) - return self.node:addObject(obj) - end, - update = function(self, obj, ...) - return self.node:update(obj, ...) +Decorator = function(tab) + local node = make(tab[1]) + return function(...) + local result = node(object, ...) + if not (result == running) then + result = tab.decorate(result, ...) end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, node) - if node == nil then - node = Node() - end - self.node = node - return _class_0.__parent.__init(self) - end, - __base = _base_0, - __name = "Decorator", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) + return result end - Decorator = _class_0 end -local Repeat -do - local _class_0 - local _parent_0 = Decorator - local _base_0 = { - addObject = function(self, obj) - _class_0.__parent.__base.addObject(self) - obj[self.u] = { - counter = 1, - running = false - } - end, - update = function(self, obj, ...) - local result = success - if obj[self.u].running then - result = self.node:update(obj, ...) - if not (result == running) then - obj[self.u].running = false - end +local Selector +Selector = function(tab) + local nodes = get_nodes(tab) + local length = #nodes + local i = 1 + return function(...) + local result = nodes[i](...) + while not result do + i = i + 1 + if i > length then + i = 1 + return false end - while result == success and obj[self.u].counter < self.cycles do - obj[self.u].counter = obj[self.u].counter + 1 - result = self.node:update(obj, ...) - end - if result == running then - obj[self.u].running = true - else - obj[self.u].counter = 1 - end - return result + result = nodes[i](...) end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, cycles, node) - if cycles == nil then - cycles = 2 - end - if node == nil then - node = Node() - end - self.cycles, self.node = cycles, node - return _class_0.__parent.__init(self, self.node) - end, - __base = _base_0, - __name = "Repeat", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 + if result ~= running then + i = 1 end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) + return result end - Repeat = _class_0 end -local Succeed -do - local _class_0 - local _parent_0 = Decorator - local _base_0 = { - update = function(self, obj, ...) - if running == self.node:update(obj, ...) then - return running - else - return success - end - end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "Succeed", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - Succeed = _class_0 -end -local Fail -do - local _class_0 - local _parent_0 = Decorator - local _base_0 = { - update = function(self, obj, ...) - if running == self.node:update(obj, ...) then - return running - else - return fail - end - end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "Fail", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - Fail = _class_0 -end -local Invert -do - local _class_0 - local _parent_0 = Decorator - local _base_0 = { - update = function(self, obj, ...) - local result = self.node:update(obj, ...) - if result == running then - return running - elseif result == success then - return fail - else - return success - end - end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "Invert", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) - end - Invert = _class_0 -end -local RunOnce -do - local _class_0 - local _parent_0 = Decorator - local _base_0 = { - addObject = function(self, obj) - obj[self.u] = { - ran = false - } - end, - update = function(self, obj, ...) - if not (obj[self.u].ran) then - local result = self.node:update(obj, ...) - if not (result == running) then - obj[self.u].ran = true - end +local Sequence +Sequence = function(tab) + local nodes = get_nodes(tab) + local length = #nodes + local i = 1 + return function(...) + local result = nodes[i](...) + while result and result ~= running do + i = i + 1 + if i > length then + i = 1 return result - else - return fail end + result = nodes[i](...) end - } - _base_0.__index = _base_0 - setmetatable(_base_0, _parent_0.__base) - _class_0 = setmetatable({ - __init = function(self, ...) - return _class_0.__parent.__init(self, ...) - end, - __base = _base_0, - __name = "RunOnce", - __parent = _parent_0 - }, { - __index = function(cls, name) - local val = rawget(_base_0, name) - if val == nil then - local parent = rawget(cls, "__parent") - if parent then - return parent[name] - end - else - return val - end - end, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 + if not (result) then + i = 1 end - }) - _base_0.__class = _class_0 - if _parent_0.__inherited then - _parent_0.__inherited(_parent_0, _class_0) + return result end - RunOnce = _class_0 end -local Class -Class = function(name, parent) - local newClass, base - base = { - __index = base, - __class = newClass - } - newClass = setmetatable({ - __init = function() end, - __base = base, - __name = name - }, { - __call = function(cls, ...) - local self = setmetatable({ }, base) - cls.__init(self, ...) - return self +local Random +Random = function(tab) + local nodes = get_nodes(tab) + local length = #nodes + local r + return function(...) + if not (r) then + r = 1 + math.random(length) end - }) - if parent then - setmetatable(base, { - __parent = parent.__base - }) - newClass.__parent = parent - newClass.__index = function(cls, name) - local val = rawget(base, name) - if val == nil then - return parent[name] - else - return val - end - end - if parent.__inherited then - parent:__inherited(newClass) + local result = nodes[r](...) + if not (result == running) then + r = nil end + return result end - return newClass, base end -return setmetatable({ - Node = Node, - Sequence = Sequence, - Selector = Selector, - Random = Random, - RandomSequence = RandomSequence, - RandomSelector = RandomSelector, - Decorator = Decorator, - Repeat = Repeat, - Succeed = Succeed, - Fail = Fail, - Invert = Invert, - RunOnce = RunOnce, - success = success, +return { + success = true, running = running, - fail = fail, - Class = Class -}, { - __call = function(bt, ...) - return bt.Sequence(...) - end -}) + failure = false, + Node = Node, + Decorator = Decorator, + Selector = Selector, + Sequence = Sequence, + Random = Random +} diff --git a/behave.moon b/behave.moon index a3d4f07..8463d74 100644 --- a/behave.moon +++ b/behave.moon @@ -1,316 +1,100 @@ -success = setmetatable({}, {__tostring: -> return "success"}) -running = setmetatable({}, {__tostring: -> return "running"}) -fail = setmetatable({}, {__tostring: -> return "fail"}) +local make, Node, Decorate -class Node - new: (fns={}) => - for key, value in pairs fns - @[key] = value +running = setmetatable {}, __tostring: -> return "running" - @success = success - @running = running - @fail = fail +get_nodes = (tab) -> + nodes = {} + for node in *tab + table.insert nodes, make node + return nodes - @u = {} +-- produces a callable function from a defined node or full behavior tree +make = (tab) -> + if "function" == type tab + return tab + elseif "function" == type tab.type + return tab.type tab + elseif tab.decorate + return Decorate tab + else + return Node tab - addObject: (obj) => - obj[@u] = { - started: false - } - - run: => - return success - - update: (obj, ...) => - result = success - if not obj[@u].started and @start - result = @\start obj, ... - obj[@u].started = true - if result == success - result = @\run obj, ... - if result == success - result = @\finish(obj, ...) if @finish - obj[@u].started = false +-- complex leaf node +-- state is preserved through calls, optional start/finish functions +Node = (tab) -> + state, started = {}, false + return (...) -> + local result + unless started + result = tab.start state, ... if tab.start + started = true unless result == false + result = tab.run state, ... unless result == false + if result != running + started = false + result = tab.finish state, ... if result and tab.finish return result -class Composite extends Node - new: (@nodes={}) => - super! - - addObject: (obj) => - for node in *@nodes - node\addObject obj - --- Runs children in order until one returns fail, or all succeed. -class Sequence extends Composite - addObject: (obj) => - super! - obj[@u] = { - index: 0 - running: 0 - } - - update: (obj, ...) => - result = success - - if obj[@u].running - result = obj[@u].running\update obj, ... - unless result == running - obj[@u].running = false - - while result == success and obj[@u].index < #@nodes - obj[@u].index += 1 - result = @nodes[obj[@u].index]\update obj, ... - - if result == running - obj[@u].running = @nodes[obj[@u].index] - else - obj[@u].index = 0 - +-- modifies the result of a node before returning it +Decorator = (tab) -> + node = make tab[1] + return (...) -> + result = node object, ... + result = tab.decorate result, ... unless result == running return result --- Runs children in order until one succeeds or all fail. -class Selector extends Composite - addObject: (obj) => - super! - obj[@u] = { - index: 0 - running: 0 - } - - update: (obj, ...) => - result = fail - if obj[@u].running - result = obj[@u].running\update obj, ... - unless result == running - obj[@u].running = false - - while result == fail and obj[@u].index < #@nodes - obj[@u].index += 1 - result = @nodes[obj[@u].index]\update obj, ... - - if result == running - obj[@u].running = @nodes[obj[@u].index] - else - obj[@u].index = 0 - +-- returns first success/running, or failure +Selector = (tab) -> + nodes = get_nodes tab + length = #nodes + i = 1 + return (...) -> + result = nodes[i](...) + while not result + i += 1 + if i > length + i = 1 + return false + result = nodes[i](...) + i = 1 if result != running return result --- Runs a random child. -class Random extends Composite - update: (obj, ...) => - index = math.floor math.random! * #@nodes + 1 - return @nodes[index]\update obj, ... - -class Randomizer extends Composite - addObject: (obj) => - super! - obj[@u] = { - running: false - } - @shuffle obj - - shuffle: (obj) => - obj[@u].shuffledNodes = {} - for i = 1, #@nodes - r = math.random i - unless r == i - obj[@u].shuffledNodes[i] = obj[@u].shuffledNodes[r] - obj[@u].shuffledNodes[r] = @nodes[i] - --- Randomizes order of nodes in between complete runs of them as a Sequence. -class RandomSequence extends Randomizer - update: (obj, ...) => - result = success - - if obj[@u].running - result = obj[@u].running\update obj, ... - unless result == running - obj[@u].running = false - - local tmp - while result == success and #obj[@u].shuffledNodes > 0 - result = obj[@u].shuffledNodes[1]\update obj, ... - tmp = table.remove obj[@u].shuffledNodes, 1 - - if result == running - obj[@u].running = tmp - else - @shuffle obj - +-- returns first running/failure, or success +Sequence = (tab) -> + nodes = get_nodes tab + length = #nodes + i = 1 + return (...) -> + result = nodes[i](...) + while result and result != running + i += 1 + if i > length + i = 1 + return result + result = nodes[i](...) + i = 1 unless result return result --- Randomizes order of nodes in between complete runs of them as a Selector. -class RandomSelector extends Randomizer - update: (obj, ...) => - result = fail - - if obj[@u].running - result = obj[@u].running\update obj, ... - unless result == running - obj[@u].running = false - - local tmp - while result == fail and #obj[@u].shuffledNodes > 0 - result = obj[@u].shuffledNodes[1]\update obj, ... - tmp = table.remove obj[@u].shuffledNodes, 1 - - if result == running - obj[@u].running = tmp - else - @shuffle! - +-- returns success/running/failure from random child +Random = (tab) -> + nodes = get_nodes tab + length = #nodes + local r + return (...) -> + unless r + r = 1 + math.random length + result = nodes[r](...) + unless result == running + r = nil return result -class Decorator extends Node - new: (@node=Node!) => - super! - - addObject: (obj) => - @node\addObject obj - - update: (obj, ...) => - return @node\update obj, ... - --- Repeats a node a specified number of times, unless it fails. -class Repeat extends Decorator - new: (@cycles=2, @node=Node!) => - super @node - - addObject: (obj) => - super! - obj[@u] = { - counter: 1 - running: false - } - - update: (obj, ...) => - result = success - - if obj[@u].running - result = @node\update obj, ... - unless result == running - obj[@u].running = false - - while result == success and obj[@u].counter < @cycles - obj[@u].counter += 1 - result = @node\update obj, ... - - if result == running - obj[@u].running = true - else - obj[@u].counter = 1 - - return result - --- Returns success whether or not the node succeeds. -class Succeed extends Decorator - update: (obj, ...) => - if running == @node\update obj, ... - return running - else - return success - --- Returns fail whether or not the node fails. -class Fail extends Decorator - update: (obj, ...) => - if running == @node\update obj, ... - return running - else - return fail - --- Returns success when the node fails, and failure on success. -class Invert extends Decorator - update: (obj, ...) => - result = @node\update obj, ... - if result == running - return running - elseif result == success - return fail - else - return success - --- Only runs children once, and returns fail from then on. -class RunOnce extends Decorator - addObject: (obj) => - obj[@u] = { - ran: false - } - - update: (obj, ...) => - unless obj[@u].ran - result = @node\update obj, ... - unless result == running - obj[@u].ran = true - return result - else - return fail - --- A MoonScript-compatible class implementation for creating your own classes. -Class = (name, parent) -> - local newClass, base - base = { - __index: base - __class: newClass - } - - newClass = setmetatable { - __init: -> - __base: base - __name: name - }, { - __call: (cls, ...) -> - @ = setmetatable({}, base) - cls.__init(@, ...) - return @ - } - - if parent - setmetatable base, { - __parent: parent.__base - } - - newClass.__parent = parent - newClass.__index = (cls, name) -> - val = rawget(base, name) - if val == nil - return parent[name] - else - return val - - if parent.__inherited - parent\__inherited newClass - - return newClass, base - -setmetatable { - -- Leaf Node - :Node - - -- Composite Nodes - :Composite - :Sequence - :Selector - :Random - :RandomSequence - :RandomSelector - - -- Decorator Nodes - :Decorator - :Repeat - :Succeed - :Fail - :Invert - :RunOnce - - -- Return Values - :success +{ + success: true :running - :fail + failure: false - -- Utility Fns - :Class -}, { - __call: (bt, ...) -> - bt.Sequence ... + :Node + :Decorator + :Selector + :Sequence + :Random }