Writing SequenceNodes

Overview

This tutorial demonstrates a couple of simple careBT SequenceNodes. For the demos the ActionNode (AddTwoNumbersAction) implemented in the previous tutorial is reused and two additional ActionNodes are implemented. The first one (CreateRandomNumberAction) generates a random numer and provides it as output parameter. The second one (PrintNumberAction) prints the, as input parameter, provided number on standard output.

Create the ActionNodes and the SimpleSequence1

This first example called SimpleSequence1 contains four child nodes.

digraph foo {
     s1 [shape=box, margin="0.05,0.1", label="«SequenceNode»\nSimpleSequence1"];

     s1c1 [shape=box, margin="0.05,0.1", label="«ActionNode»\nCreateRandomNumberAction"];
     s1c2 [shape=box, margin="0.05,0.1", label="«ActionNode»\nCreateRandomNumberAction"];
     s1c3 [shape=box, margin="0.05,0.1", label="«ActionNode»\nAddTwoNumbersAction"];
     s1c4 [shape=box, margin="0.05,0.1", label="«ActionNode»\nPrintNumberAction"];

     s1 -> s1c1
     s1 -> s1c2
     s1 -> s1c3
     s1 -> s1c4
}

The first two nodes generate two random numbers, the third one adds them and the last one prints the result.

Create a file named simple_sequence.py with following content. Or use the provided file: simple_sequence.py

 1import random
 2
 3from carebt import ActionNode
 4from carebt import NodeStatus
 5from carebt import SequenceNode
 6from carebt.examples.action_with_params import AddTwoNumbersAction
 7
 8
 9########################################################################
10
11
12class CreateRandomNumberAction(ActionNode):
13    """The `CreateRandomNumberAction` example node.
14
15    The `CreateRandomNumberAction` creates a random number between 1 and 10
16    and binds it to the output parameter.
17
18    Output Parameters
19    -----------------
20    ?number : int
21        The randomly generated number
22
23    """
24
25    def __init__(self, bt_runner):
26        super().__init__(bt_runner, '=> ?number')
27
28    def on_tick(self) -> None:
29        self._number = random.randint(1, 10)
30        print(f'CreateRandomNumberAction: number = {self._number}')
31        self.set_status(NodeStatus.SUCCESS)
32
33########################################################################
34
35
36class PrintNumberAction(ActionNode):
37    """The `PrintNumberAction` example node.
38
39    The `PrintNumberAction` prints the, as input parameter provided, number on
40    standard output.
41
42    Input Parameters
43    ----------------
44    ?number : int
45        The number to print
46
47    """
48
49    def __init__(self, bt_runner):
50        super().__init__(bt_runner, '?number')
51
52    def on_tick(self) -> None:
53        print(f'PrintNumberAction: number = {self._number}')
54        self.set_status(NodeStatus.SUCCESS)
55
56########################################################################
57
58
59class SimpleSequence1(SequenceNode):
60    """The `SimpleSequence1` example node.
61
62    The `SimpleSequence` runs the nodes `CreateRandomNumberAction`,
63    `CreateRandomNumberAction`, `AddTwoNumbersAction` and `PrintNumberAction`
64    in a sequence. The first two nodes create random numbers, the third one
65    adds them together and the last one prints the result. Furthermore, the
66    result is returned by the ouput parameter *?c*.
67
68    Output Parameters
69    -----------------
70    ?c : int
71        The result
72
73    """
74
75    def __init__(self, bt_runner):
76        super().__init__(bt_runner, '=> ?c')
77
78    def on_init(self) -> None:
79        self.append_child(CreateRandomNumberAction, '=> ?a')
80        self.append_child(CreateRandomNumberAction, '=> ?b')
81        self.append_child(AddTwoNumbersAction, '?a ?b => ?c')
82        self.append_child(PrintNumberAction, '?c')

The code explained

The first statements are the includes for the Python random library, the careBT classes (ActionNode, NodeStatus, SequenceNode) and the AddTwoNumbersAction.

import random

from carebt import ActionNode
from carebt import NodeStatus
from carebt import SequenceNode
from carebt.examples.action_with_params import AddTwoNumbersAction

The CreateRandomNumberAction is a custom ActionNode which generates a random number which is bound to the output parameter ?number.

class CreateRandomNumberAction(ActionNode):
    """The `CreateRandomNumberAction` example node.

    The `CreateRandomNumberAction` creates a random number between 1 and 10
    and binds it to the output parameter.

    Output Parameters
    -----------------
    ?number : int
        The randomly generated number

    """

    def __init__(self, bt_runner):
        super().__init__(bt_runner, '=> ?number')

    def on_tick(self) -> None:
        self._number = random.randint(1, 10)
        print(f'CreateRandomNumberAction: number = {self._number}')
        self.set_status(NodeStatus.SUCCESS)

The PrintNumberAction is a custom ActionNode which prints the provided ?number on standard output.

class PrintNumberAction(ActionNode):
    """The `PrintNumberAction` example node.

    The `PrintNumberAction` prints the, as input parameter provided, number on
    standard output.

    Input Parameters
    ----------------
    ?number : int
        The number to print

    """

    def __init__(self, bt_runner):
        super().__init__(bt_runner, '?number')

    def on_tick(self) -> None:
        print(f'PrintNumberAction: number = {self._number}')
        self.set_status(NodeStatus.SUCCESS)

The SimpleSequence1 node is implemented as a Python class which inherits from SequenceNode.

class SimpleSequence1(SequenceNode):

The class definition is followed by the Docstring documentation of the node, which also documents the interface (output parameters).

    """The `SimpleSequence1` example node.

    The `SimpleSequence` runs the nodes `CreateRandomNumberAction`,
    `CreateRandomNumberAction`, `AddTwoNumbersAction` and `PrintNumberAction`
    in a sequence. The first two nodes create random numbers, the third one
    adds them together and the last one prints the result. Furthermore, the
    result is returned by the ouput parameter *?c*.

    Output Parameters
    -----------------
    ?c : int
        The result

    """

The constructor (__init__) of the SimpleSequence1 needs to call the constructor (super().__init__) of the SequenceNode and passes the bt_runner and the signature as arguments. The signature defines one output parameter called ?c.

    def __init__(self, bt_runner):
        super().__init__(bt_runner, '=> ?c')

The on_init function is called rigth after the node was created. It is the place to put the code which should be executed once, after the node was created. For a SequenceNode the child nodes are typically added here.

    def on_init(self) -> None:
        self.append_child(CreateRandomNumberAction, '=> ?a')
        self.append_child(CreateRandomNumberAction, '=> ?b')
        self.append_child(AddTwoNumbersAction, '?a ?b => ?c')
        self.append_child(PrintNumberAction, '?c')

Run the example

Start the Python interpreter and run the SimpleSequence1 node:

>>> import carebt
>>> from carebt.examples.simple_sequence import SimpleSequence1
>>> bt_runner = carebt.BehaviorTreeRunner()
>>> bt_runner.run(SimpleSequence1, '=> ?x')
CreateRandomNumberAction: number = 6
CreateRandomNumberAction: number = 5
AddTwoNumbersAction: calculating: 6 + 5 = 11
PrintNumberAction: number = 11

>>> bt_runner.run(SimpleSequence1, '=> ?x')
CreateRandomNumberAction: number = 7
CreateRandomNumberAction: number = 9
AddTwoNumbersAction: calculating: 7 + 9 = 16
PrintNumberAction: number = 16

Create the SimpleSequence2

digraph foo {
     s2 [shape=box, margin="0.05,0.1", label="«SequenceNode»\nSimpleSequence2"];

     s2c1 [shape=box, margin="0.05,0.1", label="«ActionNode»\nCreateRandomNumberAction"];
     s2c2 [shape=box, margin="0.05,0.1", label="«ActionNode»\nAddTwoNumbersAction"];
     s2c3 [shape=box, margin="0.05,0.1", label="«ActionNode»\nPrintNumberAction"];
     s2c4 [shape=box, margin="0.05,0.1", label="«ActionNode»\nCreateRandomNumberAction"];
     s2c5 [shape=box, margin="0.05,0.1", label="«ActionNode»\nAddTwoNumbersAction"];
     s2c6 [shape=box, margin="0.05,0.1", label="«ActionNode»\nPrintNumberAction"];

     s2 -> s2c1
     s2 -> s2c2
     s2 -> s2c3
     s2 -> s2c4
     s2 -> s2c5
     s2 -> s2c6
}

The SimpleSequence2 is a modified version of the SimpleSequence1. It shows how simple a different sequence of children can be created and how the parameters can be bound across the different children.

Add the following content to simple_sequence.py. Or use the provided file: simple_sequence.py

 1class SimpleSequence2(SequenceNode):
 2    """The `SimpleSequence2` example node.
 3
 4    The `SimpleSequence2` demonstrates a modified version of the
 5    `SimpleSequence1`. The value provided as an input parameter is
 6    added to a randomly generated value. The result is then added
 7    to another randomly generated value. This final result is then
 8    provided as an output of the `SequenceNode`.
 9
10    Input Parameters
11    ----------------
12    ?a : int
13        A number for the calculations
14
15    Output Parameters
16    -----------------
17    ?e : int
18        The result
19
20    """
21
22    def __init__(self, bt_runner):
23        super().__init__(bt_runner, '?a => ?e')
24
25    def on_init(self) -> None:
26        self.append_child(CreateRandomNumberAction, '=> ?b')
27        self.append_child(AddTwoNumbersAction, '?a ?b => ?c')
28        self.append_child(PrintNumberAction, '?c')
29        self.append_child(CreateRandomNumberAction, '=> ?d')
30        self.append_child(AddTwoNumbersAction, '?c ?d => ?e')
31        self.append_child(PrintNumberAction, '?e')

Run the example

Start the Python interpreter and run the SimpleSequence2 node:

>>> import carebt
>>> from carebt.examples.simple_sequence import SimpleSequence2
>>> bt_runner = carebt.BehaviorTreeRunner()
>>> bt_runner.run(SimpleSequence2, '5 => ?x')
CreateRandomNumberAction: number = 2
AddTwoNumbersAction: calculating: 5 + 2 = 7
PrintNumberAction: number = 7
CreateRandomNumberAction: number = 2
AddTwoNumbersAction: calculating: 7 + 2 = 9
PrintNumberAction: number = 9

>>> bt_runner.run(SimpleSequence2, '7 => ?x')
CreateRandomNumberAction: number = 1
AddTwoNumbersAction: calculating: 7 + 1 = 8
PrintNumberAction: number = 8
CreateRandomNumberAction: number = 2
AddTwoNumbersAction: calculating: 8 + 2 = 10
PrintNumberAction: number = 10

Create the SimpleSequence3

digraph foo {

     // make invisible ranks
     rank1 [style=invisible];
     rank2 [style=invisible];
     rank3 [style=invisible];
     rank4 [style=invisible];

     // make "invisible" (white) link between them
     rank1 -> rank2 [color=white];
     rank2 -> rank3 [color=white];
     rank3 -> rank4 [color=white];

     s1 [shape=box, margin="0.05,0.1", label="«SequenceNode»\nSimpleSequence1"];

     s1c1 [shape=box, margin="0.05,0.1", label="«ActionNode»\nCreateRandomNumberAction"];
     s1c2 [shape=box, margin="0.05,0.1", label="«ActionNode»\nCreateRandomNumberAction"];
     s1c3 [shape=box, margin="0.05,0.1", label="«ActionNode»\nAddTwoNumbersAction"];
     s1c4 [shape=box, margin="0.05,0.1", label="«ActionNode»\nPrintNumberAction"];

     s1 -> s1c1
     s1 -> s1c2
     s1 -> s1c3
     s1 -> s1c4

     s2 [shape=box, margin="0.05,0.1", label="«SequenceNode»\nSimpleSequence2"];

     s2c1 [shape=box, margin="0.05,0.1", label="«ActionNode»\nCreateRandomNumberAction"];
     s2c2 [shape=box, margin="0.05,0.1", label="«ActionNode»\nAddTwoNumbersAction"];
     s2c3 [shape=box, margin="0.05,0.1", label="«ActionNode»\nPrintNumberAction"];
     s2c4 [shape=box, margin="0.05,0.1", label="«ActionNode»\nCreateRandomNumberAction"];
     s2c5 [shape=box, margin="0.05,0.1", label="«ActionNode»\nAddTwoNumbersAction"];
     s2c6 [shape=box, margin="0.05,0.1", label="«ActionNode»\nPrintNumberAction"];

     s2 -> s2c1
     s2 -> s2c2
     s2 -> s2c3
     s2 -> s2c4
     s2 -> s2c5
     s2 -> s2c6

     s3 [shape=box, margin="0.05,0.1", label="«SequenceNode»\nSimpleSequence3"];

     s3c1 [shape=box, margin="0.05,0.1", label="«ActionNode»\nPrintNumberAction"];
     s3c2 [shape=box, margin="0.05,0.1", label="«ActionNode»\nPrintNumberAction"];

     s3 -> s1
     s3 -> s3c1
     s3 -> s2
     s3 -> s3c2

     {
         rank = same;
         // Here you enforce the desired order with "invisible" edges and arrowheads
         rank2 -> s1 -> s3c1 [ style=invis ];
         rankdir = LR;
     }
     {
         rank = same;
         // Here you enforce the desired order with "invisible" edges and arrowheads
         rank4 -> s2 -> s3c2 [ style=invis ];
         rankdir = LR;
     }
}

The SimpleSequence3 shows another example how custom ActionNodes and custom SequenceNodes can be reused and how the parameters can be bound.

Add the following content to simple_sequence.py. Or use the provided file: simple_sequence.py

 1class SimpleSequence3(SequenceNode):
 2    """The `SimpleSequence3` example node.
 3
 4    The `SimpleSequence3` shows another example sequence where the two sequences
 5    implemented above are reused.
 6
 7    """
 8
 9    def __init__(self, bt_runner):
10        super().__init__(bt_runner)
11
12    def on_init(self) -> None:
13        self.append_child(SimpleSequence1, '=> ?x')
14        self.append_child(PrintNumberAction, '?x')
15        self.append_child(SimpleSequence2, '?x => ?y')
16        self.append_child(PrintNumberAction, '?y')

Run the example

Start the Python interpreter and run the SimpleSequence3 node:

>>> import carebt
>>> from carebt.examples.simple_sequence import SimpleSequence3
>>> bt_runner = carebt.BehaviorTreeRunner()
>>> bt_runner.run(SimpleSequence3)
CreateRandomNumberAction: number = 5
CreateRandomNumberAction: number = 8
AddTwoNumbersAction: calculating: 5 + 8 = 13
PrintNumberAction: number = 13
PrintNumberAction: number = 13
CreateRandomNumberAction: number = 2
AddTwoNumbersAction: calculating: 13 + 2 = 15
PrintNumberAction: number = 15
CreateRandomNumberAction: number = 1
AddTwoNumbersAction: calculating: 15 + 1 = 16
PrintNumberAction: number = 16
PrintNumberAction: number = 16

>>> bt_runner.run(SimpleSequence3)
CreateRandomNumberAction: number = 1
CreateRandomNumberAction: number = 2
AddTwoNumbersAction: calculating: 1 + 2 = 3
PrintNumberAction: number = 3
PrintNumberAction: number = 3
CreateRandomNumberAction: number = 6
AddTwoNumbersAction: calculating: 3 + 6 = 9
PrintNumberAction: number = 9
CreateRandomNumberAction: number = 3
AddTwoNumbersAction: calculating: 9 + 3 = 12
PrintNumberAction: number = 12
PrintNumberAction: number = 12

Note

The SimpleSequence3 example shows how easily a behavior can be composed out of already existing sub-behaviors, while the individual sub-behaviors can still be executed and tested separately.