Coder Social home page Coder Social logo

jschomay / elixir-behavior-tree Goto Github PK

View Code? Open in Web Editor NEW
55.0 6.0 5.0 44 KB

Elixir library for building AI's from composable behavior trees

Home Page: https://hex.pm/packages/behavior_tree

License: MIT License

Elixir 100.00%
elixir ai behavior-tree

elixir-behavior-tree's Introduction

BehaviorTree

Hex version badge License badge Build status badge Code coverage badge

A library for building AI's from composable behavior trees.

Usage

Add behavior_tree to your list of dependencies in mix.exs:

def deps do
  [{:behavior_tree, "~> 0.3.1"}]
end

Basic usage:

iex> tree = Node.sequence([
...>          Node.select([:a, :b]),
...>          :c
...>        ])
iex> tree |> BehaviorTree.start |> BehaviorTree.succeed |> BehaviorTree.value
:c

Full documentation at https://hexdocs.pm/behavior_tree, and see the example below.

About

A behavior tree is a method for encapsulating complex, nested logic in a declarative data structure. They are often used for video games and AI.

The key mechanics of a behavior tree is that inner nodes describe how to traverse the tree, and leaf nodes are the actual values or "behaviors." A behavior tree always has a value of one of its leaf nodes, which is advanced by signalling that the current behavior should "succeed" or "fail."

Nodes

The primary inner nodes that make up a behavior tree are "select" and "sequence" nodes:

Select nodes will go through their children from left to right. If a child fails, it moves on to the next one. If the last one fails, the select node fails. As soon as any child succeeds, the select node succeeds (and stops traversing its children).

Sequence nodes also go through their children from left to right. If a child fails, the whole select node fails (and stop traversing its children). If a child succeeds, it moves on to the next child. If the last one succeeds, the select node succeeds.

By composing these nodes as needed, you can build up complex behaviors in a simple data structure. There are also be other types of inner nodes (like randomly choosing from its children), and "decorator" nodes, which modify a single child (like repeating it n times). Also, in this implementation, the whole tree will "start over" after exhausting all of its nodes.

Behavior trees vs decision trees and state machines

Behavior trees are similar to decision trees and state machines, but have important differences. Where a decision tree "drills down" from general to specific to reach a leaf, behavior trees are stateful, and move from leaf to leaf over time based on their current context. In that way, behavior trees are more like state machines, but they differ by leveraging the simplicity and power of composable trees to create more complex transition logic.

Example

Let's build an AI to play Battleship.

The rules are simple: "ships" are secretly arranged on a 2D grid, and players guess coordinates, trying to "sink" all of the ships, by getting the clues "hit", "miss", and "sunk" after each guess.

The playing strategy is fairly simple, but we will make a few iterations of our AI.

Note, This example splits up the code into two parts: 1) the tree itself, which only expresses what it wants to do at any given step, and 2) the "handler" code, which interprets the tree's intent, does the appropriate work, and updates the tree with the outcome. An alternative approach would be to load the tree's leafs with functions that could be called directly.

(You can jump directly to the fully implemented AI code).

AI "A" - random guessing

This AI doesn't really have a strategy, and doesn't require a behavior tree, but it is a place to start.

ai_a = Node.sequence([:random_guess])

Every play, calling BehaviorTree.value will return :random_guess. Responding to that "behavior" with either BehaviorTree.fail or BehaviorTree.succeed will not change what we get next time around.

Note that the root of the tree will "start over" if it fails or succeeds, which is what keeps it running even after traversing all of the nodes.

Also note that the behavior tree does not actually know how to make a random guess, or what a valid random guess is, it just declares its intent, allowing the "handler" code to turn that intent into a guess, and then give appropriate feedback.

AI "B" - brute force

We can encode a brute force strategy as a tree:

row_by_row =
Node.repeat_until_fail(
    Node.select([
        :go_right,
        :beginning_of_next_row
    ])
)

ai_b =
Node.sequence([
    :top_left,
    row_by_row
])

"B" is notably more complex, making use of three different inner nodes. Node.repeat_until_fail will repeat its one child node until it fails (in this case, it will only fail after :beginning_of_next_row fails, which would happen after all of the board has been guessed). Each time :go_right succeeds, the select node will succeed, and the repeat_until_fail node will restart it. If go_right goes off the board, the handler code will fail it, and the select node will move on to :beginning_of_next_row, which the handling code will succeed, which will "bubble up" to the select and repeat_until_fail nodes, restarting again at :go_right for the next call.

Note that any time the value of the tree fails, the handler code won't have a valid coordinate, requiring an additional "tick" through the tree in order to get a valid guess.

AI "C" - zero in

AI "C" is the smartest of the bunch, randomly guessing until getting a "hit", and then scanning left, right, up, or down appropriately until getting a "sunk."

search_horizontally =
Node.select([
    :go_right,
    :go_left
])

search_vertically =
Node.select([
    :go_up,
    :go_down
])

narrow_down =
Node.select([
    search_horizontally,
    search_vertically
])

ai_c =
Node.sequence([
    :random_guess,
    narrow_down
])

"C" is quite complex, and requires specific feedback from the handler code. When randomly guessing, a "miss" should get a BehaviorTree.fail, a "hit" should get a BehaviorTree.succeed, and a "sunk" should not update the tree at all, so that it will still be making random guesses next time (note that BehaviorTree.fail would work the same in this case, but is less clear).

When narrowing down, a "hit" should leave the tree as it is for next time, a "miss" should get a BehaviorTree.fail, and a "sunk" should get a BehaviorTree.success. In the case that a guess is invalid (goes off the board), it should respond with a BehaviorTree.fail and run it again.

elixir-behavior-tree's People

Contributors

aus70 avatar axelson avatar jschomay avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

elixir-behavior-tree's Issues

The repeat_n behavior node does not work properly when nested.

When nesting one repeat_n behavior Node inside another the nested one only runs the first pass. After that the nested Node.repeat_n seems to be ignored.

To Reproduce:
Run the tree in the included zip file

Expected behavior:
Based on the attached tree there should be 5 "Create new membership" log entires for each "Get new pool" log entry.

Nested_Repeat.zip

Implementation of a parallel node

Hi,

I was wondering if with the current API it is possible to implement a parallel node.
I guess with a parallel you will end up with multiple active leaf nodes, end thus to signal fail/success the tree would need to accept some kind of identifier to mark the specific node that finished underneath the parallel.

  Node.parallel([:a, :b, :c])
  |> BehaviorTree.start()
  |> BehaviorTree.succeed(:b)

Currently the tree only accepts BehaviorTree.succeed/1 or BehaviorTree.fail/1.

Is there another way to achieve the parallel node with the current API ?

BehaviorTree.value returns unexpectedly a Node

To reproduce the issue:

tree = Node.sequence([
             Node.repeat_until_succeed(:wait),
             Node.repeat_until_succeed([
               Node.sequence([
                 :pose_question,
                 Node.repeat_until_fail(:evaluate_answer)
               ])
             ])
           ])

tree |> BehaviorTree.start |> BehaviorTree.fail |> BehaviorTree.value

returns:

[
  %BehaviorTree.Node{
    type: :sequence,
    children: [
      :pose_question,
      %BehaviorTree.Node{
        type: :repeat_until_fail,
        children: [:evaluate_answer],
        repeat_count: 1,
        repeat_total: 1,
        weights: []
      }
    ],
    repeat_count: 1,
    repeat_total: 1,
    weights: []
  }
]

but I was expecting :pose_question instead.

Is the tree malformed or am I missing anything?

Thanks!

Debug features: draw tree

It could be nice to get a visual representation of the tree at any given moment, including the current node.

Consider using graphviz format as a possible output.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.