This commit is contained in:
Paul Liverman III 2017-12-07 20:54:54 -08:00
commit c5adc1b351
3 changed files with 388 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.lua

100
ReadMe.md Normal file
View File

@ -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).

287
behave.moon Normal file
View File

@ -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