A behavior tree implementation for clojure and clojurescript. Also includes a state machine built on top of its primitives.
This implementation started with the idea that the game AI behavior trees could find a place in regular application developement.
These trees are processing nodes in a depth first manner, leaving these in one of 4 states:
Each time the core/tick function is called, the tree is traversed, skipping :success and :failure nodes. The :fresh and :running nodes are "ticked", possibly ticking their children. They adjust their status according to their parameters and children status.
This allows the user to describe the modeled processes sequentially although these might be asynchronous.
This implementation communicates with the outside world via incoming and outgoing events and, most importantly, the blackboard.
The behavior tree's database has a blackboard key which contains the application specific data. The tree can act or react on/to this data. The user can also update its value between tick invokations.
This is a little overview, the nodes are described in more details in the node definitions documentation.
These node have no children.
These nodes have a single child
These nodes have several children
Extending the tree with new nodes is expected and probably necessary depending on the use cases. Ping me on slack (Carkh) if the need arises and you can't figure it out.
The tree is described with a hiccup-like notation, then compiled to a more efficient data structure. Many of the parameters defined for different node types can take either a value or a function, that will then be executed receiving the tree context as its parameter. Additional keys may be passed to this parameter map with no adverse effect.
;; we define a traffic light that goes through all colors
(defn traffic-light-1 []
(-> [:repeat
[:sequence
[:update {:func (bt/bb-setter :green)}]
[:timer {:timer :traffic-light :duration 50000}]
[:update {:func (bt/bb-setter :yellow)}]
[:timer {:timer :traffic-light :duration 10000}]
[:update {:func (bt/bb-setter :red)}]
[:timer {:timer :traffic-light :duration 60000}]]]
bt/hiccup->context (bt/tick 0)))
;; Real use would not use the time parameter when calling the tick function.
(defn do-traffic-light-tests [traffic-light]
(is (= :green (-> traffic-light bt/bb-get)))
(is (= :green (-> traffic-light (bt/tick+ 49999) bt/bb-get)))
(is (= :yellow (-> traffic-light (bt/tick+ 50000) bt/bb-get)))
(is (= :red (-> traffic-light (bt/tick+ 50000) (bt/tick+ 10000) bt/bb-get)))
(is (= :green (-> traffic-light (bt/tick+ 50000) (bt/tick+ 10000) (bt/tick+ 60000) bt/bb-get)))
(is (= :yellow (-> traffic-light (bt/tick+ 50000) (bt/tick+ 10000) (bt/tick+ 60000) (bt/tick+ 50000) bt/bb-get)))
;;do the same in a single tick (catching up an exceptionally long GC pause !)
(is (= :red (-> traffic-light (bt/tick+ 60000) bt/bb-get)))
(is (= :green (-> traffic-light (bt/tick+ 120000) bt/bb-get)))
(is (= :yellow (-> traffic-light (bt/tick+ 170000) bt/bb-get))))
There are quite a few tests that should help in understanding how the tree works. In the src/test directory, you'll also find a couple real world'ish examples.
They all can be run, for both clojurescript and clojure, with these commands :
npm install
clojure -A:all-tests
It is also possible to run the clojurescript tests with shadow-cljs in your prefered browser with this command :
npx shadow-cljs watch test
You then can navigate to http://localhost:8022/ and see the test results.
Copyright (c) Sacha De Vos and contributors. All rights reserved.
The use and distribution terms for this software are covered by the Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in the file LICENSE.html at the root of this distribution. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close