From c5adc1b351cbe61f845e1a358834e35d5c0fed62 Mon Sep 17 00:00:00 2001 From: Paul Liverman III Date: Thu, 7 Dec 2017 20:54:54 -0800 Subject: [PATCH] init --- .gitignore | 1 + ReadMe.md | 100 ++++++++++++++++++ behave.moon | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 388 insertions(+) create mode 100644 .gitignore create mode 100644 ReadMe.md create mode 100644 behave.moon diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d907c43 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.lua diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..6c80740 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,100 @@ +# Behave + +A simple implementation of behavior trees in MoonScript / Lua. + +## 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.) + +``` +bt = require "behave" + +tree = bt.Sequence({ + bt.Node({ + run = function(self) + -- do stuff + end, + finish = function(self) + -- finish up + end + }), + bt.Random({ + bt.Node({ + -- add some functions + }), + bt.Node({ + -- even more functionality! + }) + }) +}) + +node1 = { + -- pretend this has run/finish functions or whatever +} +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 + +`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). + +To run a behavior tree, call `update` on itself, with optional arguments (which +will be passed to their children as they are called). + +## Composite Nodes + +Pass a table of nodes to these to set their contents. All composite nodes repeat +after the entire tree has been processed. + +- 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. + +## Decorator Nodes + +Pass a single node to these, except for `Repeat`, which needs a number & a node. +All decorator nodes (except `RunOnce`) repeat after the entire tree has been +processed. + +- Repeat: Repeats a node a specified number of times, aborts 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. + +## 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). diff --git a/behave.moon b/behave.moon new file mode 100644 index 0000000..c597f3f --- /dev/null +++ b/behave.moon @@ -0,0 +1,287 @@ +success = setmetatable({}, {__tostring: -> return "success"}) +running = setmetatable({}, {__tostring: -> return "running"}) +fail = setmetatable({}, {__tostring: -> return "fail"}) + +class Node + new: (fns={}) => + for key, value in pairs fns + @[key] = value + + @success = success + @running = running + @fail = fail + + @started = false + + run: => + return success + + update: (...) => + result = success + if not @started and @start + result = @\start ... + @started = true + if result == success + result = @\run ... + if result == success + result = @\finish(...) if @finish + @started = false + return result + +-- Runs children in order until one returns fail, or all succeed. +class Sequence extends Node + new: (@nodes={}) => + super! + + @index = 0 + @_running = false + + update: (...) => + result = success + + if @_running + result = @_running\update ... + unless result == running + @_running = false + + while result == success and @index < #@nodes + @index += 1 + result = @nodes[@index]\update ... + + if result == running + @_running = @nodes[@index] + else + @index = 0 + + return result + +-- Runs children in order until one succeeds or all fail. +class Selector extends Node + new: (@nodes={}) => + super! + + @index = 0 + @_running = false + + update: (...) => + result = fail + if @_running + result = @_running\update ... + unless result == running + @_running = false + + while result == fail and @index < #@nodes + @index += 1 + result = @nodes[@index]\update ... + + if result == running + @_running = @nodes[@index] + else + @index = 0 + + return result + +-- Runs a random child. +class Random extends Node + new: (@nodes={}) => + super! + + update: (...) => + index = math.floor math.random! * #@nodes + 1 + return @nodes[index]\update ... + +-- Randomizes order of nodes in between complete runs of them as a Sequence. +class RandomSequence extends Node + new: (@nodes={}) => + super! + + @_running = false + @shuffle! + + shuffle: => + @_shuffled = {} + for i = 1, #@nodes + r = math.random i + unless r == i + @_shuffled[i] = @_shuffled[r] + @_shuffled[r] = @nodes[i] + + update: (...) => + result = success + + if @_running + result = @_running\update ... + unless result == running + @_running = false + + local tmp + while result == success and #@_shuffled > 0 + result = @_shuffled[1]\update ... + tmp = table.remove @_shuffled, 1 + + if result == running + @_running = tmp + else + @shuffle! + + return result + +-- Randomizes order of nodes in between complete runs of them as a Selector. +class RandomSelector extends Node + new: (@nodes={}) => + super! + + @_running = false + @shuffle! + + shuffle: => + @_shuffled = {} + for i = 1, #@nodes + r = math.random i + unless r == i + @_shuffled[i] = @_shuffled[r] + @_shuffled[r] = @nodes[i] + + update: (...) => + result = fail + + if @_running + result = @_running\update ... + unless result == running + @_running = false + + local tmp + while result == fail and #@_shuffled > 0 + result = @_shuffled[1]\update ... + tmp = table.remove @_shuffled, 1 + + if result == running + @_running = tmp + else + @shuffle! + + return result + +-- Repeats a node a specified number of times, unless it fails. +class Repeat extends Node + new: (@cycles=2, @node=Node!) => + super! + + @counter = 1 + @_running = false + + update: (...) => + result = success + + if @_running + result = @node\update ... + unless result == running + @_running = false + + while result == success and @counter < @cycles + @counter += 1 + result = @node\update ... + + if result == running + @_running = true + else + @counter = 1 + + return result + +-- Returns success whether or not the node succeeds. +class Succeed extends Node + new: (@node=Node!) => + super! + + update: (...) => + if running == @node\update ... + return running + else + return success + +-- Returns fail whether or not the node fails. +class Fail extends Node + new: (@node=Node!) => + super! + + update: (...) => + if running == @node\update ... + return running + else + return fail + +-- Returns success when the node fails, and failure on success. +class Invert extends Node + new: (@node=Node!) => + super! + + update: (...) => + result = @node\update ... + 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 Node + new: (@node=Node!) => + super! + + @ran = false + + update: (...) => + unless @ran + result = @node\update ... + unless result == running + @run = true + return result + else + return fail + +behave = { + -- Leaf Node + :Node + + -- Composite Nodes + :Sequence + :Selector + :Random + :RandomSequence + :RandomSelector + + -- Decorator Nodes + :Repeat + :Succeed + :Fail + :Invert + :RunOnce + + -- Return Values + :success + :running + :fail +} + +behave.clone = (object) -> + local new + cls = getmetatable(object).__class.__name + + if cls == "Repeat" + new = behave[cls] object.cycles, object + else + new = behave[cls] object + + if object.nodes + nodes = {} + for k,v in pairs object.nodes + nodes[k] = behave.clone v + new.nodes = nodes + elseif object.node + new.node = behave.clone object.node + + return new + +return behave