diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..420caba --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\dist\\index.js" + }, { + "type": "node", + "request": "launch", + "name": "Mocha Tests", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "-r", + "ts-node/register", + "--timeout", + "999999", + "--colors", + "${workspaceFolder}/test/**/*spec.ts", + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/README.md b/README.md index 562b233..33733b7 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![npm version](https://badge.fury.io/js/mistreevous.svg)](https://badge.fury.io/js/mistreevous) [![Node.js CI](https://github.com/nikkorn/mistreevous/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/nikkorn/mistreevous/actions/workflows/node.js.yml) -A tool to declaratively define and generate behaviour trees, built using Typescript. Behaviour trees are used to create complex AI via the modular heirarchical composition of individual tasks. +A library to declaratively define, build and execute behaviour trees, written in Typescript. Behaviour trees are used to create complex AI via the modular heirarchical composition of individual tasks. -Using this tool, trees can be defined with a simple and minimal built-in DSL, avoiding the need to write verbose definitions in JSON. +Using this tool, trees can be defined with either JSON or a simple and minimal built-in DSL (MDSL), avoiding the need to write verbose definitions in JSON. ![Sorting Lunch](resources/images/sorting-lunch-example.png?raw=true "Sorting Lunch") @@ -84,19 +84,20 @@ The `BehaviourTree` constructor can take an options object as an argument, the p ## States Behaviour tree nodes can be in one of the following states: -- **READY** A node is in a ready state when it has not been visited yet in the execution of the tree. -- **RUNNING** A node is in a running state when it is is still being processed, these nodes will usually represent or encompass a long running action. -- **SUCCEEDED** A node is in a succeeded state when it is no longer being processed and has succeeded. -- **FAILED** A node is in a failed state when it is no longer being processed but has failed. +- **READY** A node is in the `READY` state when it has not been visited yet in the execution of the tree. +- **RUNNING** A node is in the `RUNNING` state when it is is still being processed, these nodes will usually represent or encompass a long running action. +- **SUCCEEDED** A node is in a `SUCCEEDED` state when it is no longer being processed and has succeeded. +- **FAILED** A node is in the `FAILED` state when it is no longer being processed but has failed. ## Composite Nodes -Composite nodes wrap one or more child nodes, each of which will be processed in a sequence determined by the type of the composite node. A composite node will remain in the running state until it is finished processing the child nodes, after which the state of the composite node will reflect the success or failure of the child nodes. +Composite nodes wrap one or more child nodes, each of which will be processed in a sequence determined by the type of the composite node. A composite node will remain in the `RUNNING` state until it is finished processing the child nodes, after which the state of the composite node will reflect the success or failure of the child nodes. ### Sequence -This composite node will update each child node in sequence. It will succeed if all of its children have succeeded and will fail if any of its children fail. This node will remain in the running state if one of its children is running. +This composite node will update each child node in sequence. It will move to the `SUCCEEDED` state if all of its children have moved to the `SUCCEEDED` state and and will move to the `FAILED` state if any of its children move to the `FAILED` state. This node will remain in the `RUNNING` state if one of its children remains in the `RUNNING` state. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=sequence) +*MDSL* ``` root { sequence { @@ -107,10 +108,35 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "sequence", + "children": [ + { + "type": "action", + "call": "Walk" + }, + { + "type": "action", + "call": "Fall" + }, + { + "type": "action", + "call": "Laugh" + } + ] + } +} +``` + ### Selector -This composite node will update each child node in sequence. It will fail if all of its children have failed and will succeed if any of its children succeed. This node will remain in the running state if one of its children is running. +This composite node will update each child node in sequence. It will move to the `FAILED` state if all of its children have moved to the `FAILED` state and will move to the `SUCCEEDED` state if any of its children move to the `SUCCEEDED` state. This node will remain in the `RUNNING` state if one of its children is in the `RUNNING` state. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=selector) +*MDSL* ``` root { selector { @@ -121,10 +147,35 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "selector", + "children": [ + { + "type": "action", + "call": "TryThis" + }, + { + "type": "action", + "call": "ThenTryThis" + }, + { + "type": "action", + "call": "TryThisLast" + } + ] + } +} +``` + ### Parallel -This composite node will update each child node concurrently. It will succeed if all of its children have succeeded and will fail if any of its children fail. This node will remain in the running state if any of its children are running. +This composite node will update each child node concurrently. It will move to the `SUCCEEDED` state if all of its children have moved to the `SUCCEEDED` state and will move to the `FAILED` state if any of its children move to the `FAILED` state. This node will remain in the `RUNNING` state if any of its children are in the `RUNNING` state. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=parallel) +*MDSL* ``` root { parallel { @@ -134,10 +185,31 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "parallel", + "children": [ + { + "type": "action", + "call": "RubBelly" + }, + { + "type": "action", + "call": "PatHead" + } + ] + } +} +``` + ### Lotto This composite node will select a single child at random to run as the active running node. The state of this node will reflect the state of the active child. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=lotto) +*MDSL* ``` root { lotto { @@ -147,9 +219,30 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "lotto", + "children": [ + { + "type": "action", + "call": "MoveLeft" + }, + { + "type": "action", + "call": "MoveRight" + } + ] + } +} +``` + A probability weight can be defined for each child node as an optional integer node argument, influencing the likelihood that a particular child will be picked. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=weighted-lotto) +*MDSL* ``` root { lotto [10,5,3,1] { @@ -161,6 +254,35 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "lotto", + "children": [ + { + "type": "action", + "call": "CommonAction" + }, + { + "type": "action", + "call": "UncommonAction" + }, + { + "type": "action", + "call": "RareAction" + }, + { + "type": "action", + "call": "VeryRareAction" + } + ], + "weights": [10, 5, 3, 1] + } +} +``` + ## Decorator Nodes A decorator node is similar to a composite node, but it can only have a single child node. The state of a decorator node is usually some transformation of the state of the child node. Decorator nodes are also used to repeat or terminate execution of a particular node. @@ -169,14 +291,27 @@ This decorator node represents the root of a behaviour tree and cannot be the ch The state of a root node will reflect the state of its child node. +*MDSL* ``` root { action [Dance] } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "action", + "call": "Dance" + } +} +``` + Additional named root nodes can be defined and reused throughout a definition. Other root nodes can be referenced via the **branch** node. Exactly one root node must be left unnamed, this root node will be used as the main root node for the entire tree. +*MDSL* ``` root { branch [SomeOtherTree] @@ -187,12 +322,35 @@ root [SomeOtherTree] { } ``` +*JSON* +```json +[ + { + "type": "root", + "child": { + "type": "branch", + "ref": "SomeOtherTree" + } + }, + { + "type": "root", + "id": "SomeOtherTree", + "child": { + "type": "action", + "call": "Dance", + "args": [] + } + } +] +``` + ### Repeat -This decorator node will repeat the execution of its child node if the child moves to the succeeded state. It will do this until either the child fails, at which point the repeat node will fail, or the maximum number of iterations is reached, which moves the repeat node to a succeeded state. This node will be in a running state if its child is also in a running state, or if further iterations need to be made. +This decorator node will repeat the execution of its child node if the child moves to the `SUCCEEDED` state. It will do this until either the child moves to the `FAILED` state, at which point the repeat node will move to the `FAILED` state, or the maximum number of iterations is reached, which moves the repeat node to the `SUCCEEDED` state. This node will be in the `RUNNING` state if its child is also in the `RUNNING` state, or if further iterations need to be made. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=repeat) -The maximum number of iterations can be defined as a single integer node argument. In the example below, we would be repeating the action **SomeAction** 5 times. +The maximum number of iterations can be defined as a single integer iteration argument. In the example below, we would be repeating the action **SomeAction** 5 times. +*MDSL* ``` root { repeat [5] { @@ -200,8 +358,25 @@ root { } } ``` -The number of iterations to make can be selected at random within a lower and upper bound if these are defined as two integer node arguments. In the example below, we would be repeating the action **SomeAction** between 1 and 5 times. +*JSON* +```json +{ + "type": "root", + "child": { + "type": "repeat", + "iterations": 5, + "child": { + "type": "action", + "call": "SomeAction" + } + } +} +``` + +The number of iterations to make can be selected at random within a lower and upper bound if these are defined as two integer arguments. In the example below, we would be repeating the action **SomeAction** between 1 and 5 times. + +*MDSL* ``` root { repeat [1,5] { @@ -209,8 +384,25 @@ root { } } ``` -The maximum number of iterations to make can be omitted as a node argument. This would result in the child node being run infinitely, as can be seen in the example below. +*JSON* +```json +{ + "type": "root", + "child": { + "type": "repeat", + "iterations": [1, 5], + "child": { + "type": "action", + "call": "SomeAction" + } + } +} +``` + +The maximum number of iterations to make can be omitted. This would result in the child node being run infinitely, as can be seen in the example below. + +*MDSL* ``` root { repeat { @@ -219,11 +411,26 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "repeat", + "child": { + "type": "action", + "call": "SomeAction" + } + } +} +``` + ### Retry -This decorator node will repeat the execution of its child node if the child moves to the failed state. It will do this until either the child succeeds, at which point the retry node will succeed, or the maximum number of attempts is reached, which moves the retry node to a failed state. This node will be in a running state if its child is also in a running state, or if further attempts need to be made. +This decorator node will repeat the execution of its child node if the child moves to the `FAILED` state. It will do this until either the child moves to the `SUCCEEDED` state, at which point the retry node will move to the `SUCCEEDED` state, or the maximum number of attempts is reached, which moves the retry node to the `FAILED` state. This node will be in a `RUNNING` state if its child is also in the `RUNNING` state, or if further attempts need to be made. -The maximum number of attempts can be defined as a single integer node argument. In the example below, we would be retrying the action **SomeAction** 5 times. +The maximum number of attempts can be defined as a single integer attempt argument. In the example below, we would be retrying the action **SomeAction** 5 times. +*MDSL* ``` root { retry [5] { @@ -231,8 +438,25 @@ root { } } ``` -The number of attempts to make can be selected at random within a lower and upper bound if these are defined as two integer node arguments. In the example below, we would be retrying the action **SomeAction** between 1 and 5 times. +*JSON* +```json +{ + "type": "root", + "child": { + "type": "retry", + "attempts": 5, + "child": { + "type": "action", + "call": "SomeAction" + } + } +} +``` + +The number of attempts to make can be selected at random within a lower and upper bound if these are defined as two integer arguments. In the example below, we would be retrying the action **SomeAction** between 1 and 5 times. + +*MDSL* ``` root { retry [1,5] { @@ -240,8 +464,25 @@ root { } } ``` -The maximum number of attempts to make can be omitted as a node argument. This would result in the child node being run infinitely until it moves to the succeeded state, as can be seen in the example below. +*JSON* +```json +{ + "type": "root", + "child": { + "type": "retry", + "attempts": [1, 5], + "child": { + "type": "action", + "call": "SomeAction" + } + } +} +``` + +The maximum number of attempts to make can be omitted. This would result in the child node being run infinitely until it moves to the `SUCCEEDED` state, as can be seen in the example below. + +*MDSL* ``` root { retry { @@ -250,10 +491,25 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "retry", + "child": { + "type": "action", + "call": "SomeAction" + } + } +} +``` + ### Flip -This decorator node will move to the succeed state when its child moves to the failed state, and it will fail if its child moves to the succeeded state. This node will remain in the running state if its child is in the running state. +This decorator node will move to the `SUCCEEDED` state when its child moves to the `FAILED` state, and it will move to the `FAILED` if its child moves to the `SUCCEEDED` state. This node will remain in the `RUNNING` state if its child is in the `RUNNING` state. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=flip) +*MDSL* ``` root { flip { @@ -262,10 +518,25 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "flip", + "child": { + "type": "action", + "call": "SomeAction" + } + } +} +``` + ### Succeed -This decorator node will move to the succeed state when its child moves to the either the failed state or the succeeded state. This node will remain in the running state if its child is in the running state. +This decorator node will move to the `SUCCEEDED` state when its child moves to either the `FAILED` state or the `SUCCEEDED` state. This node will remain in the `RUNNING` state if its child is in the `RUNNING` state. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=succeed) +*MDSL* ``` root { succeed { @@ -274,10 +545,25 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "succeed", + "child": { + "type": "action", + "call": "SomeAction" + } + } +} +``` + ### Fail -This decorator node will move to the failed state when its child moves to the either the failed state or the succeeded state. This node will remain in the running state if its child is in the running state. +This decorator node will move to the `FAILED` state when its child moves to either the `FAILED` state or the `SUCCEEDED` state. This node will remain in the `RUNNING` state if its child is in the `RUNNING` state. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=fail) +*MDSL* ``` root { fail { @@ -286,22 +572,48 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "fail", + "child": { + "type": "action", + "call": "SomeAction" + } + } +} +``` + ## Leaf Nodes Leaf nodes are the lowest level node type and cannot be the parent of other child nodes. ### Action -An action node represents an action that can be completed immediately as part of a single tree step, or ongoing behaviour that can take a prolonged amount of time and may take multiple tree steps to complete. Each action node will correspond to some action that can be carried out by the agent, where the first action node argument will be an identifier matching the name of the corresponding agent action function. +An action node represents an action that can be completed immediately as part of a single tree step, or ongoing behaviour that can take a prolonged amount of time and may take multiple tree steps to complete. Each action node will correspond to some action that can be carried out by the agent, where the first action node argument will be an identifier matching the name of the corresponding agent action function. It may also reference a globally registered function. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=action) -An agent action function can optionally return a finished action state of **succeeded** or **failed**. If the **succeeded** or **failed** state is returned, then the action will move into that state. +An agent action function can optionally return a value of **State.SUCCEEDED**, **State.FAILED** or **State.RUNNING**. If the **State.SUCCEEDED** or **State.FAILED** state is returned, then the action will move to that state. +*MDSL* ``` root { action [Attack] } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "action", + "call": "Attack" + } +} +``` + ```js const agent = { //... @@ -321,7 +633,7 @@ const agent = { }; ``` -If no value or undefined is returned from the action function the action node will move into the **running** state and no following nodes will be processed as part of the current tree step. In the example below, any action node that references **WalkToPosition** will remain in the **running** state until the target position is reached. +If no value or a value of **State.RUNNING** is returned from the action function the action node will move into the `RUNNING` state and no following nodes will be processed as part of the current tree step. In the example below, any action node that references **WalkToPosition** will remain in the `RUNNING` state until the target position is reached. ```js const agent = { @@ -334,15 +646,18 @@ const agent = { // We have finally reached the target position! return Mistreevous.State.SUCCEEDED; } + + // We have not reached the target position yet. + return Mistreevous.State.RUNNING; } // ... }; ``` -Further steps of the tree will resume processing from leaf nodes that were left in the **running** state until those nodes succeed, fail, or processing of the running branch is aborted via a guard. +Further steps of the tree will resume processing from leaf nodes that were left in the `RUNNING` state until those nodes move to either the `SUCCEEDED` or `FAILED` state or processing of the running branch is aborted via a guard. #### Promise-based Actions -As well as returning a finished action state from an action function, you can also return a promise that should eventually resolve with a finished state as its value. The action will remain in the running state until the promise is fulfilled, and any following tree steps will not call the action function again. +As well as returning a finished action state from an action function, you can also return a promise that should eventually resolve with a finished state of **State.SUCCEEDED** or **State.FAILED** as its value. The action will remain in the `RUNNING` state until the promise is fulfilled, and any following tree steps will not call the action function again. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=async-action) ```js @@ -360,15 +675,28 @@ const agent = { ``` #### Optional Arguments -Arguments can optionally be passed to agent action functions. This is done by including them in the action node argument list in the definition. These optional arguments must be defined after the action name identifier argument, and can be a `number`, `string`, `boolean` or `null`. +Arguments can optionally be passed to agent action functions. In MDSL these optional arguments must be defined after the action name identifier argument, and can be a `number`, `string`, `boolean` or `null`. If using JSON then these arguments are defined in an `args` array and these arguments can be any valid JSON. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=action-with-args) +*MDSL* ``` root { action [Say, "hello world", 5, true] } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "action", + "call": "Say", + "args": ["hello world", 5, true] + } +} +``` + ```js const agent = { //... @@ -385,9 +713,10 @@ const agent = { ``` ### Condition -A Condition node will immediately move into either a **succeeded** or **failed** state based on the boolean result of calling a function on the agent. Each condition node will correspond to functionality defined on the agent, where the first condition node argument will be an identifier matching the name of the corresponding agent condition function. +A Condition node will immediately move into either a `SUCCEEDED` or `FAILED` state based on the boolean result of calling either a function on the agent or a globally registered function. Each condition node will correspond to functionality defined on the agent, where the first condition node argument will be an identifier matching the name of the corresponding agent condition function. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=condition) +*MDSL* ``` root { sequence { @@ -396,6 +725,27 @@ root { } } ``` + +*JSON* +```json +{ + "type": "root", + "child": { + "type": "sequence", + "children": [ + { + "type": "condition", + "call": "HasWeapon" + }, + { + "type": "action", + "call": "Attack" + } + ] + } +} +``` + ```js const agent = { //... @@ -407,9 +757,10 @@ const agent = { ``` #### Optional Arguments -Arguments can optionally be passed to agent condition functions in the same was as action nodes. This is done by including them in the condition node argument list in the definition. These optional arguments must be defined after the condition name identifier argument, and can be a `number`, `string`, `boolean` or `null`. +Arguments can optionally be passed to agent condition functions in the same was as action nodes. This is done by including them in the condition node argument list in the definition. These optional arguments must be defined after the condition name identifier argument, and can be a `number`, `string`, `boolean` or `null` if using MDSL, or any valid JSON when using a JSON definition. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=condition-with-args) +*MDSL* ``` root { sequence { @@ -417,6 +768,24 @@ root { } } ``` + +*JSON* +```json +{ + "type": "root", + "child": { + "type": "sequence", + "children": [ + { + "type": "condition", + "call": "HasItem", + "args": ["potion"] + } + ] + } +} +``` + ```js const agent = { //... @@ -429,6 +798,7 @@ const agent = { A wait node will remain in a running state for a specified duration, after which it will move into the succeeded state. The duration in milliseconds can be defined as a single integer node argument. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=wait) +*MDSL* ``` root { repeat { @@ -439,11 +809,36 @@ root { } } ``` + +*JSON* +```json +{ + "type": "root", + "child": { + "type": "repeat", + "child": { + "type": "sequence", + "children": [ + { + "type": "action", + "call": "FireWeapon" + }, + { + "type": "wait", + "duration": 2000 + } + ] + } + } +} +``` + In the above example, we are using a wait node to wait 2 seconds between each run of the **FireWeapon** action. The duration to wait in milliseconds can also be selected at random within a lower and upper bound if these are defined as two integer node arguments. In the example below, we would run the **PickUpProjectile** action and then wait for 2 to 8 seconds before running the **ThrowProjectile** action. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=wait) +*MDSL* ``` root { sequence { @@ -454,18 +849,55 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "sequence", + "children": [ + { + "type": "action", + "call": "PickUpProjectile" + }, + { + "type": "wait", + "duration": [2000, 8000] + }, + { + "type": "action", + "call": "ThrowProjectile" + } + ] + } +} +``` + If no node arguments are defined then the wait node will remain in the running state indefinitely until it is aborted. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=wait) +*MDSL* ``` root { wait } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "wait" + } +} +``` + ### Branch -Named root nodes can be referenced using the **branch** node. This node acts as a placeholder that will be replaced by the child node of the referenced root node. The two definitions below are synonymous. +Named root nodes can be referenced using the **branch** node. This node acts as a placeholder that will be replaced by the child node of the referenced root node. All definitions below are synonymous. +[Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=branch) +*MDSL* ``` root { branch [SomeOtherTree] @@ -482,6 +914,38 @@ root { } ``` +*JSON* +```json +[ + { + "type": "root", + "child": { + "type": "branch", + "ref": "SomeOtherTree" + } + }, + { + "type": "root", + "id": "SomeOtherTree", + "child": { + "type": "action", + "call": "Dance" + } + } +] +``` + +```json +{ + "type": "root", + "child": { + "type": "action", + "call": "Dance" + } +} + +``` + ## Callbacks Callbacks can be defined for tree nodes and will be invoked as the node is processed during a tree step. Any number of callbacks can be attached to a node as long as there are not multiple callbacks of the same type. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=callbacks) @@ -491,6 +955,7 @@ Optional arguments can be defined for callback functions in the same way as acti ### Entry An entry callback defines a function to call whenever the associated node moves out of the **ready** state when it is first visited. +*MDSL* ``` root { sequence entry(StartWalkingAnimation) { @@ -505,6 +970,7 @@ root { ### Exit An exit callback defines a function to call whenever the associated node moves to a finished state or is aborted. A results object is passed to the referenced function containing the **succeeded** and **aborted** boolean properties. +*MDSL* ``` root { sequence entry(StartWalkingAnimation) exit(StopWalkingAnimation) { @@ -516,9 +982,44 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "sequence", + "entry": { + "call": "StartWalkingAnimation" + }, + "exit": { + "call": "StopWalkingAnimation" + }, + "children": [ + { + "type": "action", + "call": "WalkNorthOneSpace" + }, + { + "type": "action", + "call": "WalkEastOneSpace" + }, + { + "type": "action", + "call": "WalkSouthOneSpace" + }, + { + "type": "action", + "call": "WalkWestOneSpace" + } + ] + } +} +``` + ### Step A step callback defines a function to call whenever the associated node is updated as part of a tree step. +*MDSL* ``` root { sequence step(OnMoving) { @@ -530,45 +1031,143 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "sequence", + "step": { + "call": "OnMoving" + }, + "children": [ + { + "type": "action", + "call": "WalkNorthOneSpace" + }, + { + "type": "action", + "call": "WalkEastOneSpace" + }, + { + "type": "action", + "call": "WalkSouthOneSpace" + }, + { + "type": "action", + "call": "WalkWestOneSpace" + } + ] + } +} +``` + #### Optional Arguments -Arguments can optionally be passed to agent callback functions and can be a `number`, `string`, `boolean` or `null`. +Arguments can optionally be passed to agent callback functions and can be a `number`, `string`, `boolean` or `null` if using MDSL, or any valid JSON when using a JSON definition. +*MDSL* ``` root { action [Walk] entry(OnMovementStart, "walking") } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "action", + "call": "Walk", + "entry": { + "call": "OnMovementStart", + "args": [ + "walking" + ] + } + } +} +``` + ## Guards A guard defines a condition that must be met in order for the associated node to remain active. Any running nodes will have their guard condition evaluated for each leaf node update, and will move to a failed state if the guard condition is not met. [Example](https://nikkorn.github.io/mistreevous-visualiser/index.html?example=guards) This functionality is useful as a means of aborting long running actions or branches that span across multiple steps of the tree. +*MDSL* ``` root { wait while(CanWait) } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "wait", + "while": { + "call": "CanWait" + } + } +} +``` + In the above example, we have a **wait** node that waits for 10 seconds before moving to a succeeded state. We are using a **while** guard to give up on waiting this long if the guard function **CanWait** returns false during a tree step. #### Optional Arguments -Arguments can optionally be passed to agent guard functions and can be a `number`, `string`, `boolean` or `null`. +Arguments can optionally be passed to agent guard functions and can be a `number`, `string`, `boolean` or `null` if using MDSL, or any valid JSON when using a JSON definition. +*MDSL* ``` root { action [Run] while(HasItemEquipped, "running-shoes") } - +``` +``` root { action [Gamble] until(HasGold, 1000) } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "action", + "call": "Run", + "while": { + "call": "HasItemEquipped", + "args": [ + "running-shoes" + ] + } + } +} +``` +```json +{ + "type": "root", + "child": { + "type": "action", + "call": "Gamble", + "until": { + "call": "HasGold", + "args": [ + 1000 + ] + } + } +} +``` + ### While A while guard will be satisfied as long as its condition evaluates to true. +*MDSL* ``` root { sequence while(IsWandering) { @@ -580,9 +1179,41 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "sequence", + "while": { + "call": "IsWandering" + }, + "children": [ + { + "type": "action", + "call": "Whistle" + }, + { + "type": "wait", + "duration": 5000 + }, + { + "type": "action", + "call": "Yawn" + }, + { + "type": "wait", + "duration": 5000 + } + ] + } +} +``` + ### Until An until guard will be satisfied as long as its condition evaluates to false. +*MDSL* ``` root { sequence until(CanSeePlayer) { @@ -594,6 +1225,37 @@ root { } ``` +*JSON* +```json +{ + "type": "root", + "child": { + "type": "sequence", + "until": { + "call": "CanSeePlayer" + }, + "children": [ + { + "type": "action", + "call": "LookLeft" + }, + { + "type": "wait", + "duration": 5000 + }, + { + "type": "action", + "call": "LookRight" + }, + { + "type": "wait", + "duration": 5000 + } + ] + } +} +``` + ## Globals When dealing with multiple agents, each with their own behaviour tree instance, it can often be useful to have functions and subtrees that can be registered globally once and referenced by each of them. @@ -666,6 +1328,15 @@ A practical look at behaviour trees and a good example of modelling behaviour fo ## Version History | Version | Notes | | -------------- |:----------------------------------------------------------------------------------------| +| 4.0.0 | Added support for JSON tree defintions | +| | Added validateDefintion function to use in validating JSON/MDSL definitons | +| | Added convertMDSLToJSON function to convert existing MDSL definitions to JSON | +| | Tidied up error handling for agent and registered function invocation | +| | Action functions can now explictly return a value of State.RUNNING instead of having to return undefined | +| | Fixed issue where rejected action function promises were not handled correctly | +| | Fixed issue where registered functions were called with incorrect arguments | +| | Fixed some typings | +| | Added a BUNCH of tests | | 3.2.0 | The 'random' function option is used for iteration and attempt selection for `repeat` and `retry` nodes respectively when minimum and maximum bounds are defined | | 3.1.0 | Added 'random' function option to allow users to provide psuedo-random numbers for use in operations such as `lotto` node child selection and wait node duration selection when a minimum and maximum duration are defined. Wait nodes will now remain in the running state indefinitely until they are aborted if no duration is defined for them | | 3.0.0 | Converted to Typescript | diff --git a/dist/Agent.d.ts b/dist/Agent.d.ts index abde15d..b43f651 100644 --- a/dist/Agent.d.ts +++ b/dist/Agent.d.ts @@ -1,12 +1,15 @@ -import { CompleteState } from "./State"; +import State, { CompleteState } from "./State"; +/** + * A type representing an agent that a behavior tree instance would operate on. + */ export type Agent = { - [actionName: string]: AgentFunction; + [propertyName: string]: AgentFunction | unknown; }; export type ExitFunctionArg = { succeeded: boolean; aborted: boolean; }; -export type FunctionArg = number | string | boolean | null | ExitFunctionArg; -export type ActionResult = CompleteState | Promise | boolean; -export type AgentFunction = (this: Agent, ...args: FunctionArg[]) => ActionResult; -export type GlobalFunction = (agent: Agent, ...args: FunctionArg[]) => ActionResult; +export type FunctionArg = any | ExitFunctionArg; +export type ActionResult = CompleteState | Promise | State.RUNNING | void; +export type AgentFunction = (this: Agent, ...args: FunctionArg[]) => ActionResult | boolean; +export type GlobalFunction = (agent: Agent, ...args: FunctionArg[]) => ActionResult | boolean; diff --git a/dist/BehaviourTree.d.ts b/dist/BehaviourTree.d.ts index 586452e..b54ab39 100644 --- a/dist/BehaviourTree.d.ts +++ b/dist/BehaviourTree.d.ts @@ -1,10 +1,9 @@ -import { AnyArgument } from "./RootAstNodesBuilder"; import { AnyState } from "./State"; -import Root from "./nodes/decorator/Root"; import { Agent, GlobalFunction } from "./Agent"; import { CallbackAttributeDetails } from "./attributes/callbacks/Callback"; import { GuardAttributeDetails } from "./attributes/guards/Guard"; import { BehaviourTreeOptions } from "./BehaviourTreeOptions"; +import { RootNodeDefinition } from "./BehaviourTreeDefinition"; export type FlattenedTreeNode = { id: string; type: string; @@ -12,7 +11,7 @@ export type FlattenedTreeNode = { state: AnyState; guards: GuardAttributeDetails[]; callbacks: CallbackAttributeDetails[]; - args: AnyArgument[]; + args: any[]; parentId: string | null; }; /** @@ -24,14 +23,14 @@ export declare class BehaviourTree { /** * The main root tree node. */ - readonly rootNode: Root; + private readonly _rootNode; /** * Creates a new instance of the BehaviourTree class. - * @param definition The behaviour tree definition. + * @param definition The behaviour tree definition as either an MDSL string, root node definition object or array of root node definition objects. * @param agent The agent instance that this behaviour tree is modelling behaviour for. * @param options The behaviour tree options object. */ - constructor(definition: string, agent: Agent, options?: BehaviourTreeOptions); + constructor(definition: string | RootNodeDefinition | RootNodeDefinition[], agent: Agent, options?: BehaviourTreeOptions); /** * Gets whether the tree is in the RUNNING state. * @returns true if the tree is in the RUNNING state, otherwise false. @@ -65,7 +64,7 @@ export declare class BehaviourTree { * @param name The name of the function or subtree to register. * @param value The function or subtree definition to register. */ - static register(name: string, value: GlobalFunction | string): void; + static register(name: string, value: GlobalFunction | string | RootNodeDefinition): void; /** * Unregisters the registered action/condition/guard/callback function or subtree with the given name. * @param name The name of the registered action/condition/guard/callback function or subtree to unregister. @@ -75,15 +74,4 @@ export declare class BehaviourTree { * Unregister all registered action/condition/guard/callback functions and subtrees. */ static unregisterAll(): void; - /** - * Parses a behaviour tree definition and creates a tree of behaviour tree nodes. - * @param {string} definition The behaviour tree definition. - * @returns The root behaviour tree node. - */ - private static createRootNode; - /** - * Applies a guard path to every leaf of the tree to evaluate as part of each update. - * @param rootNode The main root tree node. - */ - private static applyLeafNodeGuardPaths; } diff --git a/dist/BehaviourTreeBuilder.d.ts b/dist/BehaviourTreeBuilder.d.ts new file mode 100644 index 0000000..f99c54a --- /dev/null +++ b/dist/BehaviourTreeBuilder.d.ts @@ -0,0 +1,8 @@ +import { RootNodeDefinition } from "./BehaviourTreeDefinition"; +import Root from "./nodes/decorator/Root"; +/** + * Build and populate the root nodes based on the provided definition, assuming that the definition has been validated. + * @param definition The root node definitions. + * @returns The built and populated root node definitions. + */ +export default function buildRootNode(definition: RootNodeDefinition[]): Root; diff --git a/dist/BehaviourTreeDefinition.d.ts b/dist/BehaviourTreeDefinition.d.ts new file mode 100644 index 0000000..129b9b3 --- /dev/null +++ b/dist/BehaviourTreeDefinition.d.ts @@ -0,0 +1,225 @@ +/** + * An attribute for a node. + */ +export interface NodeAttributeDefinition { + /** + * The name of the agent function to invoke. + */ + call: string; + /** + * An array of arguments to pass when invoking the agent function. + */ + args?: any[]; +} +/** + * A type defining a general node definition. + */ +export interface NodeDefinition { + /** + * The node type. + */ + type: string; + /** + * The 'while' node attribute definition. + */ + while?: NodeAttributeDefinition; + /** + * The 'until' node attribute definition. + */ + until?: NodeAttributeDefinition; + /** + * The 'entry' node attribute definition. + */ + entry?: NodeAttributeDefinition; + /** + * The 'exit' node attribute definition. + */ + exit?: NodeAttributeDefinition; + /** + * The 'step' node attribute definition. + */ + step?: NodeAttributeDefinition; +} +/** + * A composite node that can contain any number of child nodes. + */ +export interface CompositeNodeDefinition extends NodeDefinition { + /** + * The child nodes of this composite node. + */ + children: AnyChildNodeDefinition[]; +} +/** + * A decorator node, a composite with only a single child node. + */ +export interface DecoratorNodeDefinition extends NodeDefinition { + /** + * The child node of this decorator node. + */ + child: AnyChildNodeDefinition; +} +/** + * A branch node. + */ +export interface BranchNodeDefinition extends NodeDefinition { + /** + * The node type. + */ + type: "branch"; + /** + * The reference matching a root node identifier. + */ + ref: string; +} +/** + * An action node. + */ +export interface ActionNodeDefinition extends NodeDefinition { + /** + * The node type. + */ + type: "action"; + /** + * The name of the agent function or registered function to invoke. + */ + call: string; + /** + * An array of arguments to pass when invoking the action function. + */ + args?: any[]; +} +/** + * A condition node. + */ +export interface ConditionNodeDefinition extends NodeDefinition { + /** + * The node type. + */ + type: "condition"; + /** + * The name of the agent function or registered function to invoke. + */ + call: string; + /** + * An array of arguments to pass when invoking the condition function. + */ + args?: any[]; +} +/** + * A wait node. + */ +export interface WaitNodeDefinition extends NodeDefinition { + /** + * The node type. + */ + type: "wait"; + duration?: number | [number, number]; +} +/** + * A sequence node. + */ +export interface SequenceNodeDefinition extends CompositeNodeDefinition { + /** + * The node type. + */ + type: "sequence"; +} +/** + * A selector node. + */ +export interface SelectorNodeDefinition extends CompositeNodeDefinition { + /** + * The node type. + */ + type: "selector"; +} +/** + * A lotto node. + */ +export interface LottoNodeDefinition extends CompositeNodeDefinition { + /** + * The node type. + */ + type: "lotto"; + /** + * The selection weights for child nodes that correspond to the child node position. + */ + weights?: number[]; +} +/** + * A parallel node. + */ +export interface ParallelNodeDefinition extends CompositeNodeDefinition { + /** + * The node type. + */ + type: "parallel"; +} +/** + * A root node. + */ +export interface RootNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "root"; + /** + * The unique root node identifier. + */ + id?: string; +} +/** + * A repeat node. + */ +export interface RepeatNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "repeat"; + iterations?: number | [number, number]; +} +/** + * A retry node. + */ +export interface RetryNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "retry"; + attempts?: number | [number, number]; +} +/** + * A flip node. + */ +export interface FlipNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "flip"; +} +/** + * A succeed node. + */ +export interface SucceedNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "succeed"; +} +/** + * A fail node. + */ +export interface FailNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "fail"; +} +/** + * A type defining any node definition. + */ +export type AnyNodeDefinition = BranchNodeDefinition | ActionNodeDefinition | ConditionNodeDefinition | WaitNodeDefinition | SequenceNodeDefinition | SelectorNodeDefinition | LottoNodeDefinition | ParallelNodeDefinition | RootNodeDefinition | RepeatNodeDefinition | RetryNodeDefinition | FlipNodeDefinition | SucceedNodeDefinition | FailNodeDefinition; +/** + * A type defining any node type that can be a child of composite parent node. + */ +export type AnyChildNodeDefinition = Exclude; diff --git a/dist/BehaviourTreeDefinitionUtilities.d.ts b/dist/BehaviourTreeDefinitionUtilities.d.ts new file mode 100644 index 0000000..bbf6b0e --- /dev/null +++ b/dist/BehaviourTreeDefinitionUtilities.d.ts @@ -0,0 +1,49 @@ +import { NodeDefinition, RootNodeDefinition, DecoratorNodeDefinition, CompositeNodeDefinition, AnyNodeDefinition, BranchNodeDefinition } from "./BehaviourTreeDefinition"; +/** + * A type guard function that returns true if the specified node satisfies the RootNodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the RootNodeDefinition type. + */ +export declare function isRootNode(node: NodeDefinition): node is RootNodeDefinition; +/** + * A type guard function that returns true if the specified node satisfies the BranchNodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the BranchNodeDefinition type. + */ +export declare function isBranchNode(node: NodeDefinition): node is BranchNodeDefinition; +/** + * A type guard function that returns true if the specified node satisfies the NodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the NodeDefinition type. + */ +export declare function isLeafNode(node: NodeDefinition): node is NodeDefinition; +/** + * A type guard function that returns true if the specified node satisfies the DecoratorNodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the DecoratorNodeDefinition type. + */ +export declare function isDecoratorNode(node: NodeDefinition): node is DecoratorNodeDefinition; +/** + * A type guard function that returns true if the specified node satisfies the CompositeNodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the CompositeNodeDefinition type. + */ +export declare function isCompositeNode(node: NodeDefinition): node is CompositeNodeDefinition; +/** + * Flatten a node definition into an array of all of its nested node definitions. + * @param nodeDefinition The node definition to flatten. + * @returns An array of all of nested node definitions. + */ +export declare function flattenDefinition(nodeDefinition: AnyNodeDefinition): AnyNodeDefinition[]; +/** + * Determines whether the passed value is an integer. + * @param value The value to check. + * @returns Whether the passed value is an integer. + */ +export declare function isInteger(value: unknown): boolean; +/** + * Determines whether the passed value is null or undefined. + * @param value The value to check. + * @returns Whether the passed value is null or undefined. + */ +export declare function isNullOrUndefined(value: unknown): boolean; diff --git a/dist/BehaviourTreeDefinitionValidator.d.ts b/dist/BehaviourTreeDefinitionValidator.d.ts new file mode 100644 index 0000000..e8f7dba --- /dev/null +++ b/dist/BehaviourTreeDefinitionValidator.d.ts @@ -0,0 +1,38 @@ +import { RootNodeDefinition } from "./BehaviourTreeDefinition"; +/** + * An object representing the result of validating a tree definition. + */ +export type DefinitionValidationResult = { + /** + * A flag defining whether validation succeeded. + */ + succeeded: boolean; + /** + * A string containing the error message if validation did not succeed. + */ + errorMessage?: string; + /** + * The definition as json if the validation was successful, or undefined if validation did not succeed. + */ + json?: RootNodeDefinition[]; +}; +/** + * Validates the specified behaviour tree definition in the form of JSON or MDSL, not taking any globally registered subtrees into consideration. + * @param definition The behaviour tree definition in the form of JSON or MDSL. + * @returns An object representing the result of validating the given tree definition. + */ +export declare function validateDefinition(definition: any): DefinitionValidationResult; +/** + * Validates the specified behaviour tree definition in the form of JSON. + * @param definition The behaviour tree definition in the form of JSON. + * @returns An object representing the result of validating the given tree definition. + */ +export declare function validateJSONDefinition(definition: RootNodeDefinition | RootNodeDefinition[]): DefinitionValidationResult; +/** + * Validates the branch -> subtree links across all provided root node definitions. + * This will not consider branch nodes that reference any globally registered subtrees unless includesGlobalSubtrees + * is set to true, in which case we will also verify that there are no broken branch -> subtree links. + * @param rootNodeDefinitions The array of root node definitions. + * @param includesGlobalSubtrees A flag defining whether the array includes all global subtree root node definitions. + */ +export declare function validateBranchSubtreeLinks(rootNodeDefinitions: RootNodeDefinition[], includesGlobalSubtrees: boolean): void; diff --git a/dist/Lookup.d.ts b/dist/Lookup.d.ts index 8efbc28..e9534b4 100644 --- a/dist/Lookup.d.ts +++ b/dist/Lookup.d.ts @@ -1,10 +1,6 @@ -import { ActionResult, Agent, ExitFunctionArg, GlobalFunction } from "./Agent"; -import { AnyArgument, RootAstNode } from "./RootAstNodesBuilder"; -type ExitResultArg = { - value: ExitFunctionArg; -}; -export type AnyExitArgument = AnyArgument | ExitResultArg; -export type InvokerFunction = (args: AnyExitArgument[]) => ActionResult; +import { ActionResult, Agent, GlobalFunction } from "./Agent"; +import { RootNodeDefinition } from "./BehaviourTreeDefinition"; +export type InvokerFunction = (args: any[]) => ActionResult | boolean; /** * A singleton used to store and lookup registered functions and subtrees. */ @@ -12,11 +8,11 @@ export default class Lookup { /** * The object holding any registered functions keyed on function name. */ - private static functionTable; + private static registeredFunctions; /** - * The object holding any registered sub-trees keyed on tree name. + * The object holding any registered subtree root node definitions keyed on tree name. */ - private static subtreeTable; + private static registeredSubtrees; /** * Gets the function with the specified name. * @param name The name of the function. @@ -39,17 +35,17 @@ export default class Lookup { */ static getFuncInvoker(agent: Agent, name: string): InvokerFunction | null; /** - * Gets the subtree with the specified name. - * @param name The name of the subtree. - * @returns The subtree with the specified name. + * Gets all registered subtree root node definitions. */ - static getSubtree(name: string): RootAstNode; + static getSubtrees(): { + [key: string]: RootNodeDefinition; + }; /** * Sets the subtree with the specified name for later lookup. * @param name The name of the subtree. * @param subtree The subtree. */ - static setSubtree(name: string, subtree: RootAstNode): void; + static setSubtree(name: string, subtree: RootNodeDefinition): void; /** * Removes the registered function or subtree with the specified name. * @param name The name of the registered function or subtree. @@ -60,4 +56,3 @@ export default class Lookup { */ static empty(): void; } -export {}; diff --git a/dist/attributes/Attribute.d.ts b/dist/attributes/Attribute.d.ts index 6fb7bb3..515015a 100644 --- a/dist/attributes/Attribute.d.ts +++ b/dist/attributes/Attribute.d.ts @@ -1,30 +1,21 @@ -import { AnyArgument } from "../RootAstNodesBuilder"; import Guard from "./guards/Guard"; export type AttributeDetails = { /** The attribute type. */ type: string; /** The attribute arguments. */ - args: AnyArgument[]; + args: any[]; }; /** * A base node attribute. */ export default abstract class Attribute { - protected type: string; - protected args: AnyArgument[]; + type: string; + args: any[]; /** * @param type The node attribute type. - * @param args The array of attribute argument definitions. - */ - constructor(type: string, args: AnyArgument[]); - /** - * Gets the type of the attribute. - */ - getType: () => string; - /** - * Gets the array of attribute argument definitions. + * @param args The array of attribute arguments. */ - getArguments: () => AnyArgument[]; + constructor(type: string, args: any[]); /** * Gets the attribute details. */ diff --git a/dist/attributes/callbacks/Callback.d.ts b/dist/attributes/callbacks/Callback.d.ts index 6a294b7..0ff5296 100644 --- a/dist/attributes/callbacks/Callback.d.ts +++ b/dist/attributes/callbacks/Callback.d.ts @@ -1,5 +1,4 @@ import { Agent } from "../../Agent"; -import { AnyArgument } from "../../RootAstNodesBuilder"; import Attribute, { AttributeDetails } from "../Attribute"; export type CallbackAttributeDetails = { /** The name of the agent function that is called. */ @@ -15,7 +14,7 @@ export default abstract class Callback extends Attribute { * @param args The array of decorator argument definitions. * @param condition The name of the condition function that determines whether the guard is satisfied. */ - constructor(type: string, args: AnyArgument[], condition: string); + constructor(type: string, args: any[], condition: string); /** * Gets the name of the condition function that determines whether the guard is satisfied. */ diff --git a/dist/attributes/guards/Until.d.ts b/dist/attributes/guards/Until.d.ts index 5ec505f..55bb8bf 100644 --- a/dist/attributes/guards/Until.d.ts +++ b/dist/attributes/guards/Until.d.ts @@ -1,6 +1,5 @@ import Guard from "./Guard"; import { Agent } from "../../Agent"; -import { AnyArgument } from "../../RootAstNodesBuilder"; /** * An UNTIL guard which is satisfied as long as the given condition remains false. */ @@ -9,7 +8,7 @@ export default class Until extends Guard { * @param condition The name of the condition function that determines whether the guard is satisfied. * @param args The array of decorator argument definitions. */ - constructor(condition: string, args: AnyArgument[]); + constructor(condition: string, args: any[]); /** * Gets whether the guard is satisfied. * @param agent The agent. diff --git a/dist/attributes/guards/While.d.ts b/dist/attributes/guards/While.d.ts index dd4f6bf..d40f4bf 100644 --- a/dist/attributes/guards/While.d.ts +++ b/dist/attributes/guards/While.d.ts @@ -1,6 +1,5 @@ import Guard from "./Guard"; import { Agent } from "../../Agent"; -import { AnyArgument } from "../../RootAstNodesBuilder"; /** * A WHILE guard which is satisfied as long as the given condition remains true. */ @@ -9,7 +8,7 @@ export default class While extends Guard { * @param condition The name of the condition function that determines whether the guard is satisfied. * @param args The array of decorator argument definitions. */ - constructor(condition: string, args: AnyArgument[]); + constructor(condition: string, args: any[]); /** * Gets whether the guard is satisfied. * @param agent The agent. diff --git a/dist/bundle.js b/dist/bundle.js index 82f3ec6..f9c95fa 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -75,10 +75,10 @@ var mistreevous = (() => { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isNaturalNumber = exports.isNullOrUndefined = void 0; - function isNullOrUndefined(value) { + function isNullOrUndefined2(value) { return value === null || value === void 0; } - exports.isNullOrUndefined = isNullOrUndefined; + exports.isNullOrUndefined = isNullOrUndefined2; function isNaturalNumber(value) { return typeof value === "number" && value >= 1 && Math.floor(value) === value; } @@ -252,9 +252,1015 @@ var mistreevous = (() => { var src_exports = {}; __export(src_exports, { BehaviourTree: () => BehaviourTree, - State: () => State + State: () => State, + convertMDSLToJSON: () => convertMDSLToJSON, + validateDefinition: () => validateDefinition }); + // src/State.ts + var State = /* @__PURE__ */ ((State2) => { + State2["READY"] = "mistreevous.ready"; + State2["RUNNING"] = "mistreevous.running"; + State2["SUCCEEDED"] = "mistreevous.succeeded"; + State2["FAILED"] = "mistreevous.failed"; + return State2; + })(State || {}); + + // src/BehaviourTreeDefinitionUtilities.ts + function isRootNode(node) { + return node.type === "root"; + } + function isBranchNode(node) { + return node.type === "branch"; + } + function isLeafNode(node) { + return ["branch", "action", "condition", "wait"].includes(node.type); + } + function isDecoratorNode(node) { + return ["root", "repeat", "retry", "flip", "succeed", "fail"].includes(node.type); + } + function isCompositeNode(node) { + return ["sequence", "selector", "lotto", "parallel"].includes(node.type); + } + function flattenDefinition(nodeDefinition) { + const nodes = []; + const processNode = (currentNodeDefinition) => { + nodes.push(currentNodeDefinition); + if (isCompositeNode(currentNodeDefinition)) { + currentNodeDefinition.children.forEach(processNode); + } else if (isDecoratorNode(currentNodeDefinition)) { + processNode(currentNodeDefinition.child); + } + }; + processNode(nodeDefinition); + return nodes; + } + function isInteger(value) { + return typeof value === "number" && Math.floor(value) === value; + } + function isNullOrUndefined(value) { + return typeof value === "undefined" || value === null; + } + + // src/mdsl/MDSLUtilities.ts + function popAndCheck(tokens, expected) { + const popped = tokens.shift(); + if (popped === void 0) { + throw new Error("unexpected end of definition"); + } + if (expected != void 0) { + const expectedValues = typeof expected === "string" ? [expected] : expected; + var tokenMatchesExpectation = expectedValues.some((item) => popped.toUpperCase() === item.toUpperCase()); + if (!tokenMatchesExpectation) { + const expectationString = expectedValues.map((item) => "'" + item + "'").join(" or "); + throw new Error("unexpected token found. Expected " + expectationString + " but got '" + popped + "'"); + } + } + return popped; + } + function substituteStringLiterals(definition) { + const placeholders = {}; + const processedDefinition = definition.replace(/\"(\\.|[^"\\])*\"/g, (match) => { + var strippedMatch = match.substring(1, match.length - 1); + var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch); + if (!placeholder) { + placeholder = `@@${Object.keys(placeholders).length}@@`; + placeholders[placeholder] = strippedMatch; + } + return placeholder; + }); + return { placeholders, processedDefinition }; + } + function parseTokensFromDefinition(definition) { + definition = definition.replace(/\(/g, " ( "); + definition = definition.replace(/\)/g, " ) "); + definition = definition.replace(/\{/g, " { "); + definition = definition.replace(/\}/g, " } "); + definition = definition.replace(/\]/g, " ] "); + definition = definition.replace(/\[/g, " [ "); + definition = definition.replace(/\,/g, " , "); + return definition.replace(/\s+/g, " ").trim().split(" "); + } + + // src/mdsl/MDSLNodeArgumentParser.ts + function parseArgumentTokens(tokens, stringArgumentPlaceholders) { + const argumentList = []; + if (!["[", "("].includes(tokens[0])) { + return argumentList; + } + const closingToken = popAndCheck(tokens, ["[", "("]) === "[" ? "]" : ")"; + const argumentListTokens = []; + while (tokens.length && tokens[0] !== closingToken) { + argumentListTokens.push(tokens.shift()); + } + argumentListTokens.forEach((token, index) => { + const shouldBeArgumentToken = !(index & 1); + if (shouldBeArgumentToken) { + const argumentDefinition = getArgumentDefinition(token, stringArgumentPlaceholders); + argumentList.push(argumentDefinition); + } else { + if (token !== ",") { + throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`); + } + } + }); + popAndCheck(tokens, closingToken); + return argumentList; + } + function getArgumentDefinition(token, stringArgumentPlaceholders) { + if (token === "null") { + return { + value: null, + type: "null" + }; + } + if (token === "true" || token === "false") { + return { + value: token === "true", + type: "boolean" + }; + } + if (!isNaN(token)) { + return { + value: parseFloat(token), + isInteger: parseFloat(token) === parseInt(token, 10), + type: "number" + }; + } + if (token.match(/^@@\d+@@$/g)) { + return { + value: stringArgumentPlaceholders[token].replace('\\"', '"'), + type: "string" + }; + } + return { + value: token, + type: "identifier" + }; + } + + // src/mdsl/MDSLNodeAttributeParser.ts + function parseAttributeTokens(tokens, stringArgumentPlaceholders) { + const nodeAttributeNames = ["while", "until", "entry", "exit", "step"]; + const attributes = {}; + let nextAttributeName = tokens[0]?.toLowerCase(); + while (nodeAttributeNames.includes(nextAttributeName)) { + if (attributes[nextAttributeName]) { + throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`); + } + tokens.shift(); + const [attributeCallIdentifier, ...attributeArguments] = parseArgumentTokens( + tokens, + stringArgumentPlaceholders + ); + if (attributeCallIdentifier?.type !== "identifier") { + throw new Error("expected agent function or registered function name identifier argument for attribute"); + } + attributeArguments.filter((arg) => arg.type === "identifier").forEach((arg) => { + throw new Error( + `invalid attribute argument value '${arg.value}', must be string, number, boolean or null` + ); + }); + attributes[nextAttributeName] = { + call: attributeCallIdentifier.value, + args: attributeArguments.map(({ value }) => value) + }; + nextAttributeName = tokens[0]?.toLowerCase(); + } + return attributes; + } + + // src/mdsl/MDSLDefinitionParser.ts + function convertMDSLToJSON(definition) { + const { placeholders, processedDefinition } = substituteStringLiterals(definition); + const tokens = parseTokensFromDefinition(processedDefinition); + return convertTokensToJSONDefinition(tokens, placeholders); + } + function convertTokensToJSONDefinition(tokens, stringLiteralPlaceholders) { + if (tokens.length < 3) { + throw new Error("invalid token count"); + } + if (tokens.filter((token) => token === "{").length !== tokens.filter((token) => token === "}").length) { + throw new Error("scope character mismatch"); + } + const treeStacks = []; + const rootNodes = []; + const pushNode = (node) => { + if (isRootNode(node)) { + if (treeStacks[treeStacks.length - 1]?.length) { + throw new Error("a root node cannot be the child of another node"); + } + rootNodes.push(node); + treeStacks.push([node]); + return; + } + if (!treeStacks.length || !treeStacks[treeStacks.length - 1].length) { + throw new Error("expected root node at base of definition"); + } + const topTreeStack = treeStacks[treeStacks.length - 1]; + const topTreeStackTopNode = topTreeStack[topTreeStack.length - 1]; + if (isCompositeNode(topTreeStackTopNode)) { + topTreeStackTopNode.children = topTreeStackTopNode.children || []; + topTreeStackTopNode.children.push(node); + } else if (isDecoratorNode(topTreeStackTopNode)) { + if (topTreeStackTopNode.child) { + throw new Error("a decorator node must only have a single child node"); + } + topTreeStackTopNode.child = node; + } + if (!isLeafNode(node)) { + topTreeStack.push(node); + } + }; + const popNode = () => { + let poppedNode = null; + const topTreeStack = treeStacks[treeStacks.length - 1]; + if (topTreeStack.length) { + poppedNode = topTreeStack.pop(); + } + if (!topTreeStack.length) { + treeStacks.pop(); + } + return poppedNode; + }; + while (tokens.length) { + const token = tokens.shift(); + switch (token.toUpperCase()) { + case "ROOT": { + pushNode(createRootNode(tokens, stringLiteralPlaceholders)); + break; + } + case "SUCCEED": { + pushNode(createSucceedNode(tokens, stringLiteralPlaceholders)); + break; + } + case "FAIL": { + pushNode(createFailNode(tokens, stringLiteralPlaceholders)); + break; + } + case "FLIP": { + pushNode(createFlipNode(tokens, stringLiteralPlaceholders)); + break; + } + case "REPEAT": { + pushNode(createRepeatNode(tokens, stringLiteralPlaceholders)); + break; + } + case "RETRY": { + pushNode(createRetryNode(tokens, stringLiteralPlaceholders)); + break; + } + case "SEQUENCE": { + pushNode(createSequenceNode(tokens, stringLiteralPlaceholders)); + break; + } + case "SELECTOR": { + pushNode(createSelectorNode(tokens, stringLiteralPlaceholders)); + break; + } + case "PARALLEL": { + pushNode(createParallelNode(tokens, stringLiteralPlaceholders)); + break; + } + case "LOTTO": { + pushNode(createLottoNode(tokens, stringLiteralPlaceholders)); + break; + } + case "ACTION": { + pushNode(createActionNode(tokens, stringLiteralPlaceholders)); + break; + } + case "CONDITION": { + pushNode(createConditionNode(tokens, stringLiteralPlaceholders)); + break; + } + case "WAIT": { + pushNode(createWaitNode(tokens, stringLiteralPlaceholders)); + break; + } + case "BRANCH": { + pushNode(createBranchNode(tokens, stringLiteralPlaceholders)); + break; + } + case "}": { + const poppedNode = popNode(); + if (poppedNode) { + validatePoppedNode(poppedNode); + } + break; + } + default: { + throw new Error(`unexpected token: ${token}`); + } + } + } + return rootNodes; + } + function createRootNode(tokens, stringLiteralPlaceholders) { + let node = { + type: "root" + }; + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length) { + if (nodeArguments.length === 1 && nodeArguments[0].type === "identifier") { + node.id = nodeArguments[0].value; + } else { + throw new Error("expected single root name argument"); + } + } + node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + popAndCheck(tokens, "{"); + return node; + } + function createSucceedNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "succeed", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; + } + function createFailNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "fail", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; + } + function createFlipNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "flip", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; + } + function createRepeatNode(tokens, stringLiteralPlaceholders) { + let node = { type: "repeat" }; + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length) { + nodeArguments.filter((arg) => arg.type !== "number" || !arg.isInteger).forEach(() => { + throw new Error(`repeat node iteration counts must be integer values`); + }); + if (nodeArguments.length === 1) { + node.iterations = nodeArguments[0].value; + if (node.iterations < 0) { + throw new Error("a repeat node must have a positive number of iterations if defined"); + } + } else if (nodeArguments.length === 2) { + node.iterations = [nodeArguments[0].value, nodeArguments[1].value]; + if (node.iterations[0] < 0 || node.iterations[1] < 0) { + throw new Error("a repeat node must have a positive minimum and maximum iteration count if defined"); + } + if (node.iterations[0] > node.iterations[1]) { + throw new Error( + "a repeat node must not have a minimum iteration count that exceeds the maximum iteration count" + ); + } + } else { + throw new Error("invalid number of repeat node iteration count arguments defined"); + } + } + node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + popAndCheck(tokens, "{"); + return node; + } + function createRetryNode(tokens, stringLiteralPlaceholders) { + let node = { type: "retry" }; + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length) { + nodeArguments.filter((arg) => arg.type !== "number" || !arg.isInteger).forEach(() => { + throw new Error(`retry node attempt counts must be integer values`); + }); + if (nodeArguments.length === 1) { + node.attempts = nodeArguments[0].value; + if (node.attempts < 0) { + throw new Error("a retry node must have a positive number of attempts if defined"); + } + } else if (nodeArguments.length === 2) { + node.attempts = [nodeArguments[0].value, nodeArguments[1].value]; + if (node.attempts[0] < 0 || node.attempts[1] < 0) { + throw new Error("a retry node must have a positive minimum and maximum attempt count if defined"); + } + if (node.attempts[0] > node.attempts[1]) { + throw new Error( + "a retry node must not have a minimum attempt count that exceeds the maximum attempt count" + ); + } + } else { + throw new Error("invalid number of retry node attempt count arguments defined"); + } + } + node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + popAndCheck(tokens, "{"); + return node; + } + function createSequenceNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "sequence", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; + } + function createSelectorNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "selector", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; + } + function createParallelNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "parallel", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; + } + function createLottoNode(tokens, stringLiteralPlaceholders) { + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + nodeArguments.filter((arg) => arg.type !== "number" || !arg.isInteger || arg.value < 0).forEach(() => { + throw new Error(`lotto node weight arguments must be positive integer values`); + }); + const node = { + type: "lotto", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + if (nodeArguments.length) { + node.weights = nodeArguments.map(({ value }) => value); + } + popAndCheck(tokens, "{"); + return node; + } + function createActionNode(tokens, stringLiteralPlaceholders) { + const [actionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (actionNameIdentifier?.type !== "identifier") { + throw new Error("expected action name identifier argument"); + } + agentFunctionArgs.filter((arg) => arg.type === "identifier").forEach((arg) => { + throw new Error( + `invalid action node argument value '${arg.value}', must be string, number, boolean or null` + ); + }); + return { + type: "action", + call: actionNameIdentifier.value, + args: agentFunctionArgs.map(({ value }) => value), + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + } + function createConditionNode(tokens, stringLiteralPlaceholders) { + const [conditionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (conditionNameIdentifier?.type !== "identifier") { + throw new Error("expected condition name identifier argument"); + } + agentFunctionArgs.filter((arg) => arg.type === "identifier").forEach((arg) => { + throw new Error( + `invalid condition node argument value '${arg.value}', must be string, number, boolean or null` + ); + }); + return { + type: "condition", + call: conditionNameIdentifier.value, + args: agentFunctionArgs.map(({ value }) => value), + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + } + function createWaitNode(tokens, stringLiteralPlaceholders) { + let node = { type: "wait" }; + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length) { + nodeArguments.filter((arg) => arg.type !== "number" || !arg.isInteger).forEach(() => { + throw new Error(`wait node durations must be integer values`); + }); + if (nodeArguments.length === 1) { + node.duration = nodeArguments[0].value; + if (node.duration < 0) { + throw new Error("a wait node must have a positive duration"); + } + } else if (nodeArguments.length === 2) { + node.duration = [nodeArguments[0].value, nodeArguments[1].value]; + if (node.duration[0] < 0 || node.duration[1] < 0) { + throw new Error("a wait node must have a positive minimum and maximum duration"); + } + if (node.duration[0] > node.duration[1]) { + throw new Error("a wait node must not have a minimum duration that exceeds the maximum duration"); + } + } else if (nodeArguments.length > 2) { + throw new Error("invalid number of wait node duration arguments defined"); + } + } + return { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + } + function createBranchNode(tokens, stringLiteralPlaceholders) { + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length !== 1 || nodeArguments[0].type !== "identifier") { + throw new Error("expected single branch name argument"); + } + return { type: "branch", ref: nodeArguments[0].value }; + } + function validatePoppedNode(definition) { + if (isDecoratorNode(definition) && isNullOrUndefined(definition.child)) { + throw new Error(`a ${definition.type} node must have a single child node defined`); + } + if (isCompositeNode(definition) && !definition.children?.length) { + throw new Error(`a ${definition.type} node must have at least a single child node defined`); + } + if (definition.type === "lotto") { + if (typeof definition.weights !== "undefined") { + if (definition.weights.length !== definition.children.length) { + throw new Error( + "expected a number of weight arguments matching the number of child nodes for lotto node" + ); + } + } + } + } + + // src/BehaviourTreeDefinitionValidator.ts + function validateDefinition(definition) { + if (definition === null || typeof definition === "undefined") { + return createValidationFailureResult("definition is null or undefined"); + } + if (typeof definition === "string") { + return validateMDSLDefinition(definition); + } else if (typeof definition === "object") { + return validateJSONDefinition(definition); + } else { + return createValidationFailureResult(`unexpected definition type of '${typeof definition}'`); + } + } + function validateMDSLDefinition(definition) { + let rootNodeDefinitions; + try { + rootNodeDefinitions = convertMDSLToJSON(definition); + } catch (exception) { + return createValidationFailureResult(exception.message); + } + const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "undefined"); + const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "string" && id.length > 0); + if (mainRootNodeDefinitions.length !== 1) { + return createValidationFailureResult( + "expected single unnamed root node at base of definition to act as main root" + ); + } + const subRootNodeIdenitifers = []; + for (const { id } of subRootNodeDefinitions) { + if (subRootNodeIdenitifers.includes(id)) { + return createValidationFailureResult(`multiple root nodes found with duplicate name '${id}'`); + } + subRootNodeIdenitifers.push(id); + } + try { + validateBranchSubtreeLinks(rootNodeDefinitions, false); + } catch (exception) { + return createValidationFailureResult(exception.message); + } + return { + succeeded: true, + json: rootNodeDefinitions + }; + } + function validateJSONDefinition(definition) { + const rootNodeDefinitions = Array.isArray(definition) ? definition : [definition]; + try { + rootNodeDefinitions.forEach((rootNodeDefinition) => validateNode(rootNodeDefinition, 0)); + } catch (error) { + if (error instanceof Error) { + return createValidationFailureResult(error.message); + } + return createValidationFailureResult(`unexpected error: ${error}`); + } + const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "undefined"); + const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "string" && id.length > 0); + if (mainRootNodeDefinitions.length !== 1) { + return createValidationFailureResult( + "expected single root node without 'id' property defined to act as main root" + ); + } + const subRootNodeIdenitifers = []; + for (const { id } of subRootNodeDefinitions) { + if (subRootNodeIdenitifers.includes(id)) { + return createValidationFailureResult( + `multiple root nodes found with duplicate 'id' property value of '${id}'` + ); + } + subRootNodeIdenitifers.push(id); + } + try { + validateBranchSubtreeLinks(rootNodeDefinitions, false); + } catch (exception) { + return createValidationFailureResult(exception.message); + } + return { + succeeded: true, + json: rootNodeDefinitions + }; + } + function validateBranchSubtreeLinks(rootNodeDefinitions, includesGlobalSubtrees) { + const rootNodeMappings = rootNodeDefinitions.map( + (rootNodeDefinition) => ({ + id: rootNodeDefinition.id, + refs: flattenDefinition(rootNodeDefinition).filter(isBranchNode).map(({ ref }) => ref) + }) + ); + const followRefs = (mapping, path = []) => { + if (path.includes(mapping.id)) { + const badPath = [...path, mapping.id]; + const badPathFormatted = badPath.filter((element) => !!element).join(" => "); + throw new Error(`circular dependency found in branch node references: ${badPathFormatted}`); + } + for (const ref of mapping.refs) { + const subMapping = rootNodeMappings.find(({ id }) => id === ref); + if (subMapping) { + followRefs(subMapping, [...path, mapping.id]); + } else if (includesGlobalSubtrees) { + throw new Error( + mapping.id ? `subtree '${mapping.id}' has branch node that references root node '${ref}' which has not been defined` : `primary tree has branch node that references root node '${ref}' which has not been defined` + ); + } + } + }; + followRefs(rootNodeMappings.find((mapping) => typeof mapping.id === "undefined")); + } + function validateNode(definition, depth) { + if (typeof definition !== "object" || typeof definition.type !== "string" || definition.type.length === 0) { + throw new Error( + `node definition is not an object or 'type' property is not a non-empty string at depth '${depth}'` + ); + } + if (depth === 0 && definition.type !== "root") { + throw new Error(`expected root node at base of definition but got node of type '${definition.type}'`); + } + switch (definition.type) { + case "action": + validateActionNode(definition, depth); + break; + case "condition": + validateConditionNode(definition, depth); + break; + case "wait": + validateWaitNode(definition, depth); + break; + case "branch": + validateBranchNode(definition, depth); + break; + case "root": + validateRootNode(definition, depth); + break; + case "succeed": + validateSucceedNode(definition, depth); + break; + case "fail": + validateFailNode(definition, depth); + break; + case "flip": + validateFlipNode(definition, depth); + break; + case "repeat": + validateRepeatNode(definition, depth); + break; + case "retry": + validateRetryNode(definition, depth); + break; + case "sequence": + validateSequenceNode(definition, depth); + break; + case "selector": + validateSelectorNode(definition, depth); + break; + case "parallel": + validateParallelNode(definition, depth); + break; + case "lotto": + validateLottoNode(definition, depth); + break; + default: + throw new Error(`unexpected node type of '${definition.type}' at depth '${depth}'`); + } + } + function validateNodeAttributes(definition, depth) { + ["while", "until", "entry", "exit", "step"].forEach((attributeName) => { + const attributeDefinition = definition[attributeName]; + if (typeof attributeDefinition === "undefined") { + return; + } + if (typeof attributeDefinition !== "object") { + throw new Error( + `expected attribute '${attributeName}' to be an object for '${definition.type}' node at depth '${depth}'` + ); + } + if (typeof attributeDefinition.call !== "string" || attributeDefinition.call.length === 0) { + throw new Error( + `expected 'call' property for attribute '${attributeName}' to be a non-empty string for '${definition.type}' node at depth '${depth}'` + ); + } + if (typeof attributeDefinition.args !== "undefined" && !Array.isArray(attributeDefinition.args)) { + throw new Error( + `expected 'args' property for attribute '${attributeName}' to be an array for '${definition.type}' node at depth '${depth}'` + ); + } + }); + } + function validateRootNode(definition, depth) { + if (definition.type !== "root") { + throw new Error("expected node type of 'root' for root node"); + } + if (depth > 0) { + throw new Error("a root node cannot be the child of another node"); + } + if (typeof definition.id !== "undefined" && (typeof definition.id !== "string" || definition.id.length === 0)) { + throw new Error("expected non-empty string for 'id' property if defined for root node"); + } + if (typeof definition.child === "undefined") { + throw new Error("expected property 'child' to be defined for root node"); + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); + } + function validateSucceedNode(definition, depth) { + if (definition.type !== "succeed") { + throw new Error(`expected node type of 'succeed' for succeed node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for succeed node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); + } + function validateFailNode(definition, depth) { + if (definition.type !== "fail") { + throw new Error(`expected node type of 'fail' for fail node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for fail node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); + } + function validateFlipNode(definition, depth) { + if (definition.type !== "flip") { + throw new Error(`expected node type of 'flip' for flip node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for flip node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); + } + function validateRepeatNode(definition, depth) { + if (definition.type !== "repeat") { + throw new Error(`expected node type of 'repeat' for repeat node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for repeat node at depth '${depth}'`); + } + if (typeof definition.iterations !== "undefined") { + if (Array.isArray(definition.iterations)) { + const containsNonInteger = !!definition.iterations.filter((value) => !isInteger(value)).length; + if (definition.iterations.length !== 2 || containsNonInteger) { + throw new Error( + `expected array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + if (definition.iterations[0] < 0 || definition.iterations[1] < 0) { + throw new Error( + `expected positive minimum and maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + if (definition.iterations[0] > definition.iterations[1]) { + throw new Error( + `expected minimum iterations count that does not exceed the maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + } else if (isInteger(definition.iterations)) { + if (definition.iterations < 0) { + throw new Error( + `expected positive iterations count for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + } else { + throw new Error( + `expected integer value or array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); + } + function validateRetryNode(definition, depth) { + if (definition.type !== "retry") { + throw new Error(`expected node type of 'retry' for retry node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for retry node at depth '${depth}'`); + } + if (typeof definition.attempts !== "undefined") { + if (Array.isArray(definition.attempts)) { + const containsNonInteger = !!definition.attempts.filter((value) => !isInteger(value)).length; + if (definition.attempts.length !== 2 || containsNonInteger) { + throw new Error( + `expected array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + if (definition.attempts[0] < 0 || definition.attempts[1] < 0) { + throw new Error( + `expected positive minimum and maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + if (definition.attempts[0] > definition.attempts[1]) { + throw new Error( + `expected minimum attempts count that does not exceed the maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + } else if (isInteger(definition.attempts)) { + if (definition.attempts < 0) { + throw new Error( + `expected positive attempts count for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + } else { + throw new Error( + `expected integer value or array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); + } + function validateBranchNode(definition, depth) { + if (definition.type !== "branch") { + throw new Error(`expected node type of 'branch' for branch node at depth '${depth}'`); + } + if (typeof definition.ref !== "string" || definition.ref.length === 0) { + throw new Error(`expected non-empty string for 'ref' property for branch node at depth '${depth}'`); + } + ["while", "until"].forEach((attributeName) => { + if (typeof definition[attributeName] !== "undefined") { + throw new Error( + `guards should not be defined for branch nodes but guard '${attributeName}' was defined for branch node at depth '${depth}'` + ); + } + }); + ["entry", "exit", "step"].forEach((attributeName) => { + if (typeof definition[attributeName] !== "undefined") { + throw new Error( + `callbacks should not be defined for branch nodes but callback '${attributeName}' was defined for branch node at depth '${depth}'` + ); + } + }); + } + function validateActionNode(definition, depth) { + if (definition.type !== "action") { + throw new Error(`expected node type of 'action' for action node at depth '${depth}'`); + } + if (typeof definition.call !== "string" || definition.call.length === 0) { + throw new Error(`expected non-empty string for 'call' property of action node at depth '${depth}'`); + } + if (typeof definition.args !== "undefined" && !Array.isArray(definition.args)) { + throw new Error(`expected array for 'args' property if defined for action node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + } + function validateConditionNode(definition, depth) { + if (definition.type !== "condition") { + throw new Error(`expected node type of 'condition' for condition node at depth '${depth}'`); + } + if (typeof definition.call !== "string" || definition.call.length === 0) { + throw new Error(`expected non-empty string for 'call' property of condition node at depth '${depth}'`); + } + if (typeof definition.args !== "undefined" && !Array.isArray(definition.args)) { + throw new Error(`expected array for 'args' property if defined for condition node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + } + function validateWaitNode(definition, depth) { + if (definition.type !== "wait") { + throw new Error(`expected node type of 'wait' for wait node at depth '${depth}'`); + } + if (typeof definition.duration !== "undefined") { + if (Array.isArray(definition.duration)) { + const containsNonInteger = !!definition.duration.filter((value) => !isInteger(value)).length; + if (definition.duration.length !== 2 || containsNonInteger) { + throw new Error( + `expected array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + if (definition.duration[0] < 0 || definition.duration[1] < 0) { + throw new Error( + `expected positive minimum and maximum duration for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + if (definition.duration[0] > definition.duration[1]) { + throw new Error( + `expected minimum duration value that does not exceed the maximum duration value for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + } else if (isInteger(definition.duration)) { + if (definition.duration < 0) { + throw new Error( + `expected positive duration value for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + } else { + throw new Error( + `expected integer value or array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + } + validateNodeAttributes(definition, depth); + } + function validateSequenceNode(definition, depth) { + if (definition.type !== "sequence") { + throw new Error(`expected node type of 'sequence' for sequence node at depth '${depth}'`); + } + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for sequence node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + definition.children.forEach((child) => validateNode(child, depth + 1)); + } + function validateSelectorNode(definition, depth) { + if (definition.type !== "selector") { + throw new Error(`expected node type of 'selector' for selector node at depth '${depth}'`); + } + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for selector node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + definition.children.forEach((child) => validateNode(child, depth + 1)); + } + function validateParallelNode(definition, depth) { + if (definition.type !== "parallel") { + throw new Error(`expected node type of 'parallel' for parallel node at depth '${depth}'`); + } + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for parallel node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + definition.children.forEach((child) => validateNode(child, depth + 1)); + } + function validateLottoNode(definition, depth) { + if (definition.type !== "lotto") { + throw new Error(`expected node type of 'lotto' for lotto node at depth '${depth}'`); + } + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for lotto node at depth '${depth}'`); + } + if (typeof definition.weights !== "undefined") { + if (!Array.isArray(definition.weights) || definition.weights.length !== definition.children.length || definition.weights.filter((value) => !isInteger(value)).length || definition.weights.filter((value) => value < 0).length) { + throw new Error( + `expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '${depth}'` + ); + } + } + validateNodeAttributes(definition, depth); + definition.children.forEach((child) => validateNode(child, depth + 1)); + } + function createValidationFailureResult(errorMessage) { + return { succeeded: false, errorMessage }; + } + + // src/Lookup.ts + var Lookup = class { + static getFunc(name) { + return this.registeredFunctions[name]; + } + static setFunc(name, func) { + this.registeredFunctions[name] = func; + } + static getFuncInvoker(agent, name) { + const agentFunction = agent[name]; + if (agentFunction && typeof agentFunction === "function") { + return (args) => agentFunction.apply(agent, args); + } + if (this.registeredFunctions[name] && typeof this.registeredFunctions[name] === "function") { + const registeredFunction = this.registeredFunctions[name]; + return (args) => registeredFunction(agent, ...args); + } + return null; + } + static getSubtrees() { + return this.registeredSubtrees; + } + static setSubtree(name, subtree) { + this.registeredSubtrees[name] = subtree; + } + static remove(name) { + delete this.registeredFunctions[name]; + delete this.registeredSubtrees[name]; + } + static empty() { + this.registeredFunctions = {}; + this.registeredSubtrees = {}; + } + }; + __publicField(Lookup, "registeredFunctions", {}); + __publicField(Lookup, "registeredSubtrees", {}); + // src/attributes/guards/GuardUnsatisifedException.ts var GuardUnsatisifedException = class extends Error { constructor(source) { @@ -280,15 +1286,6 @@ var mistreevous = (() => { }; }; - // src/State.ts - var State = /* @__PURE__ */ ((State2) => { - State2["READY"] = "mistreevous.ready"; - State2["RUNNING"] = "mistreevous.running"; - State2["SUCCEEDED"] = "mistreevous.succeeded"; - State2["FAILED"] = "mistreevous.failed"; - return State2; - })(State || {}); - // src/nodes/Node.ts var Node = class { constructor(type, attributes, args) { @@ -308,7 +1305,7 @@ var mistreevous = (() => { getAttributes = () => this.attributes; getArguments = () => this.args; getAttribute(type) { - return this.getAttributes().filter((decorator) => decorator.getType().toUpperCase() === type.toUpperCase())[0] || null; + return this.getAttributes().filter((decorator) => decorator.type.toUpperCase() === type.toUpperCase())[0] || null; } getGuardAttributes = () => this.getAttributes().filter((decorator) => decorator.isGuard()); setGuardPath = (value) => this.guardPath = value; @@ -357,191 +1354,157 @@ var mistreevous = (() => { return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4(); } - // src/nodes/leaf/Leaf.ts - var Leaf = class extends Node { - isLeafNode = () => true; - }; - - // src/Lookup.ts - var Lookup = class { - static getFunc(name) { - return this.functionTable[name]; - } - static setFunc(name, func) { - this.functionTable[name] = func; + // src/nodes/composite/Composite.ts + var Composite = class extends Node { + constructor(type, attributes, children) { + super(type, attributes, []); + this.children = children; } - static getFuncInvoker(agent, name) { - const foundOnAgent = agent[name]; - if (foundOnAgent && typeof foundOnAgent === "function") { - return (args) => foundOnAgent.apply( - agent, - args.map((arg) => arg.value) - ); - } - if (this.functionTable[name] && typeof this.functionTable[name] === "function") { - return (args) => this.functionTable[name](agent, ...args.map((arg) => arg.value)); + isLeafNode = () => false; + getChildren = () => this.children; + reset = () => { + this.setState("mistreevous.ready" /* READY */); + this.getChildren().forEach((child) => child.reset()); + }; + abort = (agent) => { + if (!this.is("mistreevous.running" /* RUNNING */)) { + return; } - return null; - } - static getSubtree(name) { - return this.subtreeTable[name]; - } - static setSubtree(name, subtree) { - this.subtreeTable[name] = subtree; - } - static remove(name) { - delete this.functionTable[name]; - delete this.subtreeTable[name]; - } - static empty() { - this.functionTable = {}; - this.subtreeTable = {}; - } + this.getChildren().forEach((child) => child.abort(agent)); + this.reset(); + this.getAttribute("exit")?.callAgentFunction(agent, false, true); + }; }; - __publicField(Lookup, "functionTable", {}); - __publicField(Lookup, "subtreeTable", {}); - // src/nodes/leaf/Action.ts - var Action = class extends Leaf { - constructor(attributes, actionName, actionArguments) { - super("action", attributes, actionArguments); - this.actionName = actionName; - this.actionArguments = actionArguments; + // src/nodes/composite/Parallel.ts + var Parallel = class extends Composite { + constructor(attributes, children) { + super("parallel", attributes, children); } - isUsingUpdatePromise = false; - updatePromiseStateResult = null; onUpdate(agent, options) { - if (this.isUsingUpdatePromise) { - if (this.updatePromiseStateResult) { - this.setState(this.updatePromiseStateResult); + let succeededCount = 0; + let hasChildFailed = false; + for (const child of this.children) { + if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { + child.update(agent, options); + } + if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { + succeededCount++; + continue; + } + if (child.getState() === "mistreevous.failed" /* FAILED */) { + hasChildFailed = true; + break; + } + if (child.getState() !== "mistreevous.running" /* RUNNING */) { + throw new Error("child node was not in an expected state."); } - return; - } - const actionFuncInvoker = Lookup.getFuncInvoker(agent, this.actionName); - if (actionFuncInvoker === null) { - throw new Error( - `cannot update action node as the action '${this.actionName}' function is not defined on the agent and has not been registered` - ); } - const updateResult = actionFuncInvoker(this.actionArguments); - if (updateResult instanceof Promise) { - updateResult.then( - (result) => { - if (!this.isUsingUpdatePromise) { - return; - } - if (result !== "mistreevous.succeeded" /* SUCCEEDED */ && result !== "mistreevous.failed" /* FAILED */) { - throw new Error( - "action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned" - ); - } - this.updatePromiseStateResult = result; - }, - (reason) => { - if (!this.isUsingUpdatePromise) { - return; - } - throw new Error(reason); + if (hasChildFailed) { + this.setState("mistreevous.failed" /* FAILED */); + for (const child of this.children) { + if (child.getState() === "mistreevous.running" /* RUNNING */) { + child.abort(agent); } - ); - this.setState("mistreevous.running" /* RUNNING */); - this.isUsingUpdatePromise = true; + } } else { - this.validateUpdateResult(updateResult); - this.setState(updateResult || "mistreevous.running" /* RUNNING */); + this.setState(succeededCount === this.children.length ? "mistreevous.succeeded" /* SUCCEEDED */ : "mistreevous.running" /* RUNNING */); } } - getName = () => this.actionName; - reset = () => { - this.setState("mistreevous.ready" /* READY */); - this.isUsingUpdatePromise = false; - this.updatePromiseStateResult = null; - }; - validateUpdateResult = (result) => { - switch (result) { - case "mistreevous.succeeded" /* SUCCEEDED */: - case "mistreevous.failed" /* FAILED */: - case void 0: + getName = () => "PARALLEL"; + }; + + // src/nodes/composite/Selector.ts + var Selector = class extends Composite { + constructor(attributes, children) { + super("selector", attributes, children); + this.children = children; + } + onUpdate(agent, options) { + for (const child of this.children) { + if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { + child.update(agent, options); + } + if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { + this.setState("mistreevous.succeeded" /* SUCCEEDED */); + return; + } + if (child.getState() === "mistreevous.failed" /* FAILED */) { + if (this.children.indexOf(child) === this.children.length - 1) { + this.setState("mistreevous.failed" /* FAILED */); + return; + } else { + continue; + } + } + if (child.getState() === "mistreevous.running" /* RUNNING */) { + this.setState("mistreevous.running" /* RUNNING */); return; - default: - throw new Error( - `action '${this.actionName}' 'onUpdate' returned an invalid response, expected an optional State.SUCCEEDED or State.FAILED value to be returned` - ); + } + throw new Error("child node was not in an expected state."); } - }; + } + getName = () => "SELECTOR"; }; - // src/nodes/leaf/Condition.ts - var Condition = class extends Leaf { - constructor(attributes, conditionName, conditionArguments) { - super("condition", attributes, conditionArguments); - this.conditionName = conditionName; - this.conditionArguments = conditionArguments; + // src/nodes/composite/Sequence.ts + var Sequence = class extends Composite { + constructor(attributes, children) { + super("sequence", attributes, children); + this.children = children; } onUpdate(agent, options) { - const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.conditionName); - if (conditionFuncInvoker === null) { - throw new Error( - `cannot update condition node as the condition '${this.conditionName}' function is not defined on the agent and has not been registered` - ); + for (const child of this.children) { + if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { + child.update(agent, options); + } + if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { + if (this.children.indexOf(child) === this.children.length - 1) { + this.setState("mistreevous.succeeded" /* SUCCEEDED */); + return; + } else { + continue; + } + } + if (child.getState() === "mistreevous.failed" /* FAILED */) { + this.setState("mistreevous.failed" /* FAILED */); + return; + } + if (child.getState() === "mistreevous.running" /* RUNNING */) { + this.setState("mistreevous.running" /* RUNNING */); + return; + } + throw new Error("child node was not in an expected state."); } - this.setState(!!conditionFuncInvoker(this.conditionArguments) ? "mistreevous.succeeded" /* SUCCEEDED */ : "mistreevous.failed" /* FAILED */); } - getName = () => this.conditionName; + getName = () => "SEQUENCE"; }; - // src/nodes/leaf/Wait.ts - var Wait = class extends Leaf { - constructor(attributes, duration, durationMin, durationMax) { - super("wait", attributes, []); - this.duration = duration; - this.durationMin = durationMin; - this.durationMax = durationMax; + // src/nodes/composite/Lotto.ts + var import_lotto_draw = __toESM(require_dist()); + var Lotto = class extends Composite { + constructor(attributes, weights, children) { + super("lotto", attributes, children); + this.weights = weights; } - initialUpdateTime = 0; - totalDuration = null; - waitedDuration = 0; + selectedChild; onUpdate(agent, options) { if (this.is("mistreevous.ready" /* READY */)) { - this.initialUpdateTime = new Date().getTime(); - this.waitedDuration = 0; - if (this.duration !== null) { - this.totalDuration = this.duration; - } else if (this.durationMin !== null && this.durationMax !== null) { - const random = typeof options.random === "function" ? options.random : Math.random; - this.totalDuration = Math.floor( - random() * (this.durationMax - this.durationMin + 1) + this.durationMin - ); - } else { - this.totalDuration = null; - } - this.setState("mistreevous.running" /* RUNNING */); - } - if (this.totalDuration === null) { - return; + const lottoDraw = (0, import_lotto_draw.default)({ + random: options.random, + participants: this.children.map((child, index) => [child, this.weights?.[index] || 1]) + }); + this.selectedChild = lottoDraw.draw() || void 0; } - if (typeof options.getDeltaTime === "function") { - const deltaTime = options.getDeltaTime(); - if (typeof deltaTime !== "number" || isNaN(deltaTime)) { - throw new Error("The delta time must be a valid number and not NaN."); - } - this.waitedDuration += deltaTime * 1e3; - } else { - this.waitedDuration = new Date().getTime() - this.initialUpdateTime; + if (!this.selectedChild) { + throw new Error("failed to update lotto node as it has no active child"); } - if (this.waitedDuration >= this.totalDuration) { - this.setState("mistreevous.succeeded" /* SUCCEEDED */); + if (this.selectedChild.getState() === "mistreevous.ready" /* READY */ || this.selectedChild.getState() === "mistreevous.running" /* RUNNING */) { + this.selectedChild.update(agent, options); } + this.setState(this.selectedChild.getState()); } - getName = () => { - if (this.duration !== null) { - return `WAIT ${this.duration}ms`; - } else if (this.durationMin !== null && this.durationMax !== null) { - return `WAIT ${this.durationMin}ms-${this.durationMax}ms`; - } else { - return "WAIT"; - } - }; + getName = () => this.weights ? `LOTTO [${this.weights.join(",")}]` : "LOTTO"; }; // src/nodes/decorator/Decorator.ts @@ -566,18 +1529,54 @@ var mistreevous = (() => { }; }; - // src/nodes/decorator/Root.ts - var Root = class extends Decorator { + // src/nodes/decorator/Fail.ts + var Fail = class extends Decorator { constructor(attributes, child) { - super("root", attributes, child); + super("fail", attributes, child); } onUpdate(agent, options) { if (this.child.getState() === "mistreevous.ready" /* READY */ || this.child.getState() === "mistreevous.running" /* RUNNING */) { this.child.update(agent, options); } - this.setState(this.child.getState()); + switch (this.child.getState()) { + case "mistreevous.running" /* RUNNING */: + this.setState("mistreevous.running" /* RUNNING */); + break; + case "mistreevous.succeeded" /* SUCCEEDED */: + case "mistreevous.failed" /* FAILED */: + this.setState("mistreevous.failed" /* FAILED */); + break; + default: + this.setState("mistreevous.ready" /* READY */); + } } - getName = () => "ROOT"; + getName = () => "FAIL"; + }; + + // src/nodes/decorator/Flip.ts + var Flip = class extends Decorator { + constructor(attributes, child) { + super("flip", attributes, child); + } + onUpdate(agent, options) { + if (this.child.getState() === "mistreevous.ready" /* READY */ || this.child.getState() === "mistreevous.running" /* RUNNING */) { + this.child.update(agent, options); + } + switch (this.child.getState()) { + case "mistreevous.running" /* RUNNING */: + this.setState("mistreevous.running" /* RUNNING */); + break; + case "mistreevous.succeeded" /* SUCCEEDED */: + this.setState("mistreevous.failed" /* FAILED */); + break; + case "mistreevous.failed" /* FAILED */: + this.setState("mistreevous.succeeded" /* SUCCEEDED */); + break; + default: + this.setState("mistreevous.ready" /* READY */); + } + } + getName = () => "FLIP"; }; // src/nodes/decorator/Repeat.ts @@ -712,30 +1711,18 @@ var mistreevous = (() => { }; }; - // src/nodes/decorator/Flip.ts - var Flip = class extends Decorator { + // src/nodes/decorator/Root.ts + var Root = class extends Decorator { constructor(attributes, child) { - super("flip", attributes, child); + super("root", attributes, child); } onUpdate(agent, options) { if (this.child.getState() === "mistreevous.ready" /* READY */ || this.child.getState() === "mistreevous.running" /* RUNNING */) { this.child.update(agent, options); } - switch (this.child.getState()) { - case "mistreevous.running" /* RUNNING */: - this.setState("mistreevous.running" /* RUNNING */); - break; - case "mistreevous.succeeded" /* SUCCEEDED */: - this.setState("mistreevous.failed" /* FAILED */); - break; - case "mistreevous.failed" /* FAILED */: - this.setState("mistreevous.succeeded" /* SUCCEEDED */); - break; - default: - this.setState("mistreevous.ready" /* READY */); - } + this.setState(this.child.getState()); } - getName = () => "FLIP"; + getName = () => "ROOT"; }; // src/nodes/decorator/Succeed.ts @@ -762,183 +1749,189 @@ var mistreevous = (() => { getName = () => "SUCCEED"; }; - // src/nodes/decorator/Fail.ts - var Fail = class extends Decorator { - constructor(attributes, child) { - super("fail", attributes, child); - } - onUpdate(agent, options) { - if (this.child.getState() === "mistreevous.ready" /* READY */ || this.child.getState() === "mistreevous.running" /* RUNNING */) { - this.child.update(agent, options); - } - switch (this.child.getState()) { - case "mistreevous.running" /* RUNNING */: - this.setState("mistreevous.running" /* RUNNING */); - break; - case "mistreevous.succeeded" /* SUCCEEDED */: - case "mistreevous.failed" /* FAILED */: - this.setState("mistreevous.failed" /* FAILED */); - break; - default: - this.setState("mistreevous.ready" /* READY */); - } - } - getName = () => "FAIL"; - }; - - // src/nodes/composite/Lotto.ts - var import_lotto_draw = __toESM(require_dist()); - - // src/nodes/composite/Composite.ts - var Composite = class extends Node { - constructor(type, attributes, children) { - super(type, attributes, []); - this.children = children; - } - isLeafNode = () => false; - getChildren = () => this.children; - reset = () => { - this.setState("mistreevous.ready" /* READY */); - this.getChildren().forEach((child) => child.reset()); - }; - abort = (agent) => { - if (!this.is("mistreevous.running" /* RUNNING */)) { - return; - } - this.getChildren().forEach((child) => child.abort(agent)); - this.reset(); - this.getAttribute("exit")?.callAgentFunction(agent, false, true); - }; - }; - - // src/nodes/composite/Lotto.ts - var Lotto = class extends Composite { - constructor(attributes, tickets, children) { - super("lotto", attributes, children); - this.tickets = tickets; - } - selectedChild; - onUpdate(agent, options) { - if (this.is("mistreevous.ready" /* READY */)) { - const lottoDraw = (0, import_lotto_draw.default)({ - random: options.random, - participants: this.children.map((child, index) => [child, this.tickets[index] || 1]) - }); - this.selectedChild = lottoDraw.draw() || void 0; - } - if (!this.selectedChild) { - throw new Error("failed to update lotto node as it has no active child"); - } - if (this.selectedChild.getState() === "mistreevous.ready" /* READY */ || this.selectedChild.getState() === "mistreevous.running" /* RUNNING */) { - this.selectedChild.update(agent, options); - } - this.setState(this.selectedChild.getState()); - } - getName = () => this.tickets.length ? `LOTTO [${this.tickets.join(",")}]` : "LOTTO"; - }; - - // src/nodes/composite/Selector.ts - var Selector = class extends Composite { - constructor(attributes, children) { - super("selector", attributes, children); - this.children = children; + // src/nodes/leaf/Leaf.ts + var Leaf = class extends Node { + isLeafNode = () => true; + }; + + // src/nodes/leaf/Action.ts + var Action = class extends Leaf { + constructor(attributes, actionName, actionArguments) { + super("action", attributes, actionArguments); + this.actionName = actionName; + this.actionArguments = actionArguments; } + isUsingUpdatePromise = false; + updatePromiseResult = null; onUpdate(agent, options) { - for (const child of this.children) { - if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { - child.update(agent, options); - } - if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { - this.setState("mistreevous.succeeded" /* SUCCEEDED */); + if (this.isUsingUpdatePromise) { + if (!this.updatePromiseResult) { return; } - if (child.getState() === "mistreevous.failed" /* FAILED */) { - if (this.children.indexOf(child) === this.children.length - 1) { - this.setState("mistreevous.failed" /* FAILED */); - return; - } else { - continue; + const { isResolved, value } = this.updatePromiseResult; + if (isResolved) { + if (value !== "mistreevous.succeeded" /* SUCCEEDED */ && value !== "mistreevous.failed" /* FAILED */) { + throw new Error( + "action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned" + ); } - } - if (child.getState() === "mistreevous.running" /* RUNNING */) { - this.setState("mistreevous.running" /* RUNNING */); + this.setState(value); return; + } else { + throw new Error(`action function '${this.actionName}' promise rejected with '${value}'`); } - throw new Error("child node was not in an expected state."); + } + const actionFuncInvoker = Lookup.getFuncInvoker(agent, this.actionName); + if (actionFuncInvoker === null) { + throw new Error( + `cannot update action node as the action '${this.actionName}' function is not defined on the agent and has not been registered` + ); + } + let actionFunctionResult; + try { + actionFunctionResult = actionFuncInvoker(this.actionArguments); + } catch (error) { + if (error instanceof Error) { + throw new Error(`action function '${this.actionName}' threw: ${error.stack}`); + } else { + throw new Error(`action function '${this.actionName}' threw: ${error}`); + } + } + if (actionFunctionResult instanceof Promise) { + actionFunctionResult.then( + (result) => { + if (!this.isUsingUpdatePromise) { + return; + } + this.updatePromiseResult = { + isResolved: true, + value: result + }; + }, + (reason) => { + if (!this.isUsingUpdatePromise) { + return; + } + this.updatePromiseResult = { + isResolved: false, + value: reason + }; + } + ); + this.setState("mistreevous.running" /* RUNNING */); + this.isUsingUpdatePromise = true; + } else { + this.validateUpdateResult(actionFunctionResult); + this.setState(actionFunctionResult || "mistreevous.running" /* RUNNING */); } } - getName = () => "SELECTOR"; + getName = () => this.actionName; + reset = () => { + this.setState("mistreevous.ready" /* READY */); + this.isUsingUpdatePromise = false; + this.updatePromiseResult = null; + }; + validateUpdateResult = (result) => { + switch (result) { + case "mistreevous.succeeded" /* SUCCEEDED */: + case "mistreevous.failed" /* FAILED */: + case "mistreevous.running" /* RUNNING */: + case void 0: + return; + default: + throw new Error( + `expected action function '${this.actionName}' to return an optional State.SUCCEEDED or State.FAILED value but returned '${result}'` + ); + } + }; }; - // src/nodes/composite/Sequence.ts - var Sequence = class extends Composite { - constructor(attributes, children) { - super("sequence", attributes, children); - this.children = children; + // src/nodes/leaf/Condition.ts + var Condition = class extends Leaf { + constructor(attributes, conditionName, conditionArguments) { + super("condition", attributes, conditionArguments); + this.conditionName = conditionName; + this.conditionArguments = conditionArguments; } onUpdate(agent, options) { - for (const child of this.children) { - if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { - child.update(agent, options); - } - if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { - if (this.children.indexOf(child) === this.children.length - 1) { - this.setState("mistreevous.succeeded" /* SUCCEEDED */); - return; - } else { - continue; - } - } - if (child.getState() === "mistreevous.failed" /* FAILED */) { - this.setState("mistreevous.failed" /* FAILED */); - return; - } - if (child.getState() === "mistreevous.running" /* RUNNING */) { - this.setState("mistreevous.running" /* RUNNING */); - return; + const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.conditionName); + if (conditionFuncInvoker === null) { + throw new Error( + `cannot update condition node as the condition '${this.conditionName}' function is not defined on the agent and has not been registered` + ); + } + let conditionFunctionResult; + try { + conditionFunctionResult = conditionFuncInvoker(this.conditionArguments); + } catch (error) { + if (error instanceof Error) { + throw new Error(`condition function '${this.conditionName}' threw: ${error.stack}`); + } else { + throw new Error(`condition function '${this.conditionName}' threw: ${error}`); } - throw new Error("child node was not in an expected state."); } + if (typeof conditionFunctionResult !== "boolean") { + throw new Error( + `expected condition function '${this.conditionName}' to return a boolean but returned '${conditionFunctionResult}'` + ); + } + this.setState(!!conditionFunctionResult ? "mistreevous.succeeded" /* SUCCEEDED */ : "mistreevous.failed" /* FAILED */); } - getName = () => "SEQUENCE"; + getName = () => this.conditionName; }; - // src/nodes/composite/Parallel.ts - var Parallel = class extends Composite { - constructor(attributes, children) { - super("parallel", attributes, children); + // src/nodes/leaf/Wait.ts + var Wait = class extends Leaf { + constructor(attributes, duration, durationMin, durationMax) { + super("wait", attributes, []); + this.duration = duration; + this.durationMin = durationMin; + this.durationMax = durationMax; } + initialUpdateTime = 0; + totalDuration = null; + waitedDuration = 0; onUpdate(agent, options) { - let succeededCount = 0; - let hasChildFailed = false; - for (const child of this.children) { - if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { - child.update(agent, options); - } - if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { - succeededCount++; - continue; - } - if (child.getState() === "mistreevous.failed" /* FAILED */) { - hasChildFailed = true; - break; - } - if (child.getState() !== "mistreevous.running" /* RUNNING */) { - throw new Error("child node was not in an expected state."); + if (this.is("mistreevous.ready" /* READY */)) { + this.initialUpdateTime = new Date().getTime(); + this.waitedDuration = 0; + if (this.duration !== null) { + this.totalDuration = this.duration; + } else if (this.durationMin !== null && this.durationMax !== null) { + const random = typeof options.random === "function" ? options.random : Math.random; + this.totalDuration = Math.floor( + random() * (this.durationMax - this.durationMin + 1) + this.durationMin + ); + } else { + this.totalDuration = null; } + this.setState("mistreevous.running" /* RUNNING */); } - if (hasChildFailed) { - this.setState("mistreevous.failed" /* FAILED */); - for (const child of this.children) { - if (child.getState() === "mistreevous.running" /* RUNNING */) { - child.abort(agent); - } + if (this.totalDuration === null) { + return; + } + if (typeof options.getDeltaTime === "function") { + const deltaTime = options.getDeltaTime(); + if (typeof deltaTime !== "number" || isNaN(deltaTime)) { + throw new Error("The delta time must be a valid number and not NaN."); } + this.waitedDuration += deltaTime * 1e3; } else { - this.setState(succeededCount === this.children.length ? "mistreevous.succeeded" /* SUCCEEDED */ : "mistreevous.running" /* RUNNING */); + this.waitedDuration = new Date().getTime() - this.initialUpdateTime; + } + if (this.waitedDuration >= this.totalDuration) { + this.setState("mistreevous.succeeded" /* SUCCEEDED */); } } - getName = () => "PARALLEL"; + getName = () => { + if (this.duration !== null) { + return `WAIT ${this.duration}ms`; + } else if (this.durationMin !== null && this.durationMax !== null) { + return `WAIT ${this.durationMin}ms-${this.durationMax}ms`; + } else { + return "WAIT"; + } + }; }; // src/attributes/Attribute.ts @@ -947,8 +1940,6 @@ var mistreevous = (() => { this.type = type; this.args = args; } - getType = () => this.type; - getArguments = () => this.args; }; // src/attributes/guards/Guard.ts @@ -961,8 +1952,8 @@ var mistreevous = (() => { isGuard = () => true; getDetails() { return { - type: this.getType(), - args: this.getArguments(), + type: this.type, + args: this.args, condition: this.getCondition() }; } @@ -980,7 +1971,22 @@ var mistreevous = (() => { `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered` ); } - return !!conditionFuncInvoker(this.args); + let conditionFunctionResult; + try { + conditionFunctionResult = conditionFuncInvoker(this.args); + } catch (error) { + if (error instanceof Error) { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`); + } else { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`); + } + } + if (typeof conditionFunctionResult !== "boolean") { + throw new Error( + `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'` + ); + } + return conditionFunctionResult; }; }; @@ -996,7 +2002,22 @@ var mistreevous = (() => { `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered` ); } - return !!!conditionFuncInvoker(this.args); + let conditionFunctionResult; + try { + conditionFunctionResult = conditionFuncInvoker(this.args); + } catch (error) { + if (error instanceof Error) { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`); + } else { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`); + } + } + if (typeof conditionFunctionResult !== "boolean") { + throw new Error( + `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'` + ); + } + return !conditionFunctionResult; }; }; @@ -1010,8 +2031,8 @@ var mistreevous = (() => { isGuard = () => false; getDetails() { return { - type: this.getType(), - args: this.getArguments(), + type: this.type, + args: this.args, functionName: this.getFunctionName() }; } @@ -1033,22 +2054,6 @@ var mistreevous = (() => { }; }; - // src/attributes/callbacks/Exit.ts - var Exit = class extends Callback { - constructor(functionName, args) { - super("exit", args, functionName); - } - callAgentFunction = (agent, isSuccess, isAborted) => { - const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName()); - if (callbackFuncInvoker === null) { - throw new Error( - `cannot call exit function '${this.getFunctionName()}' as is not defined on the agent and has not been registered` - ); - } - callbackFuncInvoker([{ value: { succeeded: isSuccess, aborted: isAborted } }, ...this.args]); - }; - }; - // src/attributes/callbacks/Step.ts var Step = class extends Callback { constructor(functionName, args) { @@ -1065,668 +2070,171 @@ var mistreevous = (() => { }; }; - // src/RootAstNodesBuilder.ts - var AttributeFactories = { - WHILE: (condition, attributeArguments) => new While(condition, attributeArguments), - UNTIL: (condition, attributeArguments) => new Until(condition, attributeArguments), - ENTRY: (functionName, attributeArguments) => new Entry(functionName, attributeArguments), - EXIT: (functionName, attributeArguments) => new Exit(functionName, attributeArguments), - STEP: (functionName, attributeArguments) => new Step(functionName, attributeArguments) - }; - var ASTNodeFactories = { - ROOT: () => ({ - type: "root", - attributes: [], - name: null, - children: [], - validate(depth) { - if (depth > 1) { - throw new Error("a root node cannot be the child of another node"); - } - if (this.children.length !== 1) { - throw new Error("a root node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Root( - this.attributes, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) - ); - } - }), - BRANCH: () => ({ - type: "branch", - branchName: "", - validate() { - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - const targetRootNode = namedRootNodeProvider(this.branchName); - if (visitedBranches.indexOf(this.branchName) !== -1) { - throw new Error(`circular dependency found in branch node references for branch '${this.branchName}'`); - } - if (targetRootNode) { - return targetRootNode.createNodeInstance(namedRootNodeProvider, visitedBranches.concat(this.branchName)).getChildren()[0]; - } else { - throw new Error(`branch references root node '${this.branchName}' which has not been defined`); - } - } - }), - SELECTOR: () => ({ - type: "selector", - attributes: [], - children: [], - validate() { - if (this.children.length < 1) { - throw new Error("a selector node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Selector( - this.attributes, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - SEQUENCE: () => ({ - type: "sequence", - attributes: [], - children: [], - validate() { - if (this.children.length < 1) { - throw new Error("a sequence node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Sequence( - this.attributes, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - PARALLEL: () => ({ - type: "parallel", - attributes: [], - children: [], - validate() { - if (this.children.length < 1) { - throw new Error("a parallel node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Parallel( - this.attributes, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - LOTTO: () => ({ - type: "lotto", - attributes: [], - children: [], - tickets: [], - validate() { - if (this.children.length < 1) { - throw new Error("a lotto node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Lotto( - this.attributes, - this.tickets, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) + // src/attributes/callbacks/Exit.ts + var Exit = class extends Callback { + constructor(functionName, args) { + super("exit", args, functionName); + } + callAgentFunction = (agent, isSuccess, isAborted) => { + const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName()); + if (callbackFuncInvoker === null) { + throw new Error( + `cannot call exit function '${this.getFunctionName()}' as is not defined on the agent and has not been registered` ); } - }), - REPEAT: () => ({ - type: "repeat", - attributes: [], - iterations: null, - iterationsMin: null, - iterationsMax: null, - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a repeat node must have a single child"); - } - if (this.iterations !== null) { - if (this.iterations < 0) { - throw new Error("a repeat node must have a positive number of iterations if defined"); - } - } else if (this.iterationsMin !== null && this.iterationsMax !== null) { - if (this.iterationsMin < 0 || this.iterationsMax < 0) { - throw new Error( - "a repeat node must have a positive minimum and maximum iteration count if defined" - ); - } - if (this.iterationsMin > this.iterationsMax) { - throw new Error( - "a repeat node must not have a minimum iteration count that exceeds the maximum iteration count" - ); - } - } else { + callbackFuncInvoker([{ succeeded: isSuccess, aborted: isAborted }, ...this.args]); + }; + }; + + // src/BehaviourTreeBuilder.ts + var MAIN_ROOT_NODE_KEY = Symbol("__root__"); + function buildRootNode(definition) { + const rootNodeDefinitionMap = createRootNodeDefinitionMap(definition); + validateBranchSubtreeLinks( + [rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], ...Object.values(rootNodeDefinitionMap)], + true + ); + const rootNode = nodeFactory(rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], rootNodeDefinitionMap); + applyLeafNodeGuardPaths(rootNode); + return rootNode; + } + function nodeFactory(definition, rootNodeDefinitionMap) { + const attributes = nodeAttributesFactory(definition); + switch (definition.type) { + case "root": + return new Root(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + case "repeat": + let iterations = null; + let iterationsMin = null; + let iterationsMax = null; + if (Array.isArray(definition.iterations)) { + iterationsMin = definition.iterations[0]; + iterationsMax = definition.iterations[1]; + } else if (isInteger(definition.iterations)) { + iterations = definition.iterations; } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { return new Repeat( - this.attributes, - this.iterations, - this.iterationsMin, - this.iterationsMax, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + attributes, + iterations, + iterationsMin, + iterationsMax, + nodeFactory(definition.child, rootNodeDefinitionMap) ); - } - }), - RETRY: () => ({ - type: "retry", - attributes: [], - attempts: null, - attemptsMin: null, - attemptsMax: null, - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a retry node must have a single child"); - } - if (this.attempts !== null) { - if (this.attempts < 0) { - throw new Error("a retry node must have a positive number of attempts if defined"); - } - } else if (this.attemptsMin !== null && this.attemptsMax !== null) { - if (this.attemptsMin < 0 || this.attemptsMax < 0) { - throw new Error("a retry node must have a positive minimum and maximum attempt count if defined"); - } - if (this.attemptsMin > this.attemptsMax) { - throw new Error( - "a retry node must not have a minimum attempt count that exceeds the maximum attempt count" - ); - } - } else { + case "retry": + let attempts = null; + let attemptsMin = null; + let attemptsMax = null; + if (Array.isArray(definition.attempts)) { + attemptsMin = definition.attempts[0]; + attemptsMax = definition.attempts[1]; + } else if (isInteger(definition.attempts)) { + attempts = definition.attempts; } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { return new Retry( - this.attributes, - this.attempts, - this.attemptsMin, - this.attemptsMax, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + attributes, + attempts, + attemptsMin, + attemptsMax, + nodeFactory(definition.child, rootNodeDefinitionMap) ); - } - }), - FLIP: () => ({ - type: "flip", - attributes: [], - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a flip node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Flip( - this.attributes, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + case "flip": + return new Flip(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + case "succeed": + return new Succeed(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + case "fail": + return new Fail(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + case "sequence": + return new Sequence( + attributes, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) ); - } - }), - SUCCEED: () => ({ - type: "succeed", - attributes: [], - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a succeed node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Succeed( - this.attributes, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + case "selector": + return new Selector( + attributes, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) ); - } - }), - FAIL: () => ({ - type: "fail", - attributes: [], - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a fail node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Fail( - this.attributes, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + case "parallel": + return new Parallel( + attributes, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) ); - } - }), - WAIT: () => ({ - type: "wait", - attributes: [], - duration: null, - durationMin: null, - durationMax: null, - validate() { - if (this.duration !== null) { - if (this.duration < 0) { - throw new Error("a wait node must have a positive duration"); - } - } else if (this.durationMin !== null && this.durationMax !== null) { - if (this.durationMin < 0 || this.durationMax < 0) { - throw new Error("a wait node must have a positive minimum and maximum duration"); - } - if (this.durationMin > this.durationMax) { - throw new Error("a wait node must not have a minimum duration that exceeds the maximum duration"); - } - } else { - } - }, - createNodeInstance() { - return new Wait(this.attributes, this.duration, this.durationMin, this.durationMax); - } - }), - ACTION: () => ({ - type: "action", - attributes: [], - actionName: "", - actionArguments: [], - validate() { - }, - createNodeInstance() { - return new Action(this.attributes, this.actionName, this.actionArguments); - } - }), - CONDITION: () => ({ - type: "condition", - attributes: [], - conditionName: "", - conditionArguments: [], - validate() { - }, - createNodeInstance() { - return new Condition(this.attributes, this.conditionName, this.conditionArguments); - } - }) - }; - function buildRootASTNodes(definition) { - const { placeholders, processedDefinition } = substituteStringLiterals(definition); - const tokens = parseTokensFromDefinition(processedDefinition); - if (tokens.length < 3) { - throw new Error("invalid token count"); - } - if (tokens.filter((token) => token === "{").length !== tokens.filter((token) => token === "}").length) { - throw new Error("scope character mismatch"); - } - const stack = [[]]; - const rootScope = stack[0]; - while (tokens.length) { - const token = tokens.shift(); - const currentScope = stack[stack.length - 1]; - switch (token.toUpperCase()) { - case "ROOT": { - const node = ASTNodeFactories.ROOT(); - rootScope.push(node); - if (tokens[0] === "[") { - const rootArguments = getArguments(tokens, placeholders); - if (rootArguments.length === 1 && rootArguments[0].type === "identifier") { - node.name = rootArguments[0].value; - } else { - throw new Error("expected single root name argument"); - } - } - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "BRANCH": { - const node = ASTNodeFactories.BRANCH(); - currentScope.push(node); - if (tokens[0] !== "[") { - throw new Error("expected single branch name argument"); - } - const branchArguments = getArguments(tokens, placeholders); - if (branchArguments.length === 1 && branchArguments[0].type === "identifier") { - node.branchName = branchArguments[0].value; - } else { - throw new Error("expected single branch name argument"); - } - break; - } - case "SELECTOR": { - const node = ASTNodeFactories.SELECTOR(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "SEQUENCE": { - const node = ASTNodeFactories.SEQUENCE(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "PARALLEL": { - const node = ASTNodeFactories.PARALLEL(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "LOTTO": { - const node = ASTNodeFactories.LOTTO(); - currentScope.push(node); - if (tokens[0] === "[") { - node.tickets = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "lotto node ticket counts must be integer values" - ).map((argument) => argument.value); - } - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "CONDITION": { - const node = ASTNodeFactories.CONDITION(); - currentScope.push(node); - if (tokens[0] !== "[") { - throw new Error("expected condition name identifier argument"); - } - const conditionArguments = getArguments(tokens, placeholders); - if (conditionArguments.length && conditionArguments[0].type === "identifier") { - node.conditionName = conditionArguments.shift().value; - } else { - throw new Error("expected condition name identifier argument"); - } - conditionArguments.filter((arg) => arg.type === "identifier").forEach((arg) => { - throw new Error( - "invalid condition node argument value '" + arg.value + "', must be string, number, boolean or null" - ); - }); - node.conditionArguments = conditionArguments; - node.attributes = getAttributes(tokens, placeholders); - break; - } - case "FLIP": { - const node = ASTNodeFactories.FLIP(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "SUCCEED": { - const node = ASTNodeFactories.SUCCEED(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "FAIL": { - const node = ASTNodeFactories.FAIL(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "WAIT": { - const node = ASTNodeFactories.WAIT(); - currentScope.push(node); - if (tokens[0] === "[") { - const nodeArguments = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "wait node durations must be integer values" - ).map((argument) => argument.value); - if (nodeArguments.length === 1) { - node.duration = nodeArguments[0]; - } else if (nodeArguments.length === 2) { - node.durationMin = nodeArguments[0]; - node.durationMax = nodeArguments[1]; - } else if (nodeArguments.length > 2) { - throw new Error("invalid number of wait node duration arguments defined"); - } - } - node.attributes = getAttributes(tokens, placeholders); - break; - } - case "REPEAT": { - const node = ASTNodeFactories.REPEAT(); - currentScope.push(node); - if (tokens[0] === "[") { - const nodeArguments = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "repeat node iteration counts must be integer values" - ).map((argument) => argument.value); - if (nodeArguments.length === 1) { - node.iterations = nodeArguments[0]; - } else if (nodeArguments.length === 2) { - node.iterationsMin = nodeArguments[0]; - node.iterationsMax = nodeArguments[1]; - } else { - throw new Error("invalid number of repeat node iteration count arguments defined"); - } - } - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "RETRY": { - const node = ASTNodeFactories.RETRY(); - currentScope.push(node); - if (tokens[0] === "[") { - const nodeArguments = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "retry node attempt counts must be integer values" - ).map((argument) => argument.value); - if (nodeArguments.length === 1) { - node.attempts = nodeArguments[0]; - } else if (nodeArguments.length === 2) { - node.attemptsMin = nodeArguments[0]; - node.attemptsMax = nodeArguments[1]; - } else { - throw new Error("invalid number of retry node attempt count arguments defined"); - } - } - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "ACTION": { - const node = ASTNodeFactories.ACTION(); - currentScope.push(node); - if (tokens[0] !== "[") { - throw new Error("expected action name identifier argument"); - } - const actionArguments = getArguments(tokens, placeholders); - if (actionArguments.length && actionArguments[0].type === "identifier") { - node.actionName = actionArguments.shift().value; - } else { - throw new Error("expected action name identifier argument"); - } - actionArguments.filter((arg) => arg.type === "identifier").forEach((arg) => { - throw new Error( - "invalid action node argument value '" + arg.value + "', must be string, number, boolean or null" - ); - }); - node.actionArguments = actionArguments; - node.attributes = getAttributes(tokens, placeholders); - break; - } - case "}": { - stack.pop(); - break; - } - default: { - throw new Error(`unexpected token '${token}'`); - } - } + case "lotto": + return new Lotto( + attributes, + definition.weights, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) + ); + case "branch": + return nodeFactory(rootNodeDefinitionMap[definition.ref].child, rootNodeDefinitionMap); + case "action": + return new Action(attributes, definition.call, definition.args || []); + case "condition": + return new Condition(attributes, definition.call, definition.args || []); + case "wait": + let duration = null; + let durationMin = null; + let durationMax = null; + if (Array.isArray(definition.duration)) { + durationMin = definition.duration[0]; + durationMax = definition.duration[1]; + } else if (isInteger(definition.duration)) { + duration = definition.duration; + } + return new Wait(attributes, duration, durationMin, durationMax); } - const validateASTNode = (node, depth) => { - node.validate(depth); - (node.children || []).forEach((child) => validateASTNode(child, depth + 1)); - }; - validateASTNode( - { - children: stack[0], - validate() { - if (this.children.length === 0) { - throw new Error("expected root node to have been defined"); - } - for (const definitionLevelNode of this.children) { - if (definitionLevelNode.type !== "root") { - throw new Error("expected root node at base of definition"); - } - } - if (this.children.filter((definitionLevelNode) => definitionLevelNode.name === null).length !== 1) { - throw new Error("expected single unnamed root node at base of definition to act as main root"); - } - const rootNodeNames = []; - for (const definitionLevelNode of this.children) { - if (rootNodeNames.indexOf(definitionLevelNode.name) !== -1) { - throw new Error(`multiple root nodes found with duplicate name '${definitionLevelNode.name}'`); - } else { - rootNodeNames.push(definitionLevelNode.name); - } - } - } - }, - 0 - ); - return stack[0]; } - function popAndCheck(tokens, expected) { - const popped = tokens.shift(); - if (popped === void 0) { - throw new Error("unexpected end of definition"); + function nodeAttributesFactory(definition) { + const attributes = []; + if (definition.while) { + attributes.push(new While(definition.while.call, definition.while.args ?? [])); } - if (expected !== void 0) { - var tokenMatchesExpectation = [].concat(expected).some((item) => popped.toUpperCase() === item.toUpperCase()); - if (!tokenMatchesExpectation) { - const expectationString = [].concat(expected).map((item) => "'" + item + "'").join(" or "); - throw new Error(`unexpected token found. Expected '${expectationString}' but got '${popped}'`); - } + if (definition.until) { + attributes.push(new Until(definition.until.call, definition.until.args ?? [])); } - return popped; - } - function getArguments(tokens, stringArgumentPlaceholders, argumentValidator, validationFailedMessage) { - const closer = popAndCheck(tokens, ["[", "("]) === "[" ? "]" : ")"; - const argumentListTokens = []; - const argumentList = []; - while (tokens.length && tokens[0] !== closer) { - argumentListTokens.push(tokens.shift()); + if (definition.entry) { + attributes.push(new Entry(definition.entry.call, definition.entry.args ?? [])); } - argumentListTokens.forEach((token, index) => { - const shouldBeArgumentToken = !(index & 1); - if (shouldBeArgumentToken) { - const argumentDefinition = getArgumentDefinition(token, stringArgumentPlaceholders); - if (argumentValidator && !argumentValidator(argumentDefinition)) { - throw new Error(validationFailedMessage); - } - argumentList.push(argumentDefinition); - } else { - if (token !== ",") { - throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`); - } - } - }); - popAndCheck(tokens, closer); - return argumentList; - } - function getArgumentDefinition(token, stringArgumentPlaceholders) { - if (token === "null") { - return { - value: null, - type: "null" - }; + if (definition.step) { + attributes.push(new Step(definition.step.call, definition.step.args ?? [])); } - if (token === "true" || token === "false") { - return { - value: token === "true", - type: "boolean" - }; + if (definition.exit) { + attributes.push(new Exit(definition.exit.call, definition.exit.args ?? [])); } - if (!isNaN(token)) { - return { - value: parseFloat(token), - isInteger: parseFloat(token) === parseInt(token, 10), - type: "number" - }; + return attributes; + } + function createRootNodeDefinitionMap(definition) { + const rootNodeMap = {}; + for (const [name, rootNodeDefinition] of Object.entries(Lookup.getSubtrees())) { + rootNodeMap[name] = { ...rootNodeDefinition, id: name }; } - if (token.match(/^@@\d+@@$/g)) { - return { - value: stringArgumentPlaceholders[token].replace('\\"', '"'), - type: "string" - }; + for (const rootNodeDefinition of definition) { + rootNodeMap[rootNodeDefinition.id ?? MAIN_ROOT_NODE_KEY] = rootNodeDefinition; } - return { - value: token, - type: "identifier" - }; + return rootNodeMap; } - function getAttributes(tokens, stringArgumentPlaceholders) { - const attributes = []; - const attributesFound = []; - let attributeFactory = AttributeFactories[(tokens[0] || "").toUpperCase()]; - while (attributeFactory) { - if (attributesFound.indexOf(tokens[0].toUpperCase()) !== -1) { - throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`); - } - attributesFound.push(tokens.shift().toUpperCase()); - const attributeArguments = getArguments(tokens, stringArgumentPlaceholders); - if (attributeArguments.length === 0 || attributeArguments[0].type !== "identifier") { - throw new Error("expected agent function name identifier argument for attribute"); + function applyLeafNodeGuardPaths(root) { + const nodePaths = []; + const findLeafNodes = (path, node) => { + path = path.concat(node); + if (node.isLeafNode()) { + nodePaths.push(path); + } else { + node.getChildren().forEach((child) => findLeafNodes(path, child)); } - const attributeFunctionName = attributeArguments.shift(); - attributeArguments.filter((arg) => arg.type === "identifier").forEach((arg) => { - throw new Error( - "invalid attribute argument value '" + arg.value + "', must be string, number, boolean or null" + }; + findLeafNodes([], root); + nodePaths.forEach((path) => { + for (let depth = 0; depth < path.length; depth++) { + const currentNode = path[depth]; + if (currentNode.hasGuardPath()) { + continue; + } + const guardPath = new GuardPath( + path.slice(0, depth + 1).map((node) => ({ node, guards: node.getGuardAttributes() })).filter((details) => details.guards.length > 0) ); - }); - attributes.push(attributeFactory(attributeFunctionName.value, attributeArguments)); - attributeFactory = AttributeFactories[(tokens[0] || "").toUpperCase()]; - } - return attributes; - } - function substituteStringLiterals(definition) { - const placeholders = {}; - const processedDefinition = definition.replace(/\"(\\.|[^"\\])*\"/g, (match) => { - var strippedMatch = match.substring(1, match.length - 1); - var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch); - if (!placeholder) { - placeholder = `@@${Object.keys(placeholders).length}@@`; - placeholders[placeholder] = strippedMatch; + currentNode.setGuardPath(guardPath); } - return placeholder; }); - return { placeholders, processedDefinition }; - } - function parseTokensFromDefinition(definition) { - definition = definition.replace(/\(/g, " ( "); - definition = definition.replace(/\)/g, " ) "); - definition = definition.replace(/\{/g, " { "); - definition = definition.replace(/\}/g, " } "); - definition = definition.replace(/\]/g, " ] "); - definition = definition.replace(/\[/g, " [ "); - definition = definition.replace(/\,/g, " , "); - return definition.replace(/\s+/g, " ").trim().split(" "); } // src/BehaviourTree.ts @@ -1734,33 +2242,46 @@ var mistreevous = (() => { constructor(definition, agent, options = {}) { this.agent = agent; this.options = options; - if (typeof definition !== "string") { - throw new Error("the tree definition must be a string"); + if (isNullOrUndefined(definition)) { + throw new Error("tree definition not defined"); } if (typeof agent !== "object" || agent === null) { - throw new Error("the agent must be defined and not null"); + throw new Error("the agent must be an object and not null"); + } + const { succeeded, errorMessage, json } = validateDefinition(definition); + if (!succeeded) { + throw new Error(`invalid definition: ${errorMessage}`); + } + if (!json) { + throw new Error( + "expected json definition to be returned as part of successful definition validation response" + ); + } + try { + this._rootNode = buildRootNode(json); + } catch (exception) { + throw new Error(`error building tree: ${exception.message}`); } - this.rootNode = BehaviourTree.createRootNode(definition); } - rootNode; + _rootNode; isRunning() { - return this.rootNode.getState() === "mistreevous.running" /* RUNNING */; + return this._rootNode.getState() === "mistreevous.running" /* RUNNING */; } getState() { - return this.rootNode.getState(); + return this._rootNode.getState(); } step() { - if (this.rootNode.getState() === "mistreevous.succeeded" /* SUCCEEDED */ || this.rootNode.getState() === "mistreevous.failed" /* FAILED */) { - this.rootNode.reset(); + if (this._rootNode.getState() === "mistreevous.succeeded" /* SUCCEEDED */ || this._rootNode.getState() === "mistreevous.failed" /* FAILED */) { + this._rootNode.reset(); } try { - this.rootNode.update(this.agent, this.options); + this._rootNode.update(this.agent, this.options); } catch (exception) { throw new Error(`error stepping tree: ${exception.message}`); } } reset() { - this.rootNode.reset(); + this._rootNode.reset(); } getFlattenedNodeDetails() { const flattenedTreeNodes = []; @@ -1781,25 +2302,45 @@ var mistreevous = (() => { node.getChildren().forEach((child) => processNode(child, node.getUid())); } }; - processNode(this.rootNode, null); + processNode(this._rootNode, null); return flattenedTreeNodes; } static register(name, value) { if (typeof value === "function") { Lookup.setFunc(name, value); - } else if (typeof value === "string") { - let rootASTNodes; + return; + } + if (typeof value === "string") { + let rootNodeDefinitions; try { - rootASTNodes = buildRootASTNodes(value); + rootNodeDefinitions = convertMDSLToJSON(value); } catch (exception) { - throw new Error(`error registering definition: ${exception.message}`); + throw new Error(`error registering definition, invalid MDSL: ${exception.message}`); } - if (rootASTNodes.length != 1 || rootASTNodes[0].name !== null) { + if (rootNodeDefinitions.length != 1 || typeof rootNodeDefinitions[0].id !== "undefined") { throw new Error("error registering definition: expected a single unnamed root node"); } - Lookup.setSubtree(name, rootASTNodes[0]); + try { + const { succeeded, errorMessage } = validateJSONDefinition(rootNodeDefinitions[0]); + if (!succeeded) { + throw new Error(errorMessage); + } + } catch (exception) { + throw new Error(`error registering definition: ${exception.message}`); + } + Lookup.setSubtree(name, rootNodeDefinitions[0]); + } else if (typeof value === "object" && !Array.isArray(value)) { + try { + const { succeeded, errorMessage } = validateJSONDefinition(value); + if (!succeeded) { + throw new Error(errorMessage); + } + } catch (exception) { + throw new Error(`error registering definition: ${exception.message}`); + } + Lookup.setSubtree(name, value); } else { - throw new Error("unexpected value, expected string definition or function"); + throw new Error("unexpected value, expected string mdsl definition, root node json definition or function"); } } static unregister(name) { @@ -1808,48 +2349,6 @@ var mistreevous = (() => { static unregisterAll() { Lookup.empty(); } - static createRootNode(definition) { - try { - const rootASTNodes = buildRootASTNodes(definition); - const mainRootNodeKey = Symbol("__root__"); - const rootNodeMap = {}; - for (const rootASTNode of rootASTNodes) { - rootNodeMap[rootASTNode.name === null ? mainRootNodeKey : rootASTNode.name] = rootASTNode; - } - const rootNode = rootNodeMap[mainRootNodeKey].createNodeInstance( - (name) => rootNodeMap[name] ? rootNodeMap[name] : Lookup.getSubtree(name), - [] - ); - BehaviourTree.applyLeafNodeGuardPaths(rootNode); - return rootNode; - } catch (exception) { - throw new Error(`error parsing tree: ${exception.message}`); - } - } - static applyLeafNodeGuardPaths(rootNode) { - const nodePaths = []; - const findLeafNodes = (path, node) => { - path = path.concat(node); - if (node.isLeafNode()) { - nodePaths.push(path); - } else { - node.getChildren().forEach((child) => findLeafNodes(path, child)); - } - }; - findLeafNodes([], rootNode); - nodePaths.forEach((path) => { - for (let depth = 0; depth < path.length; depth++) { - const currentNode = path[depth]; - if (currentNode.hasGuardPath()) { - continue; - } - const guardPath = new GuardPath( - path.slice(0, depth + 1).map((node) => ({ node, guards: node.getGuardAttributes() })).filter((details) => details.guards.length > 0) - ); - currentNode.setGuardPath(guardPath); - } - }); - } }; return __toCommonJS(src_exports); })(); diff --git a/dist/bundle.js.map b/dist/bundle.js.map index 60beb25..d72f854 100644 --- a/dist/bundle.js.map +++ b/dist/bundle.js.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../node_modules/lotto-draw/dist/Participant.js", "../node_modules/lotto-draw/dist/Utilities.js", "../node_modules/lotto-draw/dist/Lotto.js", "../node_modules/lotto-draw/dist/createLotto.js", "../node_modules/lotto-draw/dist/index.js", "../src/index.ts", "../src/attributes/guards/GuardUnsatisifedException.ts", "../src/attributes/guards/GuardPath.ts", "../src/State.ts", "../src/nodes/Node.ts", "../src/nodes/leaf/Leaf.ts", "../src/Lookup.ts", "../src/nodes/leaf/Action.ts", "../src/nodes/leaf/Condition.ts", "../src/nodes/leaf/Wait.ts", "../src/nodes/decorator/Decorator.ts", "../src/nodes/decorator/Root.ts", "../src/nodes/decorator/Repeat.ts", "../src/nodes/decorator/Retry.ts", "../src/nodes/decorator/Flip.ts", "../src/nodes/decorator/Succeed.ts", "../src/nodes/decorator/Fail.ts", "../src/nodes/composite/Lotto.ts", "../src/nodes/composite/Composite.ts", "../src/nodes/composite/Selector.ts", "../src/nodes/composite/Sequence.ts", "../src/nodes/composite/Parallel.ts", "../src/attributes/Attribute.ts", "../src/attributes/guards/Guard.ts", "../src/attributes/guards/While.ts", "../src/attributes/guards/Until.ts", "../src/attributes/callbacks/Callback.ts", "../src/attributes/callbacks/Entry.ts", "../src/attributes/callbacks/Exit.ts", "../src/attributes/callbacks/Step.ts", "../src/RootAstNodesBuilder.ts", "../src/BehaviourTree.ts"], - "sourcesContent": ["\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.Participant = void 0;\r\n/**\r\n * A participant that holds a number of tickets.\r\n */\r\nvar Participant = /** @class */ (function () {\r\n /**\r\n * Creates an instance of the Participant class.\r\n * @param participant The actual participant.\r\n * @param tickets The number of tickets held by the participant.\r\n */\r\n function Participant(participant, tickets) {\r\n if (tickets === void 0) { tickets = 1; }\r\n this._participant = participant;\r\n this._tickets = tickets;\r\n }\r\n Object.defineProperty(Participant.prototype, \"participant\", {\r\n /** Gets the actual participant. */\r\n get: function () {\r\n return this._participant;\r\n },\r\n enumerable: false,\r\n configurable: true\r\n });\r\n Object.defineProperty(Participant.prototype, \"tickets\", {\r\n /** Gets or sets the number of tickets held by the participant. */\r\n get: function () {\r\n return this._tickets;\r\n },\r\n set: function (value) {\r\n this._tickets = value;\r\n },\r\n enumerable: false,\r\n configurable: true\r\n });\r\n return Participant;\r\n}());\r\nexports.Participant = Participant;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.isNaturalNumber = exports.isNullOrUndefined = void 0;\r\n/**\r\n * Gets whether the value provided is null or undefined.\r\n * @param value The value to check.\r\n * @returns Whether the value provided is null or undefined.\r\n */\r\nfunction isNullOrUndefined(value) {\r\n return value === null || value === undefined;\r\n}\r\nexports.isNullOrUndefined = isNullOrUndefined;\r\n/**\r\n * Gets whether the value provided is a natural number.\r\n * @param value The value to check.\r\n * @returns Whether the value provided is a natural number.\r\n */\r\nfunction isNaturalNumber(value) {\r\n return typeof value === \"number\" && value >= 1 && Math.floor(value) === value;\r\n}\r\nexports.isNaturalNumber = isNaturalNumber;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.Lotto = void 0;\r\nvar Participant_1 = require(\"./Participant\");\r\nvar Utilities_1 = require(\"./Utilities\");\r\n/**\r\n * Represents a lotto consisting of a number of pickable ticket-holding participants.\r\n */\r\nvar Lotto = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of Lotto.\r\n * @param customRandom The custom RNG to use in place of Math.random().\r\n */\r\n function Lotto(customRandom) {\r\n /** The array of participants that are holding tickets in the lotto. */\r\n this._participants = [];\r\n this._customRandom = customRandom;\r\n }\r\n /**\r\n * Adds a participant with the specified number of tickets, or adds to the participant ticket count if the participant already holds tickets.\r\n * @param participant The participant to add or to increase the ticket count for if they already hold tickets.\r\n * @param tickets The number of tickets, defaults to 1.\r\n * @returns The Lotto instance.\r\n */\r\n Lotto.prototype.add = function (participant, tickets) {\r\n if (tickets === void 0) { tickets = 1; }\r\n // Check that we have a valid ticket count.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n // Check whether this participant has already been added.\r\n var existingParticipant = this._participants.find(function (part) { return part.participant === participant; });\r\n if (existingParticipant) {\r\n // The participant has already been added to the lotto so just add to their ticket count.\r\n existingParticipant.tickets += tickets;\r\n }\r\n else {\r\n // The participant is not part of the lotto so we should add them.\r\n this._participants.push(new Participant_1.Participant(participant, tickets));\r\n }\r\n return this;\r\n };\r\n /**\r\n * Removes the specified number of tickets for the given participant from the draw, or all tickets if a ticket number is not defined.\r\n * @param participant The participant to remove tickets for.\r\n * @param tickets The number of tickets to remove, or undefined if all tickets are to be removed.\r\n * @returns The Lotto instance.\r\n */\r\n Lotto.prototype.remove = function (participant, tickets) {\r\n // Attempt to get the existing participant.\r\n var existingParticipant = this._participants.find(function (part) { return part.participant === participant; });\r\n // There is nothing to do if the specified participant isn't even part of the lotto.\r\n if (!existingParticipant) {\r\n return this;\r\n }\r\n // Check whether a tickets value was given.\r\n if (tickets !== undefined) {\r\n // Check that we have a valid ticket count.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n existingParticipant.tickets -= tickets;\r\n // If the participant no longer holds any tickets then they should be removed.\r\n if (existingParticipant.tickets < 1) {\r\n this._participants = this._participants.filter(function (part) { return part !== existingParticipant; });\r\n }\r\n }\r\n else {\r\n // We are removing all tickets for the participant so just remove them from the lotto.\r\n this._participants = this._participants.filter(function (part) { return part !== existingParticipant; });\r\n }\r\n return this;\r\n };\r\n /**\r\n * Draw a winning ticket and return the participant that holds the ticket.\r\n * @param options The draw options.\r\n * @returns The participant that holds the winning ticket.\r\n */\r\n Lotto.prototype.draw = function (options) {\r\n if (options === void 0) { options = {}; }\r\n // If we have no participants then just return null.\r\n if (this._participants.length === 0) {\r\n return null;\r\n }\r\n var redrawable = (0, Utilities_1.isNullOrUndefined)(options.redrawable) ? true : options.redrawable;\r\n var pickable = [];\r\n this._participants.forEach(function (_a) {\r\n var participant = _a.participant, tickets = _a.tickets;\r\n for (var ticketCount = 0; ticketCount < tickets; ticketCount++) {\r\n pickable.push(participant);\r\n }\r\n });\r\n var random;\r\n // We need a random floating-point number between 0 (inclusive) and 1 to scale up to pick our winner.\r\n // If a custom random function exists then we should use that or fall back to Math.random().\r\n if (this._customRandom) {\r\n // Call our custom random function to get a random floating-point number.\r\n random = this._customRandom();\r\n // Verify that the result of calling our custom random function is a number between 0 (inclusive) and 1.\r\n if (typeof random !== \"number\" || random < 0 || random >= 1) {\r\n throw new Error(\"the 'random' function provided did not return a number between 0 (inclusive) and 1\");\r\n }\r\n }\r\n else {\r\n // No custom random function was defined so just use good ol' Math.random().\r\n random = Math.random();\r\n }\r\n // Pick a winning participant.\r\n var winner = pickable[Math.floor(random * pickable.length)];\r\n // If the ticket isn't redrawable then we should remove a ticket from the winning participants ticket count.\r\n if (!redrawable) {\r\n this.remove(winner, 1);\r\n }\r\n // Return the winning participant.\r\n return winner;\r\n };\r\n /**\r\n * Draws multiple winning tickets and return an array of the participants that hold the winning tickets.\r\n * @param tickets The number of winning tickets to draw.\r\n * @param options The draw multiple options.\r\n * @returns An array of the participants that hold the winning tickets.\r\n */\r\n Lotto.prototype.drawMultiple = function (tickets, options) {\r\n if (options === void 0) { options = {}; }\r\n var uniqueResults = (0, Utilities_1.isNullOrUndefined)(options.unique) ? false : options.unique;\r\n // Handle cases where the user has asked for zero tickets (no idea why they would do this be we should trust them).\r\n if (tickets === 0) {\r\n return [];\r\n }\r\n // Now that we know out tickets value is not zero we should check that it is a valid natural number.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n var result = [];\r\n // Keep drawing tickets until we either reach the number of required tickets or we simply run out of tickets to draw.\r\n // We can run out of tickets to draw if 'options.redrawable' is explicity 'false' or we just had no participants when 'drawMultiple' was called.\r\n while (result.length < tickets && this._participants.length > 0) {\r\n result.push(this.draw(options));\r\n }\r\n // If the 'unique' draw option is set then we need to remove duplicates from the result list.\r\n if (uniqueResults) {\r\n // Create an array to store our unique results.\r\n var unique = [];\r\n // Iterate over all of our participants (with potential duplicates) and populate our array of unique values.\r\n for (var _i = 0, result_1 = result; _i < result_1.length; _i++) {\r\n var participant = result_1[_i];\r\n if (unique.indexOf(participant) === -1) {\r\n unique.push(participant);\r\n }\r\n }\r\n result = unique;\r\n }\r\n return result;\r\n };\r\n return Lotto;\r\n}());\r\nexports.Lotto = Lotto;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.createLotto = void 0;\r\nvar Lotto_1 = require(\"./Lotto\");\r\n/**\r\n * A function that creates and returns a Lotto instance.\r\n * @param participantsOrOptions An array of initial participants or options relating to the creation of a Lotto instance.\r\n * @returns A new Lotto instance.\r\n */\r\nfunction createLotto(participantsOrOptions) {\r\n // If no initial participants or lotto options were provided as an argument then we can just return a new lotto instance now.\r\n if (!participantsOrOptions) {\r\n return new Lotto_1.Lotto();\r\n }\r\n // Check whether we were provided with an array of initial participants or a lotto options object.\r\n if (Array.isArray(participantsOrOptions)) {\r\n // We are dealing with a pre-defined array of participants.\r\n var participants = participantsOrOptions;\r\n var lotto_1 = new Lotto_1.Lotto();\r\n // If the lotto participants have been defined upfront then we will need to add them all to our lotto instance now.\r\n participants.forEach(function (_a) {\r\n var participant = _a[0], tokens = _a[1];\r\n return lotto_1.add(participant, tokens);\r\n });\r\n // Return the Lotto instance.\r\n return lotto_1;\r\n }\r\n else {\r\n // We are dealing with some lotto options.\r\n var random = participantsOrOptions.random, participants = participantsOrOptions.participants;\r\n // Create a Lotto instance passing the custom RNG function to use in place of Math.random() (which could be undefined).\r\n var lotto_2 = new Lotto_1.Lotto(random);\r\n // If the lotto participants have been defined upfront as part of the options then we will need to add them all to our lotto instance now.\r\n if (participants) {\r\n participants.forEach(function (_a) {\r\n var participant = _a[0], tokens = _a[1];\r\n return lotto_2.add(participant, tokens);\r\n });\r\n }\r\n // Return the Lotto instance.\r\n return lotto_2;\r\n }\r\n}\r\nexports.createLotto = createLotto;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nvar createLotto_1 = require(\"./createLotto\");\r\nexports.default = createLotto_1.createLotto;\r\n", "import { BehaviourTree, FlattenedTreeNode } from \"./BehaviourTree\";\nimport State from \"./State\";\n\nexport { BehaviourTree, State };\nexport type { FlattenedTreeNode };\n", "import Node from \"../../nodes/Node\";\n\n/**\n * An exception thrown when evaluating node guard path conditions and a conditions fails.\n */\nexport default class GuardUnsatisifedException extends Error {\n /**\n * @param source The node at which a guard condition failed.\n */\n constructor(private source: Node) {\n super(\"A guard path condition has failed\");\n }\n\n /**\n * Gets whether the specified node is the node at which a guard condition failed.\n * @param node The node to check against the source node.\n * @returns Whether the specified node is the node at which a guard condition failed.\n */\n isSourceNode = (node: Node) => node === this.source;\n}\n", "import { Agent } from \"../../Agent\";\nimport Guard from \"./Guard\";\nimport Node from \"../../nodes/Node\";\nimport GuardUnsatisifedException from \"./GuardUnsatisifedException\";\n\nexport type GuardPathPart = {\n node: Node;\n guards: Guard[];\n};\n\n/**\n * Represents a path of node guards along a root-to-leaf tree path.\n */\nexport default class GuardPath {\n /**\n * @param nodes An array of objects defining a node instance -> guard link, ordered by node depth.\n */\n constructor(private nodes: GuardPathPart[]) {}\n\n /**\n * Evaluate guard conditions for all guards in the tree path, moving outwards from the root.\n * @param agent The agent, required for guard evaluation.\n * @returns An evaluation results object.\n */\n evaluate = (agent: Agent) => {\n // We need to evaluate guard conditions for nodes up the tree, moving outwards from the root.\n for (const details of this.nodes) {\n // There can be multiple guards per node.\n for (const guard of details.guards) {\n // Check whether the guard condition passes, and throw an exception if not.\n if (!guard.isSatisfied(agent)) {\n throw new GuardUnsatisifedException(details.node);\n }\n }\n }\n };\n}\n", "/**\n * Enumeration of node state types.\n */\nexport enum State {\n READY = \"mistreevous.ready\",\n RUNNING = \"mistreevous.running\",\n SUCCEEDED = \"mistreevous.succeeded\",\n FAILED = \"mistreevous.failed\"\n}\n\nexport { State as default };\n\nexport type CompleteState = State.SUCCEEDED | State.FAILED;\nexport type AnyState = State.READY | State.RUNNING | CompleteState;\n", "import { Agent } from \"../Agent\";\nimport Attribute from \"../attributes/Attribute\";\nimport Entry from \"../attributes/callbacks/Entry\";\nimport Exit from \"../attributes/callbacks/Exit\";\nimport Step from \"../attributes/callbacks/Step\";\nimport Guard from \"../attributes/guards/Guard\";\nimport GuardPath from \"../attributes/guards/GuardPath\";\nimport GuardUnsatisifedException from \"../attributes/guards/GuardUnsatisifedException\";\nimport { BehaviourTreeOptions } from \"../BehaviourTreeOptions\";\nimport { AnyArgument } from \"../RootAstNodesBuilder\";\nimport State, { AnyState } from \"../State\";\nimport Leaf from \"./leaf/Leaf\";\n\n/**\n * A base node.\n */\nexport default abstract class Node {\n /**\n * The node uid.\n */\n private readonly uid: string = createNodeUid();\n /**\n * The node state.\n */\n private state: AnyState = State.READY;\n /**\n * The guard path to evaluate as part of a node update.\n */\n private guardPath: GuardPath | undefined;\n\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param args The node argument definitions.\n */\n constructor(private type: string, private attributes: Attribute[], private args: AnyArgument[]) {}\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected abstract onUpdate(agent: Agent, options: BehaviourTreeOptions): void;\n\n /**\n * Gets the name of the node.\n */\n public abstract getName(): string;\n\n /**\n * Gets whether this node is a leaf node.\n */\n public abstract isLeafNode: () => this is Leaf;\n\n /**\n * Gets/Sets the state of the node.\n */\n getState = (): AnyState => this.state;\n setState = (value: AnyState): void => {\n this.state = value;\n };\n\n /**\n * Gets the unique id of the node.\n */\n getUid = () => this.uid;\n\n /**\n * Gets the type of the node.\n */\n getType = () => this.type;\n\n /**\n * Gets the node attributes.\n */\n getAttributes = () => this.attributes;\n\n /**\n * Gets the node arguments.\n */\n getArguments = () => this.args;\n\n /**\n * Gets the node attribute with the specified type, or null if it does not exist.\n */\n getAttribute(type: \"entry\" | \"ENTRY\"): Entry;\n getAttribute(type: \"exit\" | \"EXIT\"): Exit;\n getAttribute(type: \"step\" | \"STEP\"): Step;\n getAttribute(type: string): Attribute {\n return (\n this.getAttributes().filter((decorator) => decorator.getType().toUpperCase() === type.toUpperCase())[0] ||\n null\n );\n }\n\n /**\n * Gets the node attributes.\n */\n getGuardAttributes = (): Guard[] => this.getAttributes().filter((decorator) => decorator.isGuard()) as Guard[];\n\n /**\n * Sets the guard path to evaluate as part of a node update.\n */\n setGuardPath = (value: GuardPath) => (this.guardPath = value);\n\n /**\n * Gets whether a guard path is assigned to this node.\n */\n hasGuardPath = () => !!this.guardPath;\n\n /**\n * Gets whether this node is in the specified state.\n * @param value The value to compare to the node state.\n */\n public is(value: AnyState): boolean {\n return this.state === value;\n }\n\n /**\n * Reset the state of the node.\n */\n public reset(): void {\n this.setState(State.READY);\n }\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n public abort(agent: Agent): void {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n }\n\n /**\n * Update the node.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n * @returns The result of the update.\n */\n public update(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is already in a 'SUCCEEDED' or 'FAILED' state then there is nothing to do.\n if (this.is(State.SUCCEEDED) || this.is(State.FAILED)) {\n return;\n }\n\n try {\n // Evaluate all of the guard path conditions for the current tree path.\n this.guardPath!.evaluate(agent);\n\n // If this node is in the READY state then call the ENTRY for this node if it exists.\n if (this.is(State.READY)) {\n this.getAttribute(\"entry\")?.callAgentFunction(agent);\n }\n\n this.getAttribute(\"step\")?.callAgentFunction(agent);\n\n // Do the actual update.\n this.onUpdate(agent, options);\n\n // If this node is now in a 'SUCCEEDED' or 'FAILED' state then call the EXIT for this node if it exists.\n if (this.is(State.SUCCEEDED) || this.is(State.FAILED)) {\n this.getAttribute(\"exit\")?.callAgentFunction(agent, this.is(State.SUCCEEDED), false);\n }\n } catch (error) {\n // If the error is a GuardUnsatisfiedException then we need to determine if this node is the source.\n if (error instanceof GuardUnsatisifedException && error.isSourceNode(this)) {\n // Abort the current node.\n this.abort(agent);\n\n // Any node that is the source of an abort will be a failed node.\n this.setState(State.FAILED);\n } else {\n throw error;\n }\n }\n }\n}\n\n/**\n * Create a randomly generated node uid.\n * @returns A randomly generated node uid.\n */\nfunction createNodeUid(): string {\n var S4 = function () {\n return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);\n };\n return S4() + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + S4() + S4();\n}\n", "import Node from \"../Node\";\n\n/**\n * A leaf node.\n */\nexport default abstract class Leaf extends Node {\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => true;\n}\n", "import { ActionResult, Agent, ExitFunctionArg, FunctionArg, GlobalFunction } from \"./Agent\";\nimport { AnyArgument, RootAstNode } from \"./RootAstNodesBuilder\";\n\n// Exit callbacks receive their own special type of argument.\n// There's probably stricter ways to represent this but it feels overly complex right now.\ntype ExitResultArg = { value: ExitFunctionArg };\nexport type AnyExitArgument = AnyArgument | ExitResultArg;\n\nexport type InvokerFunction = (args: AnyExitArgument[]) => ActionResult;\n\n/**\n * A singleton used to store and lookup registered functions and subtrees.\n */\nexport default class Lookup {\n /**\n * The object holding any registered functions keyed on function name.\n */\n private static functionTable: { [key: string]: GlobalFunction } = {};\n /**\n * The object holding any registered sub-trees keyed on tree name.\n */\n private static subtreeTable: { [key: string]: RootAstNode } = {};\n\n /**\n * Gets the function with the specified name.\n * @param name The name of the function.\n * @returns The function with the specified name.\n */\n public static getFunc(name: string): GlobalFunction {\n return this.functionTable[name];\n }\n\n /**\n * Sets the function with the specified name for later lookup.\n * @param name The name of the function.\n * @param func The function.\n */\n public static setFunc(name: string, func: GlobalFunction): void {\n this.functionTable[name] = func;\n }\n\n /**\n * Gets the function invoker for the specified agent and function name.\n * If a function with the specified name exists on the agent object then it will\n * be returned, otherwise we will then check the registered functions for a match.\n * @param agent The agent instance that this behaviour tree is modelling behaviour for.\n * @param name The function name.\n * @returns The function invoker for the specified agent and function name.\n */\n static getFuncInvoker(agent: Agent, name: string): InvokerFunction | null {\n // Check whether the agent contains the specified function.\n const foundOnAgent = agent[name];\n if (foundOnAgent && typeof foundOnAgent === \"function\") {\n return (args: AnyExitArgument[]): boolean | ActionResult =>\n foundOnAgent.apply(\n agent,\n args.map((arg) => arg.value)\n );\n }\n\n // The agent does not contain the specified function but it may have been registered at some point.\n if (this.functionTable[name] && typeof this.functionTable[name] === \"function\") {\n return (args: AnyExitArgument[]) => this.functionTable[name](agent, ...args.map((arg) => arg.value));\n }\n\n // We have no function to invoke.\n return null;\n }\n\n /**\n * Gets the subtree with the specified name.\n * @param name The name of the subtree.\n * @returns The subtree with the specified name.\n */\n static getSubtree(name: string): RootAstNode {\n return this.subtreeTable[name];\n }\n\n /**\n * Sets the subtree with the specified name for later lookup.\n * @param name The name of the subtree.\n * @param subtree The subtree.\n */\n static setSubtree(name: string, subtree: RootAstNode) {\n this.subtreeTable[name] = subtree;\n }\n\n /**\n * Removes the registered function or subtree with the specified name.\n * @param name The name of the registered function or subtree.\n */\n static remove(name: string) {\n delete this.functionTable[name];\n delete this.subtreeTable[name];\n }\n\n /**\n * Remove all registered functions and subtrees.\n */\n static empty() {\n this.functionTable = {};\n this.subtreeTable = {};\n }\n}\n", "import Leaf from \"./Leaf\";\nimport State, { CompleteState } from \"../../State\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * An Action leaf node.\n * This represents an immediate or ongoing state of behaviour.\n */\nexport default class Action extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param actionName The action name.\n * @param actionArguments The array of action argument definitions.\n */\n constructor(attributes: Attribute[], private actionName: string, private actionArguments: AnyArgument[]) {\n super(\"action\", attributes, actionArguments);\n }\n\n /**\n * Whether there is a pending update promise.\n */\n private isUsingUpdatePromise = false;\n\n /**\n * The finished state result of an update promise.\n */\n private updatePromiseStateResult: CompleteState | null = null;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the result of this action depends on an update promise then there is nothing to do until\n // it resolves, unless there has been a value set as a result of the update promise resolving.\n if (this.isUsingUpdatePromise) {\n // Check whether the update promise has resolved with a state value.\n if (this.updatePromiseStateResult) {\n // Set the state of this node to match the state returned by the promise.\n this.setState(this.updatePromiseStateResult);\n }\n\n return;\n }\n\n // Attempt to get the invoker for the action function.\n const actionFuncInvoker = Lookup.getFuncInvoker(agent, this.actionName);\n\n // The action function should be defined.\n if (actionFuncInvoker === null) {\n throw new Error(\n `cannot update action node as the action '${this.actionName}' function is not defined on the agent and has not been registered`\n );\n }\n\n // Call the action function, the result of which may be:\n // - The finished state of this action node.\n // - A promise to return a finished node state.\n // - Undefined if the node should remain in the running state.\n const updateResult = actionFuncInvoker(this.actionArguments) as CompleteState | Promise;\n\n if (updateResult instanceof Promise) {\n updateResult.then(\n (result) => {\n // If 'isUpdatePromisePending' is null then the promise was cleared as it was resolving, probably via an abort of reset.\n if (!this.isUsingUpdatePromise) {\n return;\n }\n\n // Check to make sure the result is a valid finished state.\n if (result !== State.SUCCEEDED && result !== State.FAILED) {\n throw new Error(\n \"action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned\"\n );\n }\n\n // Set pending update promise state result to be processed on next update.\n this.updatePromiseStateResult = result;\n },\n (reason) => {\n // If 'isUpdatePromisePending' is null then the promise was cleared as it was resolving, probably via an abort of reset.\n if (!this.isUsingUpdatePromise) {\n return;\n }\n\n // Just throw whatever was returned as the rejection argument.\n throw new Error(reason);\n }\n );\n\n // This node will be in the 'RUNNING' state until the update promise resolves.\n this.setState(State.RUNNING);\n\n // We are now waiting for the promise returned by the use to resolve before we know what state this node is in.\n this.isUsingUpdatePromise = true;\n } else {\n // Validate the returned value.\n this.validateUpdateResult(updateResult);\n\n // Set the state of this node, this may be undefined, which just means that the node is still in the 'RUNNING' state.\n this.setState(updateResult || State.RUNNING);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => this.actionName;\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // There is no longer an update promise that we care about.\n this.isUsingUpdatePromise = false;\n this.updatePromiseStateResult = null;\n };\n\n /**\n * Validate the result of an update function call.\n * @param result The result of an update function call.\n */\n private validateUpdateResult = (result: CompleteState | boolean) => {\n switch (result) {\n case State.SUCCEEDED:\n case State.FAILED:\n case undefined:\n return;\n default:\n throw new Error(\n `action '${this.actionName}' 'onUpdate' returned an invalid response, expected an optional State.SUCCEEDED or State.FAILED value to be returned`\n );\n }\n };\n}\n", "import Leaf from \"./Leaf\";\nimport State from \"../../State\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Condition leaf node.\n * This will succeed or fail immediately based on an agent predicate, without moving to the 'RUNNING' state.\n */\nexport default class Condition extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param conditionName The name of the condition function.\n * @param conditionArguments The array of condition argument definitions.\n */\n constructor(attributes: Attribute[], private conditionName: string, private conditionArguments: AnyArgument[]) {\n super(\"condition\", attributes, conditionArguments);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.conditionName);\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot update condition node as the condition '${this.conditionName}' function is not defined on the agent and has not been registered`\n );\n }\n\n // Call the condition function to determine the state of this node.\n this.setState(!!conditionFuncInvoker(this.conditionArguments) ? State.SUCCEEDED : State.FAILED);\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => this.conditionName;\n}\n", "import Leaf from \"./Leaf\";\nimport State from \"../../State\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { Agent } from \"../../Agent\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A WAIT node.\n * The state of this node will change to SUCCEEDED after a duration of time\n */\nexport default class Wait extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param duration The duration that this node will wait to succeed in milliseconds.\n * @param durationMin The minimum possible duration in milliseconds that this node will wait to succeed.\n * @param durationMax The maximum possible duration in milliseconds that this node will wait to succeed.\n */\n constructor(\n attributes: Attribute[],\n private duration: number | null,\n private durationMin: number | null,\n private durationMax: number | null\n ) {\n super(\"wait\", attributes, []);\n }\n\n /**\n * The time in milliseconds at which this node was first updated.\n */\n private initialUpdateTime: number = 0;\n\n /**\n * The total duration in milliseconds that this node will be waiting for.\n */\n private totalDuration: number | null = null;\n\n /**\n * The duration in milliseconds that this node has been waiting for.\n */\n private waitedDuration: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to set the initial update time.\n if (this.is(State.READY)) {\n // Set the initial update time.\n this.initialUpdateTime = new Date().getTime();\n\n // Set the initial waited duration.\n this.waitedDuration = 0;\n\n // Are we dealing with an explicit duration or will we be randomly picking a duration between the min and max duration.\n if (this.duration !== null) {\n this.totalDuration = this.duration;\n } else if (this.durationMin !== null && this.durationMax !== null) {\n // We will be picking a random duration between a min and max duration, if the optional 'random' behaviour tree\n // function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random duration between a min and max duration.\n this.totalDuration = Math.floor(\n random() * (this.durationMax - this.durationMin + 1) + this.durationMin\n );\n } else {\n this.totalDuration = null;\n }\n\n // The node is now running until we finish waiting.\n this.setState(State.RUNNING);\n }\n\n // If we have no total duration then this wait node will wait indefinitely until it is aborted.\n if (this.totalDuration === null) {\n return;\n }\n\n // If we have a 'getDeltaTime' function defined as part of our options then we will use it to figure out how long we have waited for.\n if (typeof options.getDeltaTime === \"function\") {\n // Get the delta time.\n const deltaTime = options.getDeltaTime();\n\n // Our delta time must be a valid number and cannot be NaN.\n if (typeof deltaTime !== \"number\" || isNaN(deltaTime)) {\n throw new Error(\"The delta time must be a valid number and not NaN.\");\n }\n\n // Update the amount of time that this node has been waiting for based on the delta time.\n this.waitedDuration += deltaTime * 1000;\n } else {\n // We are not using a delta time, so we will just work out hom much time has passed since the first update.\n this.waitedDuration = new Date().getTime() - this.initialUpdateTime;\n }\n\n // Have we waited long enough?\n if (this.waitedDuration >= this.totalDuration) {\n // We have finished waiting!\n this.setState(State.SUCCEEDED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.duration !== null) {\n return `WAIT ${this.duration}ms`;\n } else if (this.durationMin !== null && this.durationMax !== null) {\n return `WAIT ${this.durationMin}ms-${this.durationMax}ms`;\n } else {\n return \"WAIT\";\n }\n };\n}\n", "import Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A decorator node that wraps a single child node.\n */\nexport default abstract class Decorator extends Node {\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(type: string, attributes: Attribute[], protected child: Node) {\n super(type, attributes, []);\n }\n\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => false;\n\n /**\n * Gets the children of this node.\n */\n getChildren = () => [this.child];\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the state of the child node.\n this.child.reset();\n };\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n abort = (agent: Agent) => {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Abort the child node.\n this.child.abort(agent);\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Root node.\n * The root node will have a single child.\n */\nexport default class Root extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"root\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n // Update the child of this node.\n this.child.update(agent, options);\n }\n\n // The state of the root node is the state of its child.\n this.setState(this.child.getState());\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"ROOT\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A REPEAT node.\n * The node has a single child which can have:\n * -- A number of iterations for which to repeat the child node.\n * -- An infinite repeat loop if neither an iteration count or a condition function is defined.\n * The REPEAT node will stop and have a 'FAILED' state if its child is ever in a 'FAILED' state after an update.\n * The REPEAT node will attempt to move on to the next iteration if its child is ever in a 'SUCCEEDED' state.\n */\nexport default class Repeat extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param iterations The number of iterations to repeat the child node.\n * @param iterationsMin The minimum possible number of iterations to repeat the child node.\n * @param iterationsMax The maximum possible number of iterations to repeat the child node.\n * @param child The child node.\n */\n constructor(\n attributes: Attribute[],\n private iterations: number | null,\n private iterationsMin: number | null,\n private iterationsMax: number | null,\n child: Node\n ) {\n super(\"repeat\", attributes, child);\n }\n\n /**\n * The number of target iterations to make.\n */\n private targetIterationCount: number | null = null;\n\n /**\n * The current iteration count.\n */\n private currentIterationCount: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to reset the child and the target iteration count.\n if (this.is(State.READY)) {\n // Reset the child node.\n this.child.reset();\n\n // Reset the current iteration count.\n this.currentIterationCount = 0;\n\n // Set the target iteration count.\n this.setTargetIterationCount(options);\n }\n\n // Do a check to see if we can iterate. If we can then this node will move into the 'RUNNING' state.\n // If we cannot iterate then we have hit our target iteration count, which means that the node has succeeded.\n if (this.canIterate()) {\n // This node is in the running state and can do its initial iteration.\n this.setState(State.RUNNING);\n\n // We may have already completed an iteration, meaning that the child node will be in the SUCCEEDED state.\n // If this is the case then we will have to reset the child node now.\n if (this.child.getState() === State.SUCCEEDED) {\n this.child.reset();\n }\n\n // Update the child of this node.\n this.child.update(agent, options);\n\n // If the child moved into the FAILED state when we updated it then there is nothing left to do and this node has also failed.\n // If it has moved into the SUCCEEDED state then we have completed the current iteration.\n if (this.child.getState() === State.FAILED) {\n // The child has failed, meaning that this node has failed.\n this.setState(State.FAILED);\n\n return;\n } else if (this.child.getState() === State.SUCCEEDED) {\n // We have completed an iteration.\n this.currentIterationCount += 1;\n }\n } else {\n // This node is in the 'SUCCEEDED' state as we cannot iterate any more.\n this.setState(State.SUCCEEDED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.iterations !== null) {\n return `REPEAT ${this.iterations}x`;\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n return `REPEAT ${this.iterationsMin}x-${this.iterationsMax}x`;\n } else {\n return \"REPEAT\";\n }\n };\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the current iteration count.\n this.currentIterationCount = 0;\n\n // Reset the child node.\n this.child.reset();\n };\n\n /**\n * Gets whether an iteration can be made.\n * @returns Whether an iteration can be made.\n */\n private canIterate = () => {\n if (this.targetIterationCount !== null) {\n // We can iterate as long as we have not reached our target iteration count.\n return this.currentIterationCount < this.targetIterationCount;\n }\n\n // If neither an iteration count or a condition function were defined then we can iterate indefinitely.\n return true;\n };\n\n /**\n * Sets the target iteration count.\n * @param options The behaviour tree options object.\n */\n private setTargetIterationCount = (options: BehaviourTreeOptions) => {\n // Are we dealing with an explicit iteration count or will we be randomly picking a iteration count between the min and max iteration count.\n if (this.iterations !== null) {\n this.targetIterationCount = this.iterations;\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n // We will be picking a random iteration count between a min and max iteration count, if the optional 'random'\n // behaviour tree function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random iteration count between a min and max iteration count.\n this.targetIterationCount = Math.floor(\n random() * (this.iterationsMax - this.iterationsMin + 1) + this.iterationsMin\n );\n } else {\n this.targetIterationCount = null;\n }\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A RETRY node.\n * The node has a single child which can have:\n * -- A number of iterations for which to repeat the child node.\n * -- An infinite repeat loop if neither an iteration count or a condition function is defined.\n * The RETRY node will stop and have a 'SUCCEEDED' state if its child is ever in a 'SUCCEEDED' state after an update.\n * The RETRY node will attempt to move on to the next iteration if its child is ever in a 'FAILED' state.\n */\nexport default class Retry extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param attempts The number of attempts to retry the child node.\n * @param attemptsMin The minimum possible number of attempts to retry the child node.\n * @param attemptsMax The maximum possible number of attempts to retry the child node.\n * @param child The child node.\n */\n constructor(\n attributes: Attribute[],\n private attempts: number | null,\n private attemptsMin: number | null,\n private attemptsMax: number | null,\n child: Node\n ) {\n super(\"retry\", attributes, child);\n }\n\n /**\n * The number of target attempts to make.\n */\n private targetAttemptCount: number | null = null;\n\n /**\n * The current attempt count.\n */\n private currentAttemptCount: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to reset the child and the target attempt count.\n if (this.is(State.READY)) {\n // Reset the child node.\n this.child.reset();\n\n // Reset the current attempt count.\n this.currentAttemptCount = 0;\n\n // Set the target attempt count.\n this.setTargetAttemptCount(options);\n }\n\n // Do a check to see if we can attempt. If we can then this node will move into the 'RUNNING' state.\n // If we cannot attempt then we have hit our target attempt count, which means that the node has succeeded.\n if (this.canAttempt()) {\n // This node is in the running state and can do its initial attempt.\n this.setState(State.RUNNING);\n\n // We may have already completed an attempt, meaning that the child node will be in the FAILED state.\n // If this is the case then we will have to reset the child node now.\n if (this.child.getState() === State.FAILED) {\n this.child.reset();\n }\n\n // Update the child of this node.\n this.child.update(agent, options);\n\n // If the child moved into the SUCCEEDED state when we updated it then there is nothing left to do and this node has also succeeded.\n // If it has moved into the FAILED state then we have completed the current attempt.\n if (this.child.getState() === State.SUCCEEDED) {\n // The child has succeeded, meaning that this node has succeeded.\n this.setState(State.SUCCEEDED);\n\n return;\n } else if (this.child.getState() === State.FAILED) {\n // We have completed an attempt.\n this.currentAttemptCount += 1;\n }\n } else {\n // This node is in the 'FAILED' state as we cannot iterate any more.\n this.setState(State.FAILED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.attempts !== null) {\n return `RETRY ${this.attempts}x`;\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n return `RETRY ${this.attemptsMin}x-${this.attemptsMax}x`;\n } else {\n return \"RETRY\";\n }\n };\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the current attempt count.\n this.currentAttemptCount = 0;\n\n // Reset the child node.\n this.child.reset();\n };\n\n /**\n * Gets whether an attempt can be made.\n * @returns Whether an attempt can be made.\n */\n canAttempt = () => {\n if (this.targetAttemptCount !== null) {\n // We can attempt as long as we have not reached our target attempt count.\n return this.currentAttemptCount < this.targetAttemptCount;\n }\n\n // If neither an attempt count or a condition function were defined then we can attempt indefinitely.\n return true;\n };\n\n /**\n * Sets the target attempt count.\n * @param options The behaviour tree options object.\n */\n setTargetAttemptCount = (options: BehaviourTreeOptions) => {\n // Are we dealing with an explicit attempt count or will we be randomly picking an attempt count between the min and max attempt count.\n if (this.attempts !== null) {\n this.targetAttemptCount = this.attempts;\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n // We will be picking a random attempt count between a min and max attempt count, if the optional 'random'\n // behaviour tree function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random attempt count between a min and max attempt count.\n this.targetAttemptCount = Math.floor(\n random() * (this.attemptsMax - this.attemptsMin + 1) + this.attemptsMin\n );\n } else {\n this.targetAttemptCount = null;\n }\n };\n}\n", "import Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport Node from \"../Node\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Flip node.\n * This node wraps a single child and will flip the state of the child state.\n */\nexport default class Flip extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"flip\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n this.setState(State.FAILED);\n break;\n\n case State.FAILED:\n this.setState(State.SUCCEEDED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"FLIP\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Succeed node.\n * This node wraps a single child and will always move to the 'SUCCEEDED' state when the child moves to a 'SUCCEEDED' or 'FAILED' state.\n */\nexport default class Succeed extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"succeed\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n case State.FAILED:\n this.setState(State.SUCCEEDED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SUCCEED\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Fail node.\n * This node wraps a single child and will always move to the 'FAILED' state when the child moves to a 'SUCCEEDED' or 'FAILED' state.\n */\nexport default class Fail extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"fail\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n case State.FAILED:\n this.setState(State.FAILED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"FAIL\";\n}\n", "import createLotto from \"lotto-draw\";\n\nimport Node from \"../Node\";\nimport Composite from \"./Composite\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A LOTTO node.\n * A winning child is picked on the initial update of this node, based on ticket weighting.\n * The state of this node will match the state of the winning child.\n */\nexport default class Lotto extends Composite {\n /**\n * @param attributes The node attributes.\n * @param tickets The child node tickets.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], private tickets: number[], children: Node[]) {\n super(\"lotto\", attributes, children);\n }\n\n /**\n * The child node selected to be the active one.\n */\n private selectedChild: Node | undefined;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to pick a winning child node.\n if (this.is(State.READY)) {\n // Create a lotto draw with which to randomly pick a child node to become the active one.\n const lottoDraw = createLotto({\n // Hook up the optional 'random' behaviour tree function option to the one used by 'lotto-draw'.\n random: options.random,\n // Pass in each child node as a participant in the lotto draw with their respective ticket count.\n participants: this.children.map((child, index) => [child, this.tickets[index] || 1])\n });\n\n // Randomly pick a child based on ticket weighting, this will become the active child for this composite node.\n this.selectedChild = lottoDraw.draw() || undefined;\n }\n\n // If something went wrong and we don't have an active child then we should throw an error.\n if (!this.selectedChild) {\n throw new Error(\"failed to update lotto node as it has no active child\");\n }\n\n // If the selected child has never been updated or is running then we will need to update it now.\n if (this.selectedChild.getState() === State.READY || this.selectedChild.getState() === State.RUNNING) {\n this.selectedChild.update(agent, options);\n }\n\n // The state of the lotto node is the state of its selected child.\n this.setState(this.selectedChild.getState());\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => (this.tickets.length ? `LOTTO [${this.tickets.join(\",\")}]` : \"LOTTO\");\n}\n", "import Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A composite node that wraps child nodes.\n */\nexport default abstract class Composite extends Node {\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(type: string, attributes: Attribute[], protected children: Node[]) {\n super(type, attributes, []);\n }\n\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => false;\n\n /**\n * Gets the children of this node.\n */\n getChildren = () => this.children;\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the state of any child nodes.\n this.getChildren().forEach((child) => child.reset());\n };\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n abort = (agent: Agent) => {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Abort any child nodes.\n this.getChildren().forEach((child) => child.abort(agent));\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n };\n}\n", "import Composite from \"./Composite\";\nimport Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A SELECTOR node.\n * The child nodes are executed in sequence until one succeeds or all fail.\n */\nexport default class Selector extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], protected children: Node[]) {\n super(\"selector\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then this node is also a 'SUCCEEDED' node.\n if (child.getState() === State.SUCCEEDED) {\n // This node is a 'SUCCEEDED' node.\n this.setState(State.SUCCEEDED);\n\n // There is no need to check the rest of the selector nodes.\n return;\n }\n\n // If the current child has a state of 'FAILED' then we should move on to the next child.\n if (child.getState() === State.FAILED) {\n // Find out if the current child is the last one in the selector.\n // If it is then this sequence node has also failed.\n if (this.children.indexOf(child) === this.children.length - 1) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // There is no need to check the rest of the selector as we have completed it.\n return;\n } else {\n // The child node failed, try the next one.\n continue;\n }\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() === State.RUNNING) {\n // This node is a 'RUNNING' node.\n this.setState(State.RUNNING);\n\n // There is no need to check the rest of the selector as the current child is still running.\n return;\n }\n\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SELECTOR\";\n}\n", "import Composite from \"./Composite\";\nimport Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A SEQUENCE node.\n * The child nodes are executed in sequence until one fails or all succeed.\n */\nexport default class Sequence extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], protected children: Node[]) {\n super(\"sequence\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then we should move on to the next child.\n if (child.getState() === State.SUCCEEDED) {\n // Find out if the current child is the last one in the sequence.\n // If it is then this sequence node has also succeeded.\n if (this.children.indexOf(child) === this.children.length - 1) {\n // This node is a 'SUCCEEDED' node.\n this.setState(State.SUCCEEDED);\n\n // There is no need to check the rest of the sequence as we have completed it.\n return;\n } else {\n // The child node succeeded, but we have not finished the sequence yet.\n continue;\n }\n }\n\n // If the current child has a state of 'FAILED' then this node is also a 'FAILED' node.\n if (child.getState() === State.FAILED) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // There is no need to check the rest of the sequence.\n return;\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() === State.RUNNING) {\n // This node is a 'RUNNING' node.\n this.setState(State.RUNNING);\n\n // There is no need to check the rest of the sequence as the current child is still running.\n return;\n }\n\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SEQUENCE\";\n}\n", "import Composite from \"./Composite\";\nimport State from \"../../State\";\nimport Node from \"../Node\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A PARALLEL node.\n * The child nodes are executed concurrently until one fails or all succeed.\n */\nexport default class Parallel extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], children: Node[]) {\n super(\"parallel\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Keep a count of the number of succeeded child nodes.\n let succeededCount = 0;\n\n let hasChildFailed = false;\n\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then we should move on to the next child.\n if (child.getState() === State.SUCCEEDED) {\n // The child node has succeeded, keep track of this to determine if all children have.\n succeededCount++;\n\n // The child node succeeded, but we have not finished checking every child node yet.\n continue;\n }\n\n // If the current child has a state of 'FAILED' then this node is also a 'FAILED' node.\n if (child.getState() === State.FAILED) {\n hasChildFailed = true;\n\n // There is no need to check the rest of the children.\n break;\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() !== State.RUNNING) {\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n if (hasChildFailed) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // Abort every running child.\n for (const child of this.children) {\n if (child.getState() === State.RUNNING) {\n child.abort(agent);\n }\n }\n } else {\n // If all children have succeeded then this node has also succeeded, otherwise it is still running.\n this.setState(succeededCount === this.children.length ? State.SUCCEEDED : State.RUNNING);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"PARALLEL\";\n}\n", "import { AnyArgument } from \"../RootAstNodesBuilder\";\nimport Guard from \"./guards/Guard\";\n\nexport type AttributeDetails = {\n /** The attribute type. */\n type: string;\n\n /** The attribute arguments. */\n args: AnyArgument[];\n};\n\n/**\n * A base node attribute.\n */\nexport default abstract class Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of attribute argument definitions.\n */\n constructor(protected type: string, protected args: AnyArgument[]) {}\n\n /**\n * Gets the type of the attribute.\n */\n getType = () => this.type;\n\n /**\n * Gets the array of attribute argument definitions.\n */\n getArguments = () => this.args;\n\n /**\n * Gets the attribute details.\n */\n abstract getDetails(): TAttributeDetails;\n\n /**\n * Gets whether this attribute is a guard.\n */\n abstract isGuard: () => this is Guard;\n}\n", "import { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\nimport Attribute, { AttributeDetails } from \"../Attribute\";\n\nexport type GuardAttributeDetails = {\n /** The name of the condition function that determines whether the guard is satisfied. */\n condition: string;\n} & AttributeDetails;\n\n/**\n * A base node guard attribute.\n */\nexport default abstract class Guard extends Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of decorator argument definitions.\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n */\n constructor(type: string, args: AnyArgument[], private condition: string) {\n super(type, args);\n }\n\n /**\n * Gets the name of the condition function that determines whether the guard is satisfied.\n */\n getCondition = () => this.condition;\n\n /**\n * Gets whether this attribute is a guard.\n */\n isGuard = () => true;\n\n /**\n * Gets the attribute details.\n */\n getDetails(): GuardAttributeDetails {\n return {\n type: this.getType(),\n args: this.getArguments(),\n condition: this.getCondition()\n };\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n abstract isSatisfied(agent: Agent): boolean;\n}\n", "import Guard from \"./Guard\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * A WHILE guard which is satisfied as long as the given condition remains true.\n */\nexport default class While extends Guard {\n /**\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n * @param args The array of decorator argument definitions.\n */\n constructor(condition: string, args: AnyArgument[]) {\n super(\"while\", args, condition);\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n isSatisfied = (agent: Agent) => {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.getCondition());\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered`\n );\n }\n\n // Call the condition function to determine whether this guard is satisfied.\n return !!conditionFuncInvoker(this.args);\n };\n}\n", "import Guard from \"./Guard\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * An UNTIL guard which is satisfied as long as the given condition remains false.\n */\nexport default class Until extends Guard {\n /**\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n * @param args The array of decorator argument definitions.\n */\n constructor(condition: string, args: AnyArgument[]) {\n super(\"until\", args, condition);\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n isSatisfied = (agent: Agent) => {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.getCondition());\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered`\n );\n }\n\n // Call the condition function to determine whether this guard is satisfied.\n return !!!conditionFuncInvoker(this.args);\n };\n}\n", "import { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\nimport Attribute, { AttributeDetails } from \"../Attribute\";\n\nexport type CallbackAttributeDetails = {\n /** The name of the agent function that is called. */\n functionName: string;\n} & AttributeDetails;\n\n/**\n * A base node callback attribute.\n */\nexport default abstract class Callback extends Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of decorator argument definitions.\n * @param functionName The name of the agent function to call.\n */\n constructor(type: string, args: AnyArgument[], private functionName: string) {\n super(type, args);\n }\n\n /**\n * Gets the name of the agent function to call.\n */\n getFunctionName = () => this.functionName;\n\n /**\n * Gets whether this attribute is a guard.\n */\n isGuard = () => false;\n\n /**\n * Gets the attribute details.\n */\n getDetails(): CallbackAttributeDetails {\n return {\n type: this.getType(),\n args: this.getArguments(),\n functionName: this.getFunctionName()\n };\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n abstract callAgentFunction: (agent: Agent, isSuccess: boolean, isAborted: boolean) => void;\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * An ENTRY callback which defines an agent function to call when the associated node is updated and moves out of running state.\n */\nexport default class Entry extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: AnyArgument[]) {\n super(\"entry\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n callAgentFunction = (agent: Agent) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call entry function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function.\n callbackFuncInvoker(this.args);\n };\n}\n", "import Callback from \"./Callback\";\nimport Lookup, { AnyExitArgument } from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * An EXIT callback which defines an agent function to call when the associated node is updated and moves to a finished state or is aborted.\n */\nexport default class Exit extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: AnyArgument[]) {\n super(\"exit\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n * @param isSuccess Whether the decorated node was left with a success state.\n * @param isAborted Whether the decorated node was aborted.\n */\n callAgentFunction = (agent: Agent, isSuccess: boolean, isAborted: boolean) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call exit function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function\n callbackFuncInvoker([{ value: { succeeded: isSuccess, aborted: isAborted } }, ...this.args]);\n };\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * A STEP callback which defines an agent function to call when the associated node is updated.\n */\nexport default class Step extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: AnyArgument[]) {\n super(\"step\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n callAgentFunction = (agent: Agent) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call step function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function.\n callbackFuncInvoker(this.args);\n };\n}\n", "import Action from \"./nodes/leaf/Action\";\nimport Condition from \"./nodes/leaf/Condition\";\nimport Wait from \"./nodes/leaf/Wait\";\nimport Root from \"./nodes/decorator/Root\";\nimport Repeat from \"./nodes/decorator/Repeat\";\nimport Retry from \"./nodes/decorator/Retry\";\nimport Flip from \"./nodes/decorator/Flip\";\nimport Succeed from \"./nodes/decorator/Succeed\";\nimport Fail from \"./nodes/decorator/Fail\";\nimport Lotto from \"./nodes/composite/Lotto\";\nimport Selector from \"./nodes/composite/Selector\";\nimport Sequence from \"./nodes/composite/Sequence\";\nimport Parallel from \"./nodes/composite/Parallel\";\nimport Node from \"./nodes/Node\";\nimport While from \"./attributes/guards/While\";\nimport Until from \"./attributes/guards/Until\";\nimport Entry from \"./attributes/callbacks/Entry\";\nimport Exit from \"./attributes/callbacks/Exit\";\nimport Step from \"./attributes/callbacks/Step\";\nimport Callback from \"./attributes/callbacks/Callback\";\nimport Guard from \"./attributes/guards/Guard\";\nimport Attribute from \"./attributes/Attribute\";\nimport Composite from \"./nodes/composite/Composite\";\nimport Decorator from \"./nodes/decorator/Decorator\";\nimport Leaf from \"./nodes/leaf/Leaf\";\n\nexport type Argument = {\n value: T;\n type: string; // Used for validation.\n};\ntype NullArgument = Argument & {\n type: \"null\";\n};\ntype BooleanArgument = Argument & {\n type: \"boolean\";\n};\ntype NumberArgument = Argument & {\n type: \"number\";\n isInteger: boolean; // Used for validation.\n};\ntype StringPlaceholderArgument = Argument & {\n type: \"string\";\n};\ntype IdentifierArgument = Argument & {\n type: \"identifier\";\n};\nexport type AnyArgument =\n | NullArgument\n | BooleanArgument\n | NumberArgument\n | StringPlaceholderArgument\n | IdentifierArgument;\n\n/**\n * The node attribute factories.\n */\nconst AttributeFactories: {\n [key: string]: (functionName: string, attributeArguments: AnyArgument[]) => Callback | Guard;\n} = {\n WHILE: (condition: string, attributeArguments: AnyArgument[]) => new While(condition, attributeArguments),\n UNTIL: (condition: string, attributeArguments: AnyArgument[]) => new Until(condition, attributeArguments),\n ENTRY: (functionName: string, attributeArguments: AnyArgument[]) => new Entry(functionName, attributeArguments),\n EXIT: (functionName: string, attributeArguments: AnyArgument[]) => new Exit(functionName, attributeArguments),\n STEP: (functionName: string, attributeArguments: AnyArgument[]) => new Step(functionName, attributeArguments)\n};\n\ntype Validatable = {\n children?: AstNode[];\n validate: (depth: number) => void;\n};\n\ntype NodeInstanceCreator = (\n namedRootNodeProvider: (name: string) => RootAstNode,\n visitedBranches: string[]\n) => T;\n\nexport type AstNode = Validatable & {\n type: string;\n createNodeInstance: NodeInstanceCreator;\n};\n\nexport type LeafAstNode = AstNode & {\n type: \"action\" | \"condition\" | \"wait\";\n attributes: Attribute[];\n};\n\nexport type CompositeAstNode = AstNode & {\n type: \"lotto\" | \"parallel\" | \"selector\" | \"sequence\";\n attributes: Attribute[];\n children: AstNode[];\n};\n\nexport type DecoratorAstNode = AstNode & {\n type: \"fail\" | \"flip\" | \"repeat\" | \"retry\" | \"root\" | \"succeed\";\n attributes: Attribute[];\n children: AstNode[];\n};\n\nexport type BranchAstNode = AstNode & {\n type: \"branch\";\n branchName: \"\" | string;\n};\n\nexport type LottoAstNode = CompositeAstNode & {\n type: \"lotto\";\n tickets: number[];\n};\n\nexport type RootAstNode = DecoratorAstNode & {\n type: \"root\";\n name: null | string;\n};\n\nexport type RepeatAstNode = DecoratorAstNode & {\n type: \"repeat\";\n iterations: number | null;\n iterationsMin: number | null;\n iterationsMax: number | null;\n};\n\nexport type RetryAstNode = DecoratorAstNode & {\n type: \"retry\";\n attempts: number | null;\n attemptsMin: number | null;\n attemptsMax: number | null;\n};\n\nexport type ActionAstNode = LeafAstNode & {\n type: \"action\";\n actionName: string;\n actionArguments: AnyArgument[];\n};\n\nexport type ConditionAstNode = LeafAstNode & {\n type: \"condition\";\n conditionName: string;\n conditionArguments: AnyArgument[];\n};\n\nexport type WaitAstNode = LeafAstNode & {\n type: \"wait\";\n duration: number | null;\n durationMin: number | null;\n durationMax: number | null;\n};\n\nexport type AnyAstNode =\n | BranchAstNode\n | CompositeAstNode\n | LottoAstNode\n | DecoratorAstNode\n | RootAstNode\n | RepeatAstNode\n | RetryAstNode\n | LeafAstNode\n | ActionAstNode\n | ConditionAstNode\n | WaitAstNode;\n\n/**\n * The AST node factories.\n */\nconst ASTNodeFactories = {\n ROOT: (): RootAstNode => ({\n type: \"root\",\n attributes: [],\n name: null,\n children: [],\n validate(depth: number) {\n // A root node cannot be the child of another node.\n if (depth > 1) {\n throw new Error(\"a root node cannot be the child of another node\");\n }\n\n // A root node must have a single child node.\n if (this.children.length !== 1) {\n throw new Error(\"a root node must have a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Root(\n this.attributes,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n BRANCH: (): BranchAstNode => ({\n type: \"branch\",\n branchName: \"\",\n validate() {},\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n // Try to find the root node with a matching branch name.\n const targetRootNode = namedRootNodeProvider(this.branchName);\n\n // If we have already visited this branch then we have a circular dependency.\n if (visitedBranches.indexOf(this.branchName) !== -1) {\n throw new Error(`circular dependency found in branch node references for branch '${this.branchName}'`);\n }\n\n // If we have a target root node, then the node instance we want will be the first and only child of the referenced root node.\n if (targetRootNode) {\n return targetRootNode\n .createNodeInstance(namedRootNodeProvider, visitedBranches.concat(this.branchName))\n .getChildren()[0];\n } else {\n throw new Error(`branch references root node '${this.branchName}' which has not been defined`);\n }\n }\n }),\n SELECTOR: (): CompositeAstNode => ({\n type: \"selector\",\n attributes: [],\n children: [],\n validate() {\n // A selector node must have at least a single node.\n if (this.children.length < 1) {\n throw new Error(\"a selector node must have at least a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Selector(\n this.attributes,\n this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice()))\n );\n }\n }),\n SEQUENCE: (): CompositeAstNode => ({\n type: \"sequence\",\n attributes: [],\n children: [],\n validate() {\n // A sequence node must have at least a single node.\n if (this.children.length < 1) {\n throw new Error(\"a sequence node must have at least a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Sequence(\n this.attributes,\n this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice()))\n );\n }\n }),\n PARALLEL: (): CompositeAstNode => ({\n type: \"parallel\",\n attributes: [],\n children: [],\n validate() {\n // A parallel node must have at least a single node.\n if (this.children.length < 1) {\n throw new Error(\"a parallel node must have at least a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Parallel(\n this.attributes,\n this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice()))\n );\n }\n }),\n LOTTO: (): LottoAstNode => ({\n type: \"lotto\",\n attributes: [],\n children: [],\n tickets: [],\n validate() {\n // A lotto node must have at least a single node.\n if (this.children!.length < 1) {\n throw new Error(\"a lotto node must have at least a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Lotto(\n this.attributes,\n this.tickets!,\n this.children!.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice()))\n );\n }\n }),\n REPEAT: (): RepeatAstNode => ({\n type: \"repeat\",\n attributes: [],\n iterations: null,\n iterationsMin: null,\n iterationsMax: null,\n children: [],\n validate() {\n // A repeat node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a repeat node must have a single child\");\n }\n\n if (this.iterations !== null) {\n // A repeat node must have a positive number of iterations if defined.\n if (this.iterations < 0) {\n throw new Error(\"a repeat node must have a positive number of iterations if defined\");\n }\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n // A repeat node must have a positive min and max iteration count if they are defined.\n if (this.iterationsMin < 0 || this.iterationsMax < 0) {\n throw new Error(\n \"a repeat node must have a positive minimum and maximum iteration count if defined\"\n );\n }\n\n // A repeat node must not have an minimum iteration count that exceeds the maximum iteration count.\n if (this.iterationsMin > this.iterationsMax) {\n throw new Error(\n \"a repeat node must not have a minimum iteration count that exceeds the maximum iteration count\"\n );\n }\n } else {\n // If we have no explicit iteration count or a minimum and maximum iteration count set then we are dealing with a repeat node that iterates indefinitely.\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Repeat(\n this.attributes,\n this.iterations,\n this.iterationsMin,\n this.iterationsMax,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n RETRY: (): RetryAstNode => ({\n type: \"retry\",\n attributes: [],\n attempts: null,\n attemptsMin: null,\n attemptsMax: null,\n children: [],\n validate() {\n // A retry node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a retry node must have a single child\");\n }\n\n if (this.attempts !== null) {\n // A retry node must have a positive number of attempts if defined.\n if (this.attempts < 0) {\n throw new Error(\"a retry node must have a positive number of attempts if defined\");\n }\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n // A retry node must have a positive min and max attempts count if they are defined.\n if (this.attemptsMin < 0 || this.attemptsMax < 0) {\n throw new Error(\"a retry node must have a positive minimum and maximum attempt count if defined\");\n }\n\n // A retry node must not have a minimum attempt count that exceeds the maximum attempt count.\n if (this.attemptsMin > this.attemptsMax) {\n throw new Error(\n \"a retry node must not have a minimum attempt count that exceeds the maximum attempt count\"\n );\n }\n } else {\n // If we have no explicit attempt count or a minimum and maximum attempt count set then we are dealing with a retry node that attempts indefinitely.\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Retry(\n this.attributes,\n this.attempts,\n this.attemptsMin,\n this.attemptsMax,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n FLIP: (): DecoratorAstNode => ({\n type: \"flip\",\n attributes: [],\n children: [],\n validate() {\n // A flip node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a flip node must have a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Flip(\n this.attributes,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n SUCCEED: (): DecoratorAstNode => ({\n type: \"succeed\",\n attributes: [],\n children: [],\n validate() {\n // A succeed node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a succeed node must have a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Succeed(\n this.attributes,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n FAIL: (): DecoratorAstNode => ({\n type: \"fail\",\n attributes: [],\n children: [],\n validate() {\n // A fail node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a fail node must have a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Fail(\n this.attributes,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n WAIT: (): WaitAstNode => ({\n type: \"wait\",\n attributes: [],\n duration: null,\n durationMin: null,\n durationMax: null,\n validate() {\n if (this.duration !== null) {\n // If an explict duration was defined then it must be a positive number.\n if (this.duration < 0) {\n throw new Error(\"a wait node must have a positive duration\");\n }\n } else if (this.durationMin !== null && this.durationMax !== null) {\n // A wait node must have a positive min and max duration.\n if (this.durationMin < 0 || this.durationMax < 0) {\n throw new Error(\"a wait node must have a positive minimum and maximum duration\");\n }\n\n // A wait node must not have a minimum duration that exceeds the maximum duration.\n if (this.durationMin > this.durationMax) {\n throw new Error(\"a wait node must not have a minimum duration that exceeds the maximum duration\");\n }\n } else {\n // If we have no explicit duration or duration bounds set then we are dealing with a wait node that waits indefinitely.\n }\n },\n createNodeInstance() {\n return new Wait(this.attributes, this.duration, this.durationMin, this.durationMax);\n }\n }),\n ACTION: (): ActionAstNode => ({\n type: \"action\",\n attributes: [],\n actionName: \"\",\n actionArguments: [],\n validate() {},\n createNodeInstance() {\n return new Action(this.attributes, this.actionName!, this.actionArguments!);\n }\n }),\n CONDITION: (): ConditionAstNode => ({\n type: \"condition\",\n attributes: [],\n conditionName: \"\",\n conditionArguments: [],\n validate() {},\n createNodeInstance() {\n return new Condition(this.attributes, this.conditionName!, this.conditionArguments!);\n }\n })\n};\n\ntype OtherAstNodes = AstNode[];\n\n/**\n * Create an array of root AST nodes based on the given definition.\n * @param definition The definition to parse the AST nodes from.\n * @returns The base definition AST nodes.\n */\nexport default function buildRootASTNodes(definition: string): RootAstNode[] {\n // Swap out any node/attribute argument string literals with a placeholder and get a mapping of placeholders to original values as well as the processed definition.\n const { placeholders, processedDefinition } = substituteStringLiterals(definition);\n\n // Convert the processed definition (with substituted string literals) into an array of raw tokens.\n const tokens = parseTokensFromDefinition(processedDefinition);\n\n // There must be at least 3 tokens for the tree definition to be valid. 'ROOT', '{' and '}'.\n if (tokens.length < 3) {\n throw new Error(\"invalid token count\");\n }\n\n // We should have a matching number of '{' and '}' tokens. If not, then there are scopes that have not been properly closed.\n if (tokens.filter((token) => token === \"{\").length !== tokens.filter((token) => token === \"}\").length) {\n throw new Error(\"scope character mismatch\");\n }\n\n // Create a stack of node children arrays, starting with a definition scope.\n const stack: [RootAstNode[], ...OtherAstNodes[]] = [[]];\n const rootScope = stack[0];\n\n // We should keep processing the raw tokens until we run out of them.\n while (tokens.length) {\n // Grab the next token.\n const token = tokens.shift();\n\n const currentScope = stack[stack.length - 1] as OtherAstNodes;\n\n // How we create the next AST token depends on the current raw token value.\n switch (token!.toUpperCase()) {\n case \"ROOT\": {\n // Create a ROOT AST node.\n const node = ASTNodeFactories.ROOT();\n\n // Push the ROOT node into the current scope.\n rootScope.push(node);\n\n // We may have a root node name defined as an argument.\n if (tokens[0] === \"[\") {\n const rootArguments = getArguments(tokens, placeholders);\n\n // We should have only a single argument that is not an empty string for a root node, which is the root name identifier.\n if (rootArguments.length === 1 && rootArguments[0].type === \"identifier\") {\n // The root name will be the first and only node argument.\n node.name = rootArguments[0].value as string;\n } else {\n throw new Error(\"expected single root name argument\");\n }\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new ROOT nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"BRANCH\": {\n // Create a BRANCH AST node.\n const node = ASTNodeFactories.BRANCH();\n\n // Push the BRANCH node into the current scope.\n currentScope.push(node);\n\n // We must have arguments defined, as we require a branch name argument.\n if (tokens[0] !== \"[\") {\n throw new Error(\"expected single branch name argument\");\n }\n\n // The branch name will be defined as a node argument.\n const branchArguments = getArguments(tokens, placeholders);\n\n // We should have only a single identifer argument for a branch node, which is the branch name.\n if (branchArguments.length === 1 && branchArguments[0].type === \"identifier\") {\n // The branch name will be the first and only node argument.\n node.branchName = branchArguments[0].value as string;\n } else {\n throw new Error(\"expected single branch name argument\");\n }\n break;\n }\n\n case \"SELECTOR\": {\n // Create a SELECTOR AST node.\n const node = ASTNodeFactories.SELECTOR();\n\n // Push the SELECTOR node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new SELECTOR nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"SEQUENCE\": {\n // Create a SEQUENCE AST node.\n const node = ASTNodeFactories.SEQUENCE();\n\n // Push the SEQUENCE node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new SEQUENCE nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"PARALLEL\": {\n // Create a PARALLEL AST node.\n const node = ASTNodeFactories.PARALLEL();\n\n // Push the PARALLEL node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new PARALLEL nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"LOTTO\": {\n // Create a LOTTO AST node.\n const node = ASTNodeFactories.LOTTO();\n\n // Push the LOTTO node into the current scope.\n currentScope.push(node);\n\n // If the next token is a '[' character then some ticket counts have been defined as arguments.\n if (tokens[0] === \"[\") {\n // Get the ticket count arguments, each argument must be a number.\n node.tickets = getArguments(\n tokens,\n placeholders,\n (arg) => arg.type === \"number\" && !!arg.isInteger,\n \"lotto node ticket counts must be integer values\"\n ).map((argument) => argument.value as number);\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new LOTTO nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"CONDITION\": {\n // Create a CONDITION AST node.\n const node = ASTNodeFactories.CONDITION();\n\n // Push the CONDITION node into the current scope.\n currentScope.push(node);\n\n // We must have arguments defined, as we require a condition function name argument.\n if (tokens[0] !== \"[\") {\n throw new Error(\"expected condition name identifier argument\");\n }\n\n // Grab the condition node arguments.\n const conditionArguments = getArguments(tokens, placeholders);\n\n // We should have at least a single identifier argument for a condition node, which is the condition function name.\n if (conditionArguments.length && conditionArguments[0].type === \"identifier\") {\n // The condition function name will be the first node argument.\n node.conditionName = conditionArguments.shift()!.value as string;\n } else {\n throw new Error(\"expected condition name identifier argument\");\n }\n\n // Only the first argument should have been an identifier, all following arguments must be string, number, boolean or null.\n conditionArguments\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n \"invalid condition node argument value '\" +\n arg.value +\n \"', must be string, number, boolean or null\"\n );\n });\n\n // Any node arguments that follow the condition name identifier will be treated as condition function arguments.\n node.conditionArguments = conditionArguments;\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n break;\n }\n\n case \"FLIP\": {\n // Create a FLIP AST node.\n const node = ASTNodeFactories.FLIP();\n\n // Push the Flip node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new FLIP nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"SUCCEED\": {\n // Create a SUCCEED AST node.\n const node = ASTNodeFactories.SUCCEED();\n\n // Push the Succeed node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new Succeed nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"FAIL\": {\n // Create a FAIL AST node.\n const node = ASTNodeFactories.FAIL();\n\n // Push the Fail node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new Fail nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"WAIT\": {\n // Create a WAIT AST node.\n const node = ASTNodeFactories.WAIT();\n\n // Push the WAIT node into the current scope.\n currentScope.push(node);\n\n // The arguments of a wait node are optional. We may have:\n // - No node arguments, in which case the wait will be indefinite until it is aborted.\n // - One node argument which will be the explicit duration of the wait.\n // - Two node arguments which define the min and max duration bounds from which a random duration will be picked.\n if (tokens[0] === \"[\") {\n // Get the optional duration and longest duration of the wait.\n const nodeArguments = getArguments(\n tokens,\n placeholders,\n (arg) => arg.type === \"number\" && !!arg.isInteger,\n \"wait node durations must be integer values\"\n ).map((argument) => argument.value);\n\n // We may have:\n // - One node argument which will be the explicit duration of the wait.\n // - Two node arguments which define the min and max duration bounds from which a random duration will be picked.\n // - Too many arguments, which is not valid.\n if (nodeArguments.length === 1) {\n // An explicit duration was defined.\n node.duration = nodeArguments[0] as number;\n } else if (nodeArguments.length === 2) {\n // Min and max duration bounds were defined from which a random duration will be picked.\n node.durationMin = nodeArguments[0] as number;\n node.durationMax = nodeArguments[1] as number;\n } else if (nodeArguments.length > 2) {\n // An incorrect number of durations was defined.\n throw new Error(\"invalid number of wait node duration arguments defined\");\n }\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n break;\n }\n\n case \"REPEAT\": {\n // Create a REPEAT AST node.\n const node = ASTNodeFactories.REPEAT();\n\n // Push the REPEAT node into the current scope.\n currentScope.push(node);\n\n // The arguments of a repeat node are optional. We may have:\n // - No node arguments, in which case the repeat note will iterate indefinitely.\n // - One node argument which will be the explicit number of iterations to make.\n // - Two node arguments which define the min and max iteration bounds from which a random iteration count will be picked.\n if (tokens[0] === \"[\") {\n // An iteration count has been defined. Get the iteration and potential maximum iteration of the wait.\n const nodeArguments = getArguments(\n tokens,\n placeholders,\n (arg) => arg.type === \"number\" && !!arg.isInteger,\n \"repeat node iteration counts must be integer values\"\n ).map((argument) => argument.value);\n\n // We should have got one or two iteration counts.\n if (nodeArguments.length === 1) {\n // A static iteration count was defined.\n node.iterations = nodeArguments[0] as number;\n } else if (nodeArguments.length === 2) {\n // A minimum and maximum iteration count was defined.\n node.iterationsMin = nodeArguments[0] as number;\n node.iterationsMax = nodeArguments[1] as number;\n } else {\n // An incorrect number of iteration counts was defined.\n throw new Error(\"invalid number of repeat node iteration count arguments defined\");\n }\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new REPEAT nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"RETRY\": {\n // Create a RETRY AST node.\n const node = ASTNodeFactories.RETRY();\n\n // Push the RETRY node into the current scope.\n currentScope.push(node);\n\n // The arguments of a retry node are optional. We may have:\n // - No node arguments, in which case the retry note will attempt indefinitely.\n // - One node argument which will be the explicit number of attempts to make.\n // - Two node arguments which define the min and max attempt bounds from which a random attempt count will be picked.\n if (tokens[0] === \"[\") {\n // An attempt count has been defined. Get the attempt count and potential maximum attempt count of the wait.\n const nodeArguments = getArguments(\n tokens,\n placeholders,\n (arg) => arg.type === \"number\" && !!arg.isInteger,\n \"retry node attempt counts must be integer values\"\n ).map((argument) => argument.value);\n\n // We should have got one or two attempt counts.\n if (nodeArguments.length === 1) {\n // A static attempt count was defined.\n node.attempts = nodeArguments[0] as number;\n } else if (nodeArguments.length === 2) {\n // A minimum and maximum attempt count was defined.\n node.attemptsMin = nodeArguments[0] as number;\n node.attemptsMax = nodeArguments[1] as number;\n } else {\n // An incorrect number of attempt counts was defined.\n throw new Error(\"invalid number of retry node attempt count arguments defined\");\n }\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new RETRY nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"ACTION\": {\n // Create a ACTION AST node.\n const node = ASTNodeFactories.ACTION();\n\n // Push the ACTION node into the current scope.\n currentScope.push(node);\n\n // We must have arguments defined, as we require an action name argument.\n if (tokens[0] !== \"[\") {\n throw new Error(\"expected action name identifier argument\");\n }\n\n // The action name will be defined as a node argument.\n const actionArguments = getArguments(tokens, placeholders);\n\n // We should have at least one identifer argument for an action node, which is the action name.\n if (actionArguments.length && actionArguments[0].type === \"identifier\") {\n // The action name will be the first and only node argument.\n node.actionName = actionArguments.shift()!.value as string;\n } else {\n throw new Error(\"expected action name identifier argument\");\n }\n\n // Only the first argument should have been an identifier, all following arguments must be string, number, boolean or null.\n actionArguments\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n \"invalid action node argument value '\" +\n arg.value +\n \"', must be string, number, boolean or null\"\n );\n });\n\n // Any node arguments that follow the action name identifier will be treated as action function arguments.\n node.actionArguments = actionArguments;\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n break;\n }\n\n case \"}\": {\n // The '}' character closes the current scope.\n stack.pop();\n break;\n }\n\n default: {\n throw new Error(`unexpected token '${token}'`);\n }\n }\n }\n\n // A function to recursively validate each of the nodes in the AST.\n const validateASTNode = (node: Validatable, depth: number): void => {\n // Validate the node.\n node.validate(depth);\n\n // Validate each child of the node.\n (node.children || []).forEach((child) => validateASTNode(child, depth + 1));\n };\n\n // Start node validation from the definition root.\n validateASTNode(\n {\n children: stack[0] as RootAstNode[],\n validate(this: { children: RootAstNode[] }) {\n // We must have at least one node defined as the definition scope, which should be a root node.\n if (this.children.length === 0) {\n throw new Error(\"expected root node to have been defined\");\n }\n\n // Each node at the base of the definition scope MUST be a root node.\n for (const definitionLevelNode of this.children) {\n if (definitionLevelNode.type !== \"root\") {\n throw new Error(\"expected root node at base of definition\");\n }\n }\n\n // Exactly one root node must not have a name defined. This will be the main root, others will have to be referenced via branch nodes.\n if (this.children.filter((definitionLevelNode) => definitionLevelNode.name === null).length !== 1) {\n throw new Error(\"expected single unnamed root node at base of definition to act as main root\");\n }\n\n // No two named root nodes can have matching names.\n const rootNodeNames: string[] = [];\n for (const definitionLevelNode of this.children) {\n if (rootNodeNames.indexOf(definitionLevelNode.name!) !== -1) {\n throw new Error(`multiple root nodes found with duplicate name '${definitionLevelNode.name}'`);\n } else {\n rootNodeNames.push(definitionLevelNode.name!);\n }\n }\n }\n },\n 0\n );\n\n // Return the root AST nodes.\n return stack[0];\n}\n\n/**\n * Pop the next raw token off of the stack and throw an error if it wasn't the expected one.\n * @param tokens The array of remaining tokens.\n * @param expected An optional string or array or items, one of which must match the next popped token.\n * @returns The popped token.\n */\nfunction popAndCheck(tokens: string[], expected: string | string[]) {\n // Get and remove the next token.\n const popped = tokens.shift();\n\n // We were expecting another token.\n if (popped === undefined) {\n throw new Error(\"unexpected end of definition\");\n }\n\n // Do we have an expected token/tokens array?\n if (expected !== undefined) {\n // Check whether the popped token matches at least one of our expected items.\n var tokenMatchesExpectation = ([] as string[])\n .concat(expected)\n .some((item) => popped.toUpperCase() === item.toUpperCase());\n\n // Throw an error if the popped token didn't match any of our expected items.\n if (!tokenMatchesExpectation) {\n const expectationString = ([] as string[])\n .concat(expected)\n .map((item) => \"'\" + item + \"'\")\n .join(\" or \");\n\n throw new Error(`unexpected token found. Expected '${expectationString}' but got '${popped}'`);\n }\n }\n\n // Return the popped token.\n return popped;\n}\n\ntype Placeholders = { [key: string]: string };\n\n/**\n * Pull an argument definition list off of the token stack.\n * @param tokens The array of remaining tokens.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @param argumentValidator The argument validator function.\n * @param validationFailedMessage The exception message to throw if argument validation fails.\n * @returns The argument definition list.\n */\nfunction getArguments(\n tokens: string[],\n stringArgumentPlaceholders: Placeholders,\n argumentValidator?: (arg: AnyArgument) => boolean,\n validationFailedMessage?: string\n) {\n // Any lists of arguments will always be wrapped in '[]' for node arguments or '()' for attribute arguments.\n // We are looking for a '[' or '(' opener that wraps the argument tokens and the relevant closer.\n const closer = popAndCheck(tokens, [\"[\", \"(\"]) === \"[\" ? \"]\" : \")\";\n\n const argumentListTokens: string[] = [];\n const argumentList: AnyArgument[] = [];\n\n // Grab all tokens between the '[' and ']' or '(' and ')'.\n while (tokens.length && tokens[0] !== closer) {\n // The next token is part of our arguments list.\n argumentListTokens.push(tokens.shift()!);\n }\n\n // Validate the order of the argument tokens. Each token must either be a ',' or a single argument that satisfies the validator.\n argumentListTokens.forEach((token, index) => {\n // Get whether this token should be an actual argument.\n const shouldBeArgumentToken = !(index & 1);\n\n // If the current token should be an actual argument then validate it,otherwise it should be a ',' token.\n if (shouldBeArgumentToken) {\n // Get the argument definition.\n const argumentDefinition = getArgumentDefinition(token!, stringArgumentPlaceholders);\n\n // Try to validate the argument.\n if (argumentValidator && !argumentValidator(argumentDefinition)) {\n throw new Error(validationFailedMessage);\n }\n\n // This is a valid argument!\n argumentList.push(argumentDefinition);\n } else {\n // The current token should be a ',' token.\n if (token !== \",\") {\n throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`);\n }\n }\n });\n\n // The arguments list should terminate with a ']' or ')' token, depending on the opener.\n popAndCheck(tokens, closer);\n\n // Return the argument list.\n return argumentList;\n}\n\n/**\n * Gets an argument value definition.\n * @param token The argument token.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @returns An argument value definition.\n */\nfunction getArgumentDefinition(token: string, stringArgumentPlaceholders: Placeholders): AnyArgument {\n // Check whether the token represents a null value.\n if (token === \"null\") {\n return {\n value: null,\n type: \"null\"\n } as NullArgument;\n }\n\n // Check whether the token represents a boolean value.\n if (token === \"true\" || token === \"false\") {\n return {\n value: token === \"true\",\n type: \"boolean\"\n } as BooleanArgument;\n }\n\n // Check whether the token represents a number value.\n // TODO: Relies on broken isNaN - see MDN.\n // if (!Number.isNaN(token)) {\n if (!isNaN(token as any)) {\n return {\n value: parseFloat(token),\n isInteger: parseFloat(token) === parseInt(token, 10),\n type: \"number\"\n } as NumberArgument;\n }\n\n // Check whether the token is a placeholder (e.g. @@0@@) representing a string literal.\n if (token.match(/^@@\\d+@@$/g)) {\n return {\n value: stringArgumentPlaceholders[token].replace('\\\\\"', '\"'),\n type: \"string\"\n } as StringPlaceholderArgument;\n }\n\n // The only remaining option is that the argument value is an identifier.\n return {\n value: token,\n type: \"identifier\"\n } as IdentifierArgument;\n}\n\n/**\n * Pull any attributes off of the token stack.\n * @param tokens The array of remaining tokens.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @returns An array of attributes defined by any directly following tokens.\n */\nfunction getAttributes(tokens: string[], stringArgumentPlaceholders: Placeholders) {\n // Create an array to hold any attributes found.\n const attributes: Attribute[] = [];\n\n // Keep track of names of attribute that we have found on the token stack, as we cannot have duplicates.\n const attributesFound: string[] = [];\n\n // Try to get the attribute factory for the next token.\n let attributeFactory = AttributeFactories[(tokens[0] || \"\").toUpperCase()];\n\n // Pull attribute tokens off of the tokens stack until we have no more.\n while (attributeFactory) {\n // Check to make sure that we have not already created a attribute of this type for this node.\n if (attributesFound.indexOf(tokens[0].toUpperCase()) !== -1) {\n throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`);\n }\n\n // Add the current attribute type to our array of found attributes.\n attributesFound.push(tokens.shift()!.toUpperCase());\n\n // Grab any attribute arguments.\n const attributeArguments = getArguments(tokens, stringArgumentPlaceholders);\n\n // The first attribute argument has to be an identifer, this will reference an agent function.\n if (attributeArguments.length === 0 || attributeArguments[0].type !== \"identifier\") {\n throw new Error(\"expected agent function name identifier argument for attribute\");\n }\n\n // Grab the first attribute which is an identifier that will reference an agent function.\n const attributeFunctionName = attributeArguments.shift()! as IdentifierArgument;\n\n // Any remaining attribute arguments must have a type of string, number, boolean or null.\n attributeArguments\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n \"invalid attribute argument value '\" + arg.value + \"', must be string, number, boolean or null\"\n );\n });\n\n // Create the attribute and add it to the array of attributes found.\n attributes.push(attributeFactory(attributeFunctionName.value, attributeArguments));\n\n // Try to get the next attribute name token, as there could be multiple.\n attributeFactory = AttributeFactories[(tokens[0] || \"\").toUpperCase()];\n }\n\n return attributes;\n}\n\n/**\n * Swaps out any node/attribute argument string literals with placeholders.\n * @param definition The definition.\n * @returns An object containing a mapping of placeholders to original string values as well as the processed definition string.\n */\nfunction substituteStringLiterals(definition: string): {\n placeholders: { [key: string]: string };\n processedDefinition: string;\n} {\n // Create an object to hold the mapping of placeholders to original string values.\n const placeholders: Placeholders = {};\n\n // Replace any string literals wrapped with double quotes in our definition with placeholders to be processed later.\n const processedDefinition = definition.replace(/\\\"(\\\\.|[^\"\\\\])*\\\"/g, (match) => {\n var strippedMatch = match.substring(1, match.length - 1);\n var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch);\n\n // If we have no existing string literal match then create a new placeholder.\n if (!placeholder) {\n placeholder = `@@${Object.keys(placeholders).length}@@`;\n placeholders[placeholder] = strippedMatch;\n }\n\n return placeholder;\n });\n\n return { placeholders, processedDefinition };\n}\n\n/**\n * Parse the tree definition into an array of raw tokens.\n * @param definition The definition.\n * @returns An array of tokens parsed from the definition.\n */\nfunction parseTokensFromDefinition(definition: string): string[] {\n // Add some space around various important characters so that they can be plucked out easier as individual tokens.\n definition = definition.replace(/\\(/g, \" ( \");\n definition = definition.replace(/\\)/g, \" ) \");\n definition = definition.replace(/\\{/g, \" { \");\n definition = definition.replace(/\\}/g, \" } \");\n definition = definition.replace(/\\]/g, \" ] \");\n definition = definition.replace(/\\[/g, \" [ \");\n definition = definition.replace(/\\,/g, \" , \");\n\n // Split the definition into raw token form and return it.\n return definition.replace(/\\s+/g, \" \").trim().split(\" \");\n}\n", "import GuardPath, { GuardPathPart } from \"./attributes/guards/GuardPath\";\nimport buildRootASTNodes, { AnyArgument, RootAstNode } from \"./RootAstNodesBuilder\";\nimport State, { AnyState } from \"./State\";\nimport Lookup from \"./Lookup\";\nimport Node from \"./nodes/Node\";\nimport Root from \"./nodes/decorator/Root\";\nimport Composite from \"./nodes/composite/Composite\";\nimport Decorator from \"./nodes/decorator/Decorator\";\nimport { Agent, GlobalFunction } from \"./Agent\";\nimport { CallbackAttributeDetails } from \"./attributes/callbacks/Callback\";\nimport { GuardAttributeDetails } from \"./attributes/guards/Guard\";\nimport { BehaviourTreeOptions } from \"./BehaviourTreeOptions\";\n\n// Purely for outside inspection of the tree.\nexport type FlattenedTreeNode = {\n id: string;\n type: string;\n caption: string;\n state: AnyState;\n guards: GuardAttributeDetails[];\n callbacks: CallbackAttributeDetails[];\n args: AnyArgument[];\n parentId: string | null;\n};\n\n/**\n * A representation of a behaviour tree.\n */\nexport class BehaviourTree {\n /**\n * The main root tree node.\n */\n public readonly rootNode: Root;\n\n /**\n * Creates a new instance of the BehaviourTree class.\n * @param definition The behaviour tree definition.\n * @param agent The agent instance that this behaviour tree is modelling behaviour for.\n * @param options The behaviour tree options object.\n */\n constructor(definition: string, private agent: Agent, private options: BehaviourTreeOptions = {}) {\n // The tree definition must be defined and a valid string.\n if (typeof definition !== \"string\") {\n throw new Error(\"the tree definition must be a string\");\n }\n\n // The agent must be defined and not null.\n if (typeof agent !== \"object\" || agent === null) {\n throw new Error(\"the agent must be defined and not null\");\n }\n\n // Parse the behaviour tree definition, create the populated tree of behaviour tree nodes, and get the root.\n this.rootNode = BehaviourTree.createRootNode(definition);\n }\n\n /**\n * Gets whether the tree is in the RUNNING state.\n * @returns true if the tree is in the RUNNING state, otherwise false.\n */\n isRunning() {\n return this.rootNode.getState() === State.RUNNING;\n }\n\n /**\n * Gets the current tree state of SUCCEEDED, FAILED, READY or RUNNING.\n * @returns The current tree state.\n */\n getState() {\n return this.rootNode.getState();\n }\n\n /**\n * Step the tree.\n * Carries out a node update that traverses the tree from the root node outwards to any child nodes, skipping those that are already in a resolved state of SUCCEEDED or FAILED.\n * After being updated, leaf nodes will have a state of SUCCEEDED, FAILED or RUNNING. Leaf nodes that are left in the RUNNING state as part of a tree step will be revisited each\n * subsequent step until they move into a resolved state of either SUCCEEDED or FAILED, after which execution will move through the tree to the next node with a state of READY.\n *\n * Calling this method when the tree is already in a resolved state of SUCCEEDED or FAILED will cause it to be reset before tree traversal begins.\n */\n step() {\n // If the root node has already been stepped to completion then we need to reset it.\n if (this.rootNode.getState() === State.SUCCEEDED || this.rootNode.getState() === State.FAILED) {\n this.rootNode.reset();\n }\n\n try {\n this.rootNode.update(this.agent, this.options);\n } catch (exception) {\n throw new Error(`error stepping tree: ${(exception as Error).message}`);\n }\n }\n\n /**\n * Resets the tree from the root node outwards to each nested node, giving each a state of READY.\n */\n reset() {\n this.rootNode.reset();\n }\n\n /**\n * Gets the flattened details of every node in the tree.\n * @returns The flattened details of every node in the tree.\n */\n getFlattenedNodeDetails(): FlattenedTreeNode[] {\n // Create an empty flattened array of tree nodes.\n const flattenedTreeNodes: FlattenedTreeNode[] = [];\n\n /**\n * Helper function to process a node instance and push details into the flattened tree nodes array.\n * @param node The current node.\n * @param parentUid The UID of the node parent, or null if the node is the main root node.\n */\n const processNode = (node: Node, parentUid: string | null) => {\n // Get the guard and callback attribute details for this node.\n const guards = node\n .getAttributes()\n .filter((attribute) => attribute.isGuard())\n .map((attribute) => attribute.getDetails()) as GuardAttributeDetails[];\n const callbacks = node\n .getAttributes()\n .filter((attribute) => !attribute.isGuard())\n .map((attribute) => attribute.getDetails()) as CallbackAttributeDetails[];\n\n // Push the current node into the flattened nodes array.\n flattenedTreeNodes.push({\n id: node.getUid(),\n type: node.getType(),\n caption: node.getName(),\n state: node.getState(),\n guards,\n callbacks,\n args: node.getArguments(),\n parentId: parentUid\n });\n\n // Process each of the nodes children if it is not a leaf node.\n if (!node.isLeafNode()) {\n (node as Composite | Decorator)\n .getChildren()\n .forEach((child) => processNode(child, (node as Composite | Decorator).getUid()));\n }\n };\n\n // Convert the nested node structure into a flattened array of node details.\n processNode(this.rootNode, null);\n\n return flattenedTreeNodes;\n }\n\n /**\n * Registers the action/condition/guard/callback function or subtree with the given name.\n * @param name The name of the function or subtree to register.\n * @param value The function or subtree definition to register.\n */\n static register(name: string, value: GlobalFunction | string) {\n if (typeof value === \"function\") {\n // We are going to register a action/condition/guard/callback function.\n Lookup.setFunc(name, value);\n } else if (typeof value === \"string\") {\n // We are going to register a subtree.\n let rootASTNodes: RootAstNode[];\n\n try {\n // Try to create the behaviour tree AST based on the definition provided, this could fail if the definition is invalid.\n rootASTNodes = buildRootASTNodes(value);\n } catch (exception) {\n // There was an issue in trying to parse and build the tree definition.\n throw new Error(`error registering definition: ${(exception as Error).message}`);\n }\n\n // This function should only ever be called with a definition containing a single unnamed root node.\n if (rootASTNodes.length != 1 || rootASTNodes[0].name !== null) {\n throw new Error(\"error registering definition: expected a single unnamed root node\");\n }\n\n Lookup.setSubtree(name, rootASTNodes[0]);\n } else {\n throw new Error(\"unexpected value, expected string definition or function\");\n }\n }\n\n /**\n * Unregisters the registered action/condition/guard/callback function or subtree with the given name.\n * @param name The name of the registered action/condition/guard/callback function or subtree to unregister.\n */\n static unregister(name: string): void {\n Lookup.remove(name);\n }\n\n /**\n * Unregister all registered action/condition/guard/callback functions and subtrees.\n */\n static unregisterAll(): void {\n Lookup.empty();\n }\n\n /**\n * Parses a behaviour tree definition and creates a tree of behaviour tree nodes.\n * @param {string} definition The behaviour tree definition.\n * @returns The root behaviour tree node.\n */\n private static createRootNode(definition: string): Root {\n try {\n // Try to create the behaviour tree AST based on the definition provided, this could fail if the definition is invalid.\n const rootASTNodes = buildRootASTNodes(definition);\n\n // Create a symbol to use as the main root key in our root node mapping.\n const mainRootNodeKey = Symbol(\"__root__\");\n\n // Create a mapping of root node names to root AST tokens. The main root node will have a key of Symbol(\"__root__\").\n const rootNodeMap: { [key: string | symbol]: RootAstNode } = {};\n for (const rootASTNode of rootASTNodes) {\n rootNodeMap[rootASTNode.name === null ? mainRootNodeKey : rootASTNode.name!] = rootASTNode;\n }\n\n // Convert the AST to our actual tree and get the root node.\n const rootNode: Root = rootNodeMap[mainRootNodeKey].createNodeInstance(\n // Create a provider for named root nodes that are part of our definition or have been registered. Prioritising the former.\n (name: string): RootAstNode => (rootNodeMap[name] ? rootNodeMap[name] : Lookup.getSubtree(name)),\n []\n );\n\n // Set a guard path on every leaf of the tree to evaluate as part of its update.\n BehaviourTree.applyLeafNodeGuardPaths(rootNode);\n\n // Return the root node.\n return rootNode;\n } catch (exception) {\n // There was an issue in trying to parse and build the tree definition.\n throw new Error(`error parsing tree: ${(exception as Error).message}`);\n }\n }\n\n /**\n * Applies a guard path to every leaf of the tree to evaluate as part of each update.\n * @param rootNode The main root tree node.\n */\n private static applyLeafNodeGuardPaths(rootNode: Root) {\n const nodePaths: Node[][] = [];\n\n const findLeafNodes = (path: Node[], node: Node) => {\n // Add the current node to the path.\n path = path.concat(node);\n\n // Check whether the current node is a leaf node.\n if (node.isLeafNode()) {\n nodePaths.push(path);\n } else {\n (node as Composite | Decorator).getChildren().forEach((child) => findLeafNodes(path, child));\n }\n };\n\n // Find all leaf node paths, starting from the root.\n findLeafNodes([], rootNode);\n\n nodePaths.forEach((path) => {\n // Each node in the current path will have to be assigned a guard path, working from the root outwards.\n for (let depth = 0; depth < path.length; depth++) {\n // Get the node in the path at the current depth.\n const currentNode = path[depth];\n\n // The node may already have been assigned a guard path, if so just skip it.\n if (currentNode.hasGuardPath()) {\n continue;\n }\n\n // Create the guard path for the current node.\n const guardPath = new GuardPath(\n path\n .slice(0, depth + 1)\n .map((node) => ({ node, guards: node.getGuardAttributes() }))\n .filter((details) => details.guards.length > 0)\n );\n\n // Assign the guard path to the current node.\n currentNode.setGuardPath(guardPath);\n }\n });\n }\n}\n"], - "mappings": "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", - "names": ["Participant", "Lotto", "createLotto", "State", "createLotto"] + "sources": ["../node_modules/lotto-draw/dist/Participant.js", "../node_modules/lotto-draw/dist/Utilities.js", "../node_modules/lotto-draw/dist/Lotto.js", "../node_modules/lotto-draw/dist/createLotto.js", "../node_modules/lotto-draw/dist/index.js", "../src/index.ts", "../src/State.ts", "../src/BehaviourTreeDefinitionUtilities.ts", "../src/mdsl/MDSLUtilities.ts", "../src/mdsl/MDSLNodeArgumentParser.ts", "../src/mdsl/MDSLNodeAttributeParser.ts", "../src/mdsl/MDSLDefinitionParser.ts", "../src/BehaviourTreeDefinitionValidator.ts", "../src/Lookup.ts", "../src/attributes/guards/GuardUnsatisifedException.ts", "../src/attributes/guards/GuardPath.ts", "../src/nodes/Node.ts", "../src/nodes/composite/Composite.ts", "../src/nodes/composite/Parallel.ts", "../src/nodes/composite/Selector.ts", "../src/nodes/composite/Sequence.ts", "../src/nodes/composite/Lotto.ts", "../src/nodes/decorator/Decorator.ts", "../src/nodes/decorator/Fail.ts", "../src/nodes/decorator/Flip.ts", "../src/nodes/decorator/Repeat.ts", "../src/nodes/decorator/Retry.ts", "../src/nodes/decorator/Root.ts", "../src/nodes/decorator/Succeed.ts", "../src/nodes/leaf/Leaf.ts", "../src/nodes/leaf/Action.ts", "../src/nodes/leaf/Condition.ts", "../src/nodes/leaf/Wait.ts", "../src/attributes/Attribute.ts", "../src/attributes/guards/Guard.ts", "../src/attributes/guards/While.ts", "../src/attributes/guards/Until.ts", "../src/attributes/callbacks/Callback.ts", "../src/attributes/callbacks/Entry.ts", "../src/attributes/callbacks/Step.ts", "../src/attributes/callbacks/Exit.ts", "../src/BehaviourTreeBuilder.ts", "../src/BehaviourTree.ts"], + "sourcesContent": ["\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.Participant = void 0;\r\n/**\r\n * A participant that holds a number of tickets.\r\n */\r\nvar Participant = /** @class */ (function () {\r\n /**\r\n * Creates an instance of the Participant class.\r\n * @param participant The actual participant.\r\n * @param tickets The number of tickets held by the participant.\r\n */\r\n function Participant(participant, tickets) {\r\n if (tickets === void 0) { tickets = 1; }\r\n this._participant = participant;\r\n this._tickets = tickets;\r\n }\r\n Object.defineProperty(Participant.prototype, \"participant\", {\r\n /** Gets the actual participant. */\r\n get: function () {\r\n return this._participant;\r\n },\r\n enumerable: false,\r\n configurable: true\r\n });\r\n Object.defineProperty(Participant.prototype, \"tickets\", {\r\n /** Gets or sets the number of tickets held by the participant. */\r\n get: function () {\r\n return this._tickets;\r\n },\r\n set: function (value) {\r\n this._tickets = value;\r\n },\r\n enumerable: false,\r\n configurable: true\r\n });\r\n return Participant;\r\n}());\r\nexports.Participant = Participant;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.isNaturalNumber = exports.isNullOrUndefined = void 0;\r\n/**\r\n * Gets whether the value provided is null or undefined.\r\n * @param value The value to check.\r\n * @returns Whether the value provided is null or undefined.\r\n */\r\nfunction isNullOrUndefined(value) {\r\n return value === null || value === undefined;\r\n}\r\nexports.isNullOrUndefined = isNullOrUndefined;\r\n/**\r\n * Gets whether the value provided is a natural number.\r\n * @param value The value to check.\r\n * @returns Whether the value provided is a natural number.\r\n */\r\nfunction isNaturalNumber(value) {\r\n return typeof value === \"number\" && value >= 1 && Math.floor(value) === value;\r\n}\r\nexports.isNaturalNumber = isNaturalNumber;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.Lotto = void 0;\r\nvar Participant_1 = require(\"./Participant\");\r\nvar Utilities_1 = require(\"./Utilities\");\r\n/**\r\n * Represents a lotto consisting of a number of pickable ticket-holding participants.\r\n */\r\nvar Lotto = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of Lotto.\r\n * @param customRandom The custom RNG to use in place of Math.random().\r\n */\r\n function Lotto(customRandom) {\r\n /** The array of participants that are holding tickets in the lotto. */\r\n this._participants = [];\r\n this._customRandom = customRandom;\r\n }\r\n /**\r\n * Adds a participant with the specified number of tickets, or adds to the participant ticket count if the participant already holds tickets.\r\n * @param participant The participant to add or to increase the ticket count for if they already hold tickets.\r\n * @param tickets The number of tickets, defaults to 1.\r\n * @returns The Lotto instance.\r\n */\r\n Lotto.prototype.add = function (participant, tickets) {\r\n if (tickets === void 0) { tickets = 1; }\r\n // Check that we have a valid ticket count.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n // Check whether this participant has already been added.\r\n var existingParticipant = this._participants.find(function (part) { return part.participant === participant; });\r\n if (existingParticipant) {\r\n // The participant has already been added to the lotto so just add to their ticket count.\r\n existingParticipant.tickets += tickets;\r\n }\r\n else {\r\n // The participant is not part of the lotto so we should add them.\r\n this._participants.push(new Participant_1.Participant(participant, tickets));\r\n }\r\n return this;\r\n };\r\n /**\r\n * Removes the specified number of tickets for the given participant from the draw, or all tickets if a ticket number is not defined.\r\n * @param participant The participant to remove tickets for.\r\n * @param tickets The number of tickets to remove, or undefined if all tickets are to be removed.\r\n * @returns The Lotto instance.\r\n */\r\n Lotto.prototype.remove = function (participant, tickets) {\r\n // Attempt to get the existing participant.\r\n var existingParticipant = this._participants.find(function (part) { return part.participant === participant; });\r\n // There is nothing to do if the specified participant isn't even part of the lotto.\r\n if (!existingParticipant) {\r\n return this;\r\n }\r\n // Check whether a tickets value was given.\r\n if (tickets !== undefined) {\r\n // Check that we have a valid ticket count.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n existingParticipant.tickets -= tickets;\r\n // If the participant no longer holds any tickets then they should be removed.\r\n if (existingParticipant.tickets < 1) {\r\n this._participants = this._participants.filter(function (part) { return part !== existingParticipant; });\r\n }\r\n }\r\n else {\r\n // We are removing all tickets for the participant so just remove them from the lotto.\r\n this._participants = this._participants.filter(function (part) { return part !== existingParticipant; });\r\n }\r\n return this;\r\n };\r\n /**\r\n * Draw a winning ticket and return the participant that holds the ticket.\r\n * @param options The draw options.\r\n * @returns The participant that holds the winning ticket.\r\n */\r\n Lotto.prototype.draw = function (options) {\r\n if (options === void 0) { options = {}; }\r\n // If we have no participants then just return null.\r\n if (this._participants.length === 0) {\r\n return null;\r\n }\r\n var redrawable = (0, Utilities_1.isNullOrUndefined)(options.redrawable) ? true : options.redrawable;\r\n var pickable = [];\r\n this._participants.forEach(function (_a) {\r\n var participant = _a.participant, tickets = _a.tickets;\r\n for (var ticketCount = 0; ticketCount < tickets; ticketCount++) {\r\n pickable.push(participant);\r\n }\r\n });\r\n var random;\r\n // We need a random floating-point number between 0 (inclusive) and 1 to scale up to pick our winner.\r\n // If a custom random function exists then we should use that or fall back to Math.random().\r\n if (this._customRandom) {\r\n // Call our custom random function to get a random floating-point number.\r\n random = this._customRandom();\r\n // Verify that the result of calling our custom random function is a number between 0 (inclusive) and 1.\r\n if (typeof random !== \"number\" || random < 0 || random >= 1) {\r\n throw new Error(\"the 'random' function provided did not return a number between 0 (inclusive) and 1\");\r\n }\r\n }\r\n else {\r\n // No custom random function was defined so just use good ol' Math.random().\r\n random = Math.random();\r\n }\r\n // Pick a winning participant.\r\n var winner = pickable[Math.floor(random * pickable.length)];\r\n // If the ticket isn't redrawable then we should remove a ticket from the winning participants ticket count.\r\n if (!redrawable) {\r\n this.remove(winner, 1);\r\n }\r\n // Return the winning participant.\r\n return winner;\r\n };\r\n /**\r\n * Draws multiple winning tickets and return an array of the participants that hold the winning tickets.\r\n * @param tickets The number of winning tickets to draw.\r\n * @param options The draw multiple options.\r\n * @returns An array of the participants that hold the winning tickets.\r\n */\r\n Lotto.prototype.drawMultiple = function (tickets, options) {\r\n if (options === void 0) { options = {}; }\r\n var uniqueResults = (0, Utilities_1.isNullOrUndefined)(options.unique) ? false : options.unique;\r\n // Handle cases where the user has asked for zero tickets (no idea why they would do this be we should trust them).\r\n if (tickets === 0) {\r\n return [];\r\n }\r\n // Now that we know out tickets value is not zero we should check that it is a valid natural number.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n var result = [];\r\n // Keep drawing tickets until we either reach the number of required tickets or we simply run out of tickets to draw.\r\n // We can run out of tickets to draw if 'options.redrawable' is explicity 'false' or we just had no participants when 'drawMultiple' was called.\r\n while (result.length < tickets && this._participants.length > 0) {\r\n result.push(this.draw(options));\r\n }\r\n // If the 'unique' draw option is set then we need to remove duplicates from the result list.\r\n if (uniqueResults) {\r\n // Create an array to store our unique results.\r\n var unique = [];\r\n // Iterate over all of our participants (with potential duplicates) and populate our array of unique values.\r\n for (var _i = 0, result_1 = result; _i < result_1.length; _i++) {\r\n var participant = result_1[_i];\r\n if (unique.indexOf(participant) === -1) {\r\n unique.push(participant);\r\n }\r\n }\r\n result = unique;\r\n }\r\n return result;\r\n };\r\n return Lotto;\r\n}());\r\nexports.Lotto = Lotto;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.createLotto = void 0;\r\nvar Lotto_1 = require(\"./Lotto\");\r\n/**\r\n * A function that creates and returns a Lotto instance.\r\n * @param participantsOrOptions An array of initial participants or options relating to the creation of a Lotto instance.\r\n * @returns A new Lotto instance.\r\n */\r\nfunction createLotto(participantsOrOptions) {\r\n // If no initial participants or lotto options were provided as an argument then we can just return a new lotto instance now.\r\n if (!participantsOrOptions) {\r\n return new Lotto_1.Lotto();\r\n }\r\n // Check whether we were provided with an array of initial participants or a lotto options object.\r\n if (Array.isArray(participantsOrOptions)) {\r\n // We are dealing with a pre-defined array of participants.\r\n var participants = participantsOrOptions;\r\n var lotto_1 = new Lotto_1.Lotto();\r\n // If the lotto participants have been defined upfront then we will need to add them all to our lotto instance now.\r\n participants.forEach(function (_a) {\r\n var participant = _a[0], tokens = _a[1];\r\n return lotto_1.add(participant, tokens);\r\n });\r\n // Return the Lotto instance.\r\n return lotto_1;\r\n }\r\n else {\r\n // We are dealing with some lotto options.\r\n var random = participantsOrOptions.random, participants = participantsOrOptions.participants;\r\n // Create a Lotto instance passing the custom RNG function to use in place of Math.random() (which could be undefined).\r\n var lotto_2 = new Lotto_1.Lotto(random);\r\n // If the lotto participants have been defined upfront as part of the options then we will need to add them all to our lotto instance now.\r\n if (participants) {\r\n participants.forEach(function (_a) {\r\n var participant = _a[0], tokens = _a[1];\r\n return lotto_2.add(participant, tokens);\r\n });\r\n }\r\n // Return the Lotto instance.\r\n return lotto_2;\r\n }\r\n}\r\nexports.createLotto = createLotto;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nvar createLotto_1 = require(\"./createLotto\");\r\nexports.default = createLotto_1.createLotto;\r\n", "import State from \"./State\";\nimport { validateDefinition } from \"./BehaviourTreeDefinitionValidator\";\nimport { convertMDSLToJSON } from \"./mdsl/MDSLDefinitionParser\";\nimport { BehaviourTree, FlattenedTreeNode } from \"./BehaviourTree\";\nimport { BehaviourTreeOptions } from \"./BehaviourTreeOptions\";\n\nexport { BehaviourTree, State, convertMDSLToJSON, validateDefinition };\nexport type { FlattenedTreeNode, BehaviourTreeOptions };\n", "/**\n * Enumeration of node state types.\n */\nexport enum State {\n READY = \"mistreevous.ready\",\n RUNNING = \"mistreevous.running\",\n SUCCEEDED = \"mistreevous.succeeded\",\n FAILED = \"mistreevous.failed\"\n}\n\nexport { State as default };\n\nexport type CompleteState = State.SUCCEEDED | State.FAILED;\nexport type AnyState = State.READY | State.RUNNING | CompleteState;\n", "import {\n NodeDefinition,\n RootNodeDefinition,\n DecoratorNodeDefinition,\n CompositeNodeDefinition,\n AnyNodeDefinition,\n BranchNodeDefinition\n} from \"./BehaviourTreeDefinition\";\n\n/**\n * A type guard function that returns true if the specified node satisfies the RootNodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the RootNodeDefinition type.\n */\nexport function isRootNode(node: NodeDefinition): node is RootNodeDefinition {\n return node.type === \"root\";\n}\n\n/**\n * A type guard function that returns true if the specified node satisfies the BranchNodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the BranchNodeDefinition type.\n */\nexport function isBranchNode(node: NodeDefinition): node is BranchNodeDefinition {\n return node.type === \"branch\";\n}\n\n/**\n * A type guard function that returns true if the specified node satisfies the NodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the NodeDefinition type.\n */\nexport function isLeafNode(node: NodeDefinition): node is NodeDefinition {\n return [\"branch\", \"action\", \"condition\", \"wait\"].includes(node.type);\n}\n\n/**\n * A type guard function that returns true if the specified node satisfies the DecoratorNodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the DecoratorNodeDefinition type.\n */\nexport function isDecoratorNode(node: NodeDefinition): node is DecoratorNodeDefinition {\n return [\"root\", \"repeat\", \"retry\", \"flip\", \"succeed\", \"fail\"].includes(node.type);\n}\n\n/**\n * A type guard function that returns true if the specified node satisfies the CompositeNodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the CompositeNodeDefinition type.\n */\nexport function isCompositeNode(node: NodeDefinition): node is CompositeNodeDefinition {\n return [\"sequence\", \"selector\", \"lotto\", \"parallel\"].includes(node.type);\n}\n\n/**\n * Flatten a node definition into an array of all of its nested node definitions.\n * @param nodeDefinition The node definition to flatten.\n * @returns An array of all of nested node definitions.\n */\nexport function flattenDefinition(nodeDefinition: AnyNodeDefinition): AnyNodeDefinition[] {\n const nodes: AnyNodeDefinition[] = [];\n\n const processNode = (currentNodeDefinition: AnyNodeDefinition) => {\n nodes.push(currentNodeDefinition);\n\n if (isCompositeNode(currentNodeDefinition)) {\n currentNodeDefinition.children.forEach(processNode);\n } else if (isDecoratorNode(currentNodeDefinition)) {\n processNode(currentNodeDefinition.child);\n }\n };\n\n processNode(nodeDefinition);\n\n return nodes;\n}\n\n/**\n * Determines whether the passed value is an integer.\n * @param value The value to check.\n * @returns Whether the passed value is an integer.\n */\nexport function isInteger(value: unknown): boolean {\n return typeof value === \"number\" && Math.floor(value) === value;\n}\n\n/**\n * Determines whether the passed value is null or undefined.\n * @param value The value to check.\n * @returns Whether the passed value is null or undefined.\n */\nexport function isNullOrUndefined(value: unknown): boolean {\n return typeof value === \"undefined\" || value === null;\n}\n", "/**\n * A type defining an object that holds a reference to substitued string literals parsed from the definition.\n */\nexport type StringLiteralPlaceholders = { [key: string]: string };\n\n/**\n * Pop the next raw token from the specified array of tokens and throw an error if it wasn't the expected one.\n * @param tokens The array of tokens.\n * @param expected An optional string or array or items, one of which must match the next popped token.\n * @returns The popped token.\n */\nexport function popAndCheck(tokens: string[], expected?: string | string[]): string {\n // Get and remove the next token.\n const popped = tokens.shift();\n\n // We were expecting another token but there aren't any.\n if (popped === undefined) {\n throw new Error(\"unexpected end of definition\");\n }\n\n // Do we have an expected token/tokens array?\n if (expected != undefined) {\n // Get an array of expected values, if the popped token matches any then we are all good.\n const expectedValues = typeof expected === \"string\" ? [expected] : expected;\n\n // Check whether the popped token matches at least one of our expected items.\n var tokenMatchesExpectation = expectedValues.some((item) => popped.toUpperCase() === item.toUpperCase());\n\n // Throw an error if the popped token didn't match any of our expected items.\n if (!tokenMatchesExpectation) {\n const expectationString = expectedValues.map((item) => \"'\" + item + \"'\").join(\" or \");\n throw new Error(\"unexpected token found. Expected \" + expectationString + \" but got '\" + popped + \"'\");\n }\n }\n\n // Return the popped token.\n return popped;\n}\n\n/**\n * Swaps out any node/attribute argument string literals with placeholders.\n * @param definition The definition.\n * @returns An object containing a mapping of placeholders to original string values as well as the processed definition string.\n */\nexport function substituteStringLiterals(definition: string): {\n placeholders: StringLiteralPlaceholders;\n processedDefinition: string;\n} {\n // Create an object to hold the mapping of placeholders to original string values.\n const placeholders: StringLiteralPlaceholders = {};\n\n // Replace any string literals wrapped with double quotes in our definition with placeholders to be processed later.\n const processedDefinition = definition.replace(/\\\"(\\\\.|[^\"\\\\])*\\\"/g, (match) => {\n var strippedMatch = match.substring(1, match.length - 1);\n var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch);\n\n // If we have no existing string literal match then create a new placeholder.\n if (!placeholder) {\n placeholder = `@@${Object.keys(placeholders).length}@@`;\n placeholders[placeholder] = strippedMatch;\n }\n\n return placeholder;\n });\n\n return { placeholders, processedDefinition };\n}\n\n/**\n * Parse the tree definition into an array of raw tokens.\n * @param definition The definition.\n * @returns An array of tokens parsed from the definition.\n */\nexport function parseTokensFromDefinition(definition: string): string[] {\n // Add some space around various important characters so that they can be plucked out easier as individual tokens.\n definition = definition.replace(/\\(/g, \" ( \");\n definition = definition.replace(/\\)/g, \" ) \");\n definition = definition.replace(/\\{/g, \" { \");\n definition = definition.replace(/\\}/g, \" } \");\n definition = definition.replace(/\\]/g, \" ] \");\n definition = definition.replace(/\\[/g, \" [ \");\n definition = definition.replace(/\\,/g, \" , \");\n\n // Split the definition into raw token form and return it.\n return definition.replace(/\\s+/g, \" \").trim().split(\" \");\n}\n", "import { StringLiteralPlaceholders, popAndCheck } from \"./MDSLUtilities\";\n\n/**\n * A type representing any node function argument.\n */\ntype Argument = {\n /**\n * The argument value.\n */\n value: T;\n /**\n * The argument type, used for validation.\n */\n type: string;\n};\n\ntype NullArgument = Argument & {\n type: \"null\";\n};\n\ntype BooleanArgument = Argument & {\n type: \"boolean\";\n};\n\ntype NumberArgument = Argument & {\n type: \"number\";\n /**\n * A flag defining whether the number argument value is a valid integer. (used for validation)\n */\n isInteger: boolean;\n};\n\ntype StringPlaceholderArgument = Argument & {\n type: \"string\";\n};\n\ntype IdentifierArgument = Argument & {\n type: \"identifier\";\n};\n\n/**\n * A type representing a reference to any node function argument.\n */\ntype AnyArgument = NullArgument | BooleanArgument | NumberArgument | StringPlaceholderArgument | IdentifierArgument;\n\n/**\n * Parse an array of argument definitions from the specified tokens array.\n * @param tokens The array tokens to parse the argument definitions from.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @param argumentValidator The argument validator function.\n * @param validationFailedMessage The exception message to throw if argument validation fails.\n * @returns An array of argument definitions parsed from the specified tokens array.\n */\nexport function parseArgumentTokens(\n tokens: string[],\n stringArgumentPlaceholders: StringLiteralPlaceholders\n): AnyArgument[] {\n const argumentList: AnyArgument[] = [];\n\n // If the next token is not a '[' or '(' then we have no arguments to parse.\n if (![\"[\", \"(\"].includes(tokens[0])) {\n return argumentList;\n }\n\n // Any lists of arguments will always be wrapped in '[]' for node arguments or '()' for attribute arguments.\n // We are looking for a '[' or '(' opener that wraps the argument tokens and the relevant closer.\n const closingToken = popAndCheck(tokens, [\"[\", \"(\"]) === \"[\" ? \"]\" : \")\";\n\n const argumentListTokens: string[] = [];\n\n // Grab all tokens between the '[' and ']' or '(' and ')'.\n while (tokens.length && tokens[0] !== closingToken) {\n // The next token is part of our arguments list.\n argumentListTokens.push(tokens.shift()!);\n }\n\n // Validate the order of the argument tokens. Each token must either be a ',' or a single argument that satisfies the validator.\n argumentListTokens.forEach((token, index) => {\n // Get whether this token should be an actual argument.\n const shouldBeArgumentToken = !(index & 1);\n\n // If the current token should be an actual argument then validate it, otherwise it should be a ',' token.\n if (shouldBeArgumentToken) {\n // Get the argument definition.\n const argumentDefinition = getArgumentDefinition(token, stringArgumentPlaceholders);\n\n // This is a valid argument!\n argumentList.push(argumentDefinition);\n } else {\n // The current token should be a ',' token.\n if (token !== \",\") {\n throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`);\n }\n }\n });\n\n // The arguments list should terminate with a ']' or ')' token, depending on the opener.\n popAndCheck(tokens, closingToken);\n\n // Return the arguments.\n return argumentList;\n}\n\n/**\n * Gets an argument value definition.\n * @param token The argument token.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @returns An argument value definition.\n */\nfunction getArgumentDefinition(token: string, stringArgumentPlaceholders: StringLiteralPlaceholders): AnyArgument {\n // Check whether the token represents a null value.\n if (token === \"null\") {\n return {\n value: null,\n type: \"null\"\n } as NullArgument;\n }\n\n // Check whether the token represents a boolean value.\n if (token === \"true\" || token === \"false\") {\n return {\n value: token === \"true\",\n type: \"boolean\"\n } as BooleanArgument;\n }\n\n // Check whether the token represents a number value.\n // TODO: Relies on broken isNaN - see MDN.\n // if (!Number.isNaN(token)) {\n if (!isNaN(token as any)) {\n return {\n value: parseFloat(token),\n isInteger: parseFloat(token) === parseInt(token, 10),\n type: \"number\"\n } as NumberArgument;\n }\n\n // Check whether the token is a placeholder (e.g. @@0@@) representing a string literal.\n if (token.match(/^@@\\d+@@$/g)) {\n return {\n value: stringArgumentPlaceholders[token].replace('\\\\\"', '\"'),\n type: \"string\"\n } as StringPlaceholderArgument;\n }\n\n // The only remaining option is that the argument value is an identifier.\n return {\n value: token,\n type: \"identifier\"\n } as IdentifierArgument;\n}\n", "import { NodeAttributeDefinition } from \"../BehaviourTreeDefinition\";\nimport { parseArgumentTokens } from \"./MDSLNodeArgumentParser\";\nimport { StringLiteralPlaceholders } from \"./MDSLUtilities\";\n\n/**\n * A type defining the attribute definitions of a node.\n */\ntype NodeAttributes = {\n while?: NodeAttributeDefinition;\n until?: NodeAttributeDefinition;\n entry?: NodeAttributeDefinition;\n exit?: NodeAttributeDefinition;\n step?: NodeAttributeDefinition;\n};\n\n/**\n * Parse any node attribute definitions from the specified tokens array.\n * @param tokens The array of remaining tokens.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @returns An object of attribute definitions defined by any directly following tokens.\n */\nexport function parseAttributeTokens(\n tokens: string[],\n stringArgumentPlaceholders: StringLiteralPlaceholders\n): NodeAttributes {\n const nodeAttributeNames: (keyof NodeAttributes)[] = [\"while\", \"until\", \"entry\", \"exit\", \"step\"];\n\n // Create an object to hold any attributes found.\n const attributes: NodeAttributes = {};\n\n // Try to get the name of the attribute for the next token.\n let nextAttributeName = tokens[0]?.toLowerCase() as keyof NodeAttributes;\n\n // Pull attribute tokens as well as their arguments off of the tokens stack until we have no more.\n while (nodeAttributeNames.includes(nextAttributeName)) {\n // Check to make sure that we have not already created an attribute definition of this type.\n if (attributes[nextAttributeName]) {\n throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`);\n }\n\n // Remove the attribute name token from the array of tokens.\n tokens.shift();\n\n // Grab the attribute arguments, assuming the first to be an identifier.\n const [attributeCallIdentifier, ...attributeArguments] = parseArgumentTokens(\n tokens,\n stringArgumentPlaceholders\n );\n\n // The first attribute argument has to be an identifer, this will reference an agent function.\n if (attributeCallIdentifier?.type !== \"identifier\") {\n throw new Error(\"expected agent function or registered function name identifier argument for attribute\");\n }\n\n // Any attribute arguments (other than the expected call identifier) must have a type of string, number, boolean or null.\n attributeArguments\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n `invalid attribute argument value '${arg.value}', must be string, number, boolean or null`\n );\n });\n\n // Create the attribute definition and add it to the object of attribute definitions found.\n attributes[nextAttributeName] = {\n call: attributeCallIdentifier.value,\n args: attributeArguments.map(({ value }) => value)\n };\n\n // Try to get the next attribute name token, as there could be multiple.\n nextAttributeName = tokens[0]?.toLowerCase() as keyof NodeAttributes;\n }\n\n return attributes;\n}\n", "import {\n ActionNodeDefinition,\n AnyChildNodeDefinition,\n AnyNodeDefinition,\n BranchNodeDefinition,\n ConditionNodeDefinition,\n FailNodeDefinition,\n FlipNodeDefinition,\n LottoNodeDefinition,\n ParallelNodeDefinition,\n RepeatNodeDefinition,\n RetryNodeDefinition,\n RootNodeDefinition,\n SelectorNodeDefinition,\n SequenceNodeDefinition,\n SucceedNodeDefinition,\n WaitNodeDefinition\n} from \"../BehaviourTreeDefinition\";\nimport {\n isCompositeNode,\n isDecoratorNode,\n isLeafNode,\n isNullOrUndefined,\n isRootNode\n} from \"../BehaviourTreeDefinitionUtilities\";\nimport { parseArgumentTokens } from \"./MDSLNodeArgumentParser\";\nimport { parseAttributeTokens } from \"./MDSLNodeAttributeParser\";\nimport {\n StringLiteralPlaceholders,\n parseTokensFromDefinition,\n popAndCheck,\n substituteStringLiterals\n} from \"./MDSLUtilities\";\n\n/**\n * Convert the MDSL tree definition string into an equivalent JSON definition.\n * @param definition The tree definition string as MDSL.\n * @returns The root node JSON definitions.\n */\nexport function convertMDSLToJSON(definition: string): RootNodeDefinition[] {\n // Swap out any node/attribute argument string literals with a placeholder and get a mapping of placeholders to original values as well as the processed definition.\n const { placeholders, processedDefinition } = substituteStringLiterals(definition);\n\n // Parse our definition definition string into an array of raw tokens.\n const tokens = parseTokensFromDefinition(processedDefinition);\n\n return convertTokensToJSONDefinition(tokens, placeholders);\n}\n\n/**\n * Converts the specified tree definition tokens into a JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The root node JSON definitions.\n */\nfunction convertTokensToJSONDefinition(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): RootNodeDefinition[] {\n // There must be at least 3 tokens for the tree definition to be valid. 'ROOT', '{' and '}'.\n if (tokens.length < 3) {\n throw new Error(\"invalid token count\");\n }\n\n // We should have a matching number of '{' and '}' tokens. If not, then there are scopes that have not been properly closed.\n if (tokens.filter((token) => token === \"{\").length !== tokens.filter((token) => token === \"}\").length) {\n throw new Error(\"scope character mismatch\");\n }\n\n // Create an array of tree stack arrays where root nodes will always be at the botton and the current composite/decorator node at the top.\n // There should be an element in this array for every root node defined and every element should be an array with a root note as the first element.\n // E.g. A definition with two root nodes defined:\n // [\n // [root, lotto, sequence],\n // [root, selector]\n // ]\n const treeStacks: [Partial, ...Partial[]][] = [];\n\n // Create an array of all root node definitions that we create.\n const rootNodes: Partial[] = [];\n\n // A helper function used to push node definitions onto the tree stack.\n const pushNode = (node: AnyNodeDefinition) => {\n // If the node is a root node then we need to create a new tree stack array with the root node at the root.\n if (isRootNode(node)) {\n // We need to double-check that this root node is not the child of another node.\n // We can do this by checking whether the top tree stack is not empty (contains an existing node)\n if (treeStacks[treeStacks.length - 1]?.length) {\n throw new Error(\"a root node cannot be the child of another node\");\n }\n\n // Add the root node definition to our array of all parsed root node definitions.\n rootNodes.push(node);\n\n // Add the root node definition to the root of a new tree stack.\n treeStacks.push([node]);\n\n return;\n }\n\n // All non-root nodes should be pushed after their root nodes so handle cases\n // where we may not have any tree stacks or our top-most tree stack is empty.\n if (!treeStacks.length || !treeStacks[treeStacks.length - 1].length) {\n throw new Error(\"expected root node at base of definition\");\n }\n\n // Get the current tree stack that we are populating.\n const topTreeStack = treeStacks[treeStacks.length - 1];\n\n // Get the top-most node in the current tree stack, this will be a composite/decorator node\n // for which we will populate its children array if composite or setting its child if a decorator.\n const topTreeStackTopNode = topTreeStack[topTreeStack.length - 1] as AnyNodeDefinition;\n\n // If the top-most node in the current root stack is a composite or decorator\n // node then the current node should be added as a child of the top-most node.\n if (isCompositeNode(topTreeStackTopNode)) {\n topTreeStackTopNode.children = topTreeStackTopNode.children || [];\n topTreeStackTopNode.children.push(node);\n } else if (isDecoratorNode(topTreeStackTopNode)) {\n // If the top node already has a child node set then throw an error as a decorator should only have a single child.\n if (topTreeStackTopNode.child) {\n throw new Error(\"a decorator node must only have a single child node\");\n }\n\n topTreeStackTopNode.child = node;\n }\n\n // If the node we are adding is also a composite or decorator node, then we should push it\n // onto the current tree stack, as subsequent nodes will be added as its child/children.\n if (!isLeafNode(node)) {\n topTreeStack.push(node);\n }\n };\n\n // A helper function used to pop the top-most node definition off of the tree stack and return it.\n const popNode = (): AnyNodeDefinition | null => {\n let poppedNode: AnyNodeDefinition | null = null;\n\n // Get the current tree stack that we are populating.\n const topTreeStack = treeStacks[treeStacks.length - 1];\n\n // Pop the top-most node in the current tree stack if there is one.\n if (topTreeStack.length) {\n poppedNode = topTreeStack.pop() as AnyNodeDefinition;\n }\n\n // We don't want any empty tree stacks in our stack of tree stacks.\n if (!topTreeStack.length) {\n treeStacks.pop();\n }\n\n return poppedNode;\n };\n\n // We should keep processing the raw tokens until we run out of them.\n while (tokens.length) {\n // Grab the next token.\n const token = tokens.shift()!;\n\n // How we create the next node depends on the current raw token value.\n switch (token.toUpperCase()) {\n case \"ROOT\": {\n pushNode(createRootNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"SUCCEED\": {\n pushNode(createSucceedNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"FAIL\": {\n pushNode(createFailNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"FLIP\": {\n pushNode(createFlipNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"REPEAT\": {\n pushNode(createRepeatNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"RETRY\": {\n pushNode(createRetryNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"SEQUENCE\": {\n pushNode(createSequenceNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"SELECTOR\": {\n pushNode(createSelectorNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"PARALLEL\": {\n pushNode(createParallelNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"LOTTO\": {\n pushNode(createLottoNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"ACTION\": {\n pushNode(createActionNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"CONDITION\": {\n pushNode(createConditionNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"WAIT\": {\n pushNode(createWaitNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"BRANCH\": {\n pushNode(createBranchNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"}\": {\n // The '}' character closes the current scope and means that we have to pop a node off of the current stack.\n const poppedNode = popNode();\n\n // Now that we have a node definition we can carry out any validation that may require the node to be fully populated.\n if (poppedNode) {\n validatePoppedNode(poppedNode);\n }\n\n break;\n }\n\n default: {\n throw new Error(`unexpected token: ${token}`);\n }\n }\n }\n\n return rootNodes as RootNodeDefinition[];\n}\n\n/**\n * Creates a root node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The root node JSON definition.\n */\nfunction createRootNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): RootNodeDefinition {\n // Create the root node definition.\n let node = {\n type: \"root\"\n } as Partial;\n\n // Parse any node arguments, we should only have one if any which will be an identifier argument for the root identifier.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // Check whether any node arguments were defined.\n if (nodeArguments.length) {\n // We should only have one argument, if any, which will be an identifier argument for the root identifier.\n if (nodeArguments.length === 1 && nodeArguments[0].type === \"identifier\") {\n // The root node identifier will be the first and only node argument value.\n node.id = nodeArguments[0].value as string;\n } else {\n throw new Error(\"expected single root name argument\");\n }\n }\n\n // Grab any node attribute definitions and spread them into the node definition.\n node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) };\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the root node definition.\n return node as RootNodeDefinition;\n}\n\n/**\n * Creates a succeed node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The succeed node JSON definition.\n */\nfunction createSucceedNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): SucceedNodeDefinition {\n const node = {\n type: \"succeed\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as SucceedNodeDefinition;\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the succeed node definition.\n return node;\n}\n\n/**\n * Creates a fail node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The fail node JSON definition.\n */\nfunction createFailNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): FailNodeDefinition {\n const node = {\n type: \"fail\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as FailNodeDefinition;\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the fail node definition.\n return node;\n}\n\n/**\n * Creates a flip node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The flip node JSON definition.\n */\nfunction createFlipNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): FlipNodeDefinition {\n const node = {\n type: \"flip\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as FlipNodeDefinition;\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the flip node definition.\n return node;\n}\n\n/**\n * Creates a repeat node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The repeat node JSON definition.\n */\nfunction createRepeatNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): RepeatNodeDefinition {\n let node = { type: \"repeat\" } as RepeatNodeDefinition;\n\n // Get the node arguments.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // The arguments of a repeat node are optional. We may have:\n // - No node arguments, in which case the repeat note will iterate indefinitely.\n // - One node argument which will be the explicit number of iterations to make.\n // - Two node arguments which define the min and max iteration bounds from which a random iteration count will be picked.\n if (nodeArguments.length) {\n // All repeat node arguments MUST be of type number and must be integer.\n nodeArguments\n .filter((arg) => arg.type !== \"number\" || !arg.isInteger)\n .forEach(() => {\n throw new Error(`repeat node iteration counts must be integer values`);\n });\n\n // We should have got one or two iteration counts.\n if (nodeArguments.length === 1) {\n // A static iteration count was defined.\n node.iterations = nodeArguments[0].value as number;\n\n // A repeat node must have a positive number of iterations if defined.\n if (node.iterations < 0) {\n throw new Error(\"a repeat node must have a positive number of iterations if defined\");\n }\n } else if (nodeArguments.length === 2) {\n // A minimum and maximum iteration count was defined.\n node.iterations = [nodeArguments[0].value as number, nodeArguments[1].value as number];\n\n // A repeat node must have a positive min and max iteration count if they are defined.\n if (node.iterations[0] < 0 || node.iterations[1] < 0) {\n throw new Error(\"a repeat node must have a positive minimum and maximum iteration count if defined\");\n }\n\n // A repeat node must not have an minimum iteration count that exceeds the maximum iteration count.\n if (node.iterations[0] > node.iterations[1]) {\n throw new Error(\n \"a repeat node must not have a minimum iteration count that exceeds the maximum iteration count\"\n );\n }\n } else {\n // An incorrect number of iteration counts was defined.\n throw new Error(\"invalid number of repeat node iteration count arguments defined\");\n }\n }\n\n // Grab any node attribute definitions and spread them into the node definition.\n node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) };\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the repeat node definition.\n return node;\n}\n\n/**\n * Creates a retry node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The retry node JSON definition.\n */\nfunction createRetryNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): RetryNodeDefinition {\n let node = { type: \"retry\" } as RetryNodeDefinition;\n\n // Get the node arguments.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // The arguments of a retry node are optional. We may have:\n // - No node arguments, in which case the retry note will attempt indefinitely.\n // - One node argument which will be the explicit number of attempts to make.\n // - Two node arguments which define the min and max attempt bounds from which a random attempt count will be picked.\n if (nodeArguments.length) {\n // All retry node arguments MUST be of type number and must be integer.\n nodeArguments\n .filter((arg) => arg.type !== \"number\" || !arg.isInteger)\n .forEach(() => {\n throw new Error(`retry node attempt counts must be integer values`);\n });\n\n // We should have got one or two attempt counts.\n if (nodeArguments.length === 1) {\n // A static attempt count was defined.\n node.attempts = nodeArguments[0].value as number;\n\n // A retry node must have a positive number of attempts if defined.\n if (node.attempts < 0) {\n throw new Error(\"a retry node must have a positive number of attempts if defined\");\n }\n } else if (nodeArguments.length === 2) {\n // A minimum and maximum attempt count was defined.\n node.attempts = [nodeArguments[0].value as number, nodeArguments[1].value as number];\n\n // A retry node must have a positive min and max attempts count if they are defined.\n if (node.attempts[0] < 0 || node.attempts[1] < 0) {\n throw new Error(\"a retry node must have a positive minimum and maximum attempt count if defined\");\n }\n\n // A retry node must not have a minimum attempt count that exceeds the maximum attempt count.\n if (node.attempts[0] > node.attempts[1]) {\n throw new Error(\n \"a retry node must not have a minimum attempt count that exceeds the maximum attempt count\"\n );\n }\n } else {\n // An incorrect number of attempt counts was defined.\n throw new Error(\"invalid number of retry node attempt count arguments defined\");\n }\n }\n\n // Grab any node attribute definitions and spread them into the node definition.\n node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) };\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the retry node definition.\n return node;\n}\n\n/**\n * Creates a sequence node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The sequence node JSON definition.\n */\nfunction createSequenceNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): SequenceNodeDefinition {\n const node = {\n type: \"sequence\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as SequenceNodeDefinition;\n\n // This is a composite node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the sequence node definition.\n return node;\n}\n\n/**\n * Creates a selector node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The selector node JSON definition.\n */\nfunction createSelectorNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): SelectorNodeDefinition {\n const node = {\n type: \"selector\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as SelectorNodeDefinition;\n\n // This is a composite node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the selector node definition.\n return node;\n}\n\n/**\n * Creates a parallel node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The parallel node JSON definition.\n */\nfunction createParallelNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): ParallelNodeDefinition {\n const node = {\n type: \"parallel\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as ParallelNodeDefinition;\n\n // This is a composite node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the parallel node definition.\n return node;\n}\n\n/**\n * Creates a lotto node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The lotto node JSON definition.\n */\nfunction createLottoNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): LottoNodeDefinition {\n // If any node arguments have been defined then they must be our weights.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // All lotto node arguments MUST be of type number and must be positive integers.\n nodeArguments\n .filter((arg) => arg.type !== \"number\" || !arg.isInteger || arg.value < 0)\n .forEach(() => {\n throw new Error(`lotto node weight arguments must be positive integer values`);\n });\n\n const node = {\n type: \"lotto\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as LottoNodeDefinition;\n\n // Apply the weights if any were defined.\n if (nodeArguments.length) {\n node.weights = nodeArguments.map(({ value }) => value) as number[];\n }\n\n // This is a composite node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the lotto node definition.\n return node;\n}\n\n/**\n * Creates an action node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The action node JSON definition.\n */\nfunction createActionNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): ActionNodeDefinition {\n // Parse any node arguments, we should have at least one which will be an identifier argument for the action name\n // and agent function to invoke for the action, all other arguments are to be passed as arguments to that function.\n const [actionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // Our first argument MUST be defined and be an identifier as we require an action name argument.\n if (actionNameIdentifier?.type !== \"identifier\") {\n throw new Error(\"expected action name identifier argument\");\n }\n\n // Only the first argument should have been an identifier, all agent function arguments must be string, number, boolean or null.\n agentFunctionArgs\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n `invalid action node argument value '${arg.value}', must be string, number, boolean or null`\n );\n });\n\n // Return the action node definition.\n return {\n type: \"action\",\n call: actionNameIdentifier.value,\n args: agentFunctionArgs.map(({ value }) => value),\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n };\n}\n\n/**\n * Creates a condition node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The condition node JSON definition.\n */\nfunction createConditionNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): ConditionNodeDefinition {\n // Parse any node arguments, we should have at least one which will be an identifier argument for the condition name\n // and agent function to invoke for the condition, all other arguments are to be passed as arguments to that function.\n const [conditionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // Our first argument MUST be defined and be an identifier as we require a condition name argument.\n if (conditionNameIdentifier?.type !== \"identifier\") {\n throw new Error(\"expected condition name identifier argument\");\n }\n\n // Only the first argument should have been an identifier, all agent function arguments must be string, number, boolean or null.\n agentFunctionArgs\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n `invalid condition node argument value '${arg.value}', must be string, number, boolean or null`\n );\n });\n\n // Return the condition node definition.\n return {\n type: \"condition\",\n call: conditionNameIdentifier.value,\n args: agentFunctionArgs.map(({ value }) => value),\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n };\n}\n\n/**\n * Creates a wait node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The wait node JSON definition.\n */\nfunction createWaitNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): WaitNodeDefinition {\n let node = { type: \"wait\" } as WaitNodeDefinition;\n\n // Get the node arguments.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // The arguments of a wait node are optional. We may have:\n // - No node arguments, in which case the wait will be indefinite until it is aborted.\n // - One node argument which will be the explicit duration of the wait.\n // - Two node arguments which define the min and max duration bounds from which a random duration will be picked.\n if (nodeArguments.length) {\n // All wait node arguments MUST be of type number and must be integer.\n nodeArguments\n .filter((arg) => arg.type !== \"number\" || !arg.isInteger)\n .forEach(() => {\n throw new Error(`wait node durations must be integer values`);\n });\n\n // We may have:\n // - One node argument which will be the explicit duration of the wait.\n // - Two node arguments which define the min and max duration bounds from which a random duration will be picked.\n // - Too many arguments, which is not valid.\n if (nodeArguments.length === 1) {\n // An explicit duration was defined.\n node.duration = nodeArguments[0].value as number;\n\n // If an explict duration was defined then it must be a positive number.\n if (node.duration < 0) {\n throw new Error(\"a wait node must have a positive duration\");\n }\n } else if (nodeArguments.length === 2) {\n // Min and max duration bounds were defined from which a random duration will be picked.\n node.duration = [nodeArguments[0].value as number, nodeArguments[1].value as number];\n\n // A wait node must have a positive min and max duration.\n if (node.duration[0] < 0 || node.duration[1] < 0) {\n throw new Error(\"a wait node must have a positive minimum and maximum duration\");\n }\n\n // A wait node must not have a minimum duration that exceeds the maximum duration.\n if (node.duration[0] > node.duration[1]) {\n throw new Error(\"a wait node must not have a minimum duration that exceeds the maximum duration\");\n }\n } else if (nodeArguments.length > 2) {\n // An incorrect number of duration arguments were defined.\n throw new Error(\"invalid number of wait node duration arguments defined\");\n }\n }\n\n // Return the wait node definition.\n return { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) };\n}\n\n/**\n * Creates a branch node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The branch node JSON definition.\n */\nfunction createBranchNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): BranchNodeDefinition {\n // Parse any node arguments, we should have one which will be an identifier argument for the root ref.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // We should have only a single identifer argument for a branch node, which is the root ref.\n if (nodeArguments.length !== 1 || nodeArguments[0].type !== \"identifier\") {\n throw new Error(\"expected single branch name argument\");\n }\n\n // Return the branch node definition.\n return { type: \"branch\", ref: nodeArguments[0].value };\n}\n\n/**\n * Validate a fully-populated node definition that was popped off of the tree stack.\n * @param definition The popped node to validate.\n */\nfunction validatePoppedNode(definition: AnyNodeDefinition): void {\n // Decorators MUST have a child defined.\n if (isDecoratorNode(definition) && isNullOrUndefined(definition.child)) {\n throw new Error(`a ${definition.type} node must have a single child node defined`);\n }\n\n // Composites MUST have at least one child defined.\n if (isCompositeNode(definition) && !definition.children?.length) {\n throw new Error(`a ${definition.type} node must have at least a single child node defined`);\n }\n\n // We need to make sure that lotto nodes that have weights defined have a number of weights matching the number of child nodes.\n if (definition.type === \"lotto\") {\n // Check whether a 'weights' property has been defined, if it has we expect it to be an array of weights.\n if (typeof definition.weights !== \"undefined\") {\n // Check that the weights property is an array of positive integers with an element for each child node element.\n if (definition.weights.length !== definition.children.length) {\n throw new Error(\n \"expected a number of weight arguments matching the number of child nodes for lotto node\"\n );\n }\n }\n }\n}\n", "import { RootNodeDefinition } from \"./BehaviourTreeDefinition\";\nimport { flattenDefinition, isBranchNode, isInteger } from \"./BehaviourTreeDefinitionUtilities\";\nimport { convertMDSLToJSON } from \"./mdsl/MDSLDefinitionParser\";\n\n/**\n * An object representing the result of validating a tree definition.\n */\nexport type DefinitionValidationResult = {\n /**\n * A flag defining whether validation succeeded.\n */\n succeeded: boolean;\n /**\n * A string containing the error message if validation did not succeed.\n */\n errorMessage?: string;\n /**\n * The definition as json if the validation was successful, or undefined if validation did not succeed.\n */\n json?: RootNodeDefinition[];\n};\n\n/**\n * Validates the specified behaviour tree definition in the form of JSON or MDSL, not taking any globally registered subtrees into consideration.\n * @param definition The behaviour tree definition in the form of JSON or MDSL.\n * @returns An object representing the result of validating the given tree definition.\n */\nexport function validateDefinition(definition: any): DefinitionValidationResult {\n // The definition must be defined.\n if (definition === null || typeof definition === \"undefined\") {\n return createValidationFailureResult(\"definition is null or undefined\");\n }\n\n // We are expecting a definition in one of three different forms:\n // - A string which we will assume is MDSL and we will parse this to JSON before validation.\n // - An array which we will assume is an array of root node definitions with at least one being the primary root node (no 'id' property)\n // - An object which we will assume is the primary root node and should not have an 'id' property.\n if (typeof definition === \"string\") {\n // The definition is a string which we can assume is MDSL, so attempt to validate it.\n return validateMDSLDefinition(definition);\n } else if (typeof definition === \"object\") {\n // The definition will either be an array (of root node definitions) or an object (the single primary root node definition).\n return validateJSONDefinition(definition);\n } else {\n return createValidationFailureResult(`unexpected definition type of '${typeof definition}'`);\n }\n}\n\n/**\n * Validates the specified behaviour tree definition in the form of MDSL.\n * @param definition The behaviour tree definition in the form of MDSL.\n * @returns An object representing the result of validating the given tree definition.\n */\nfunction validateMDSLDefinition(definition: string): DefinitionValidationResult {\n let rootNodeDefinitions;\n\n // The first thing the we need to do is to attempt to convert our MDSL into JSON.\n try {\n // The definition is a string which we can assume is MDSL, so attempt to parse it to a JSON definition in the form of an array of root node definitions.\n rootNodeDefinitions = convertMDSLToJSON(definition);\n } catch (exception) {\n // We failed to parse the JSON from the MDSL, this is likely to be the result of it not being a valid MDSL string.\n return createValidationFailureResult((exception as Error).message);\n }\n\n // Unpack all of the root node definitions into arrays of main ('id' defined) and sub ('id' not defined) root node definitions.\n const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === \"undefined\");\n const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === \"string\" && id.length > 0);\n\n // We should ALWAYS have exactly one root node definition without an 'id' property defined, which is out main root node definition.\n if (mainRootNodeDefinitions.length !== 1) {\n return createValidationFailureResult(\n \"expected single unnamed root node at base of definition to act as main root\"\n );\n }\n\n // We should never have duplicate 'id' properties across our sub root node definitions.\n const subRootNodeIdenitifers: string[] = [];\n for (const { id } of subRootNodeDefinitions) {\n // Have we already come across this 'id' property value?\n if (subRootNodeIdenitifers.includes(id!)) {\n return createValidationFailureResult(`multiple root nodes found with duplicate name '${id}'`);\n }\n\n subRootNodeIdenitifers.push(id!);\n }\n\n try {\n // Validate our branch -> subtree links and check for any circular dependencies, we don't care about checking for broken subtree links here.\n validateBranchSubtreeLinks(rootNodeDefinitions, false);\n } catch (exception) {\n return createValidationFailureResult((exception as Error).message);\n }\n\n // Our definition was valid!\n return {\n succeeded: true,\n json: rootNodeDefinitions\n };\n}\n\n/**\n * Validates the specified behaviour tree definition in the form of JSON.\n * @param definition The behaviour tree definition in the form of JSON.\n * @returns An object representing the result of validating the given tree definition.\n */\nexport function validateJSONDefinition(\n definition: RootNodeDefinition | RootNodeDefinition[]\n): DefinitionValidationResult {\n // The definition will either be an array (of root node definitions) or an object (the single primary root node definition).\n const rootNodeDefinitions = Array.isArray(definition) ? definition : [definition];\n\n // Iterate over our array of root nodes and call validateNode for each, passing an initial depth of 0, wrapped in a try catch to handle validation failures.\n try {\n rootNodeDefinitions.forEach((rootNodeDefinition) => validateNode(rootNodeDefinition, 0));\n } catch (error) {\n // Handle cases where we have caught a thrown Error and return a failure result with the error message.\n if (error instanceof Error) {\n return createValidationFailureResult(error.message);\n }\n\n // No idea what happened here!\n return createValidationFailureResult(`unexpected error: ${error}`);\n }\n\n // Unpack all of the root node definitions into arrays of main ('id' defined) and sub ('id' not defined) root node definitions.\n const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === \"undefined\");\n const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === \"string\" && id.length > 0);\n\n // We should ALWAYS have exactly one root node definition without an 'id' property defined, which is out main root node definition.\n if (mainRootNodeDefinitions.length !== 1) {\n return createValidationFailureResult(\n \"expected single root node without 'id' property defined to act as main root\"\n );\n }\n\n // We should never have duplicate 'id' properties across our sub root node definitions.\n const subRootNodeIdenitifers: string[] = [];\n for (const { id } of subRootNodeDefinitions) {\n // Have we already come across this 'id' property value?\n if (subRootNodeIdenitifers.includes(id!)) {\n return createValidationFailureResult(\n `multiple root nodes found with duplicate 'id' property value of '${id}'`\n );\n }\n\n subRootNodeIdenitifers.push(id!);\n }\n\n try {\n // Validate our branch -> subtree links and check for any circular dependencies, we don't care about checking for broken subtree links here.\n validateBranchSubtreeLinks(rootNodeDefinitions, false);\n } catch (exception) {\n return createValidationFailureResult((exception as Error).message);\n }\n\n // Our definition was valid!\n return {\n succeeded: true,\n json: rootNodeDefinitions\n };\n}\n\n/**\n * Validates the branch -> subtree links across all provided root node definitions.\n * This will not consider branch nodes that reference any globally registered subtrees unless includesGlobalSubtrees\n * is set to true, in which case we will also verify that there are no broken branch -> subtree links.\n * @param rootNodeDefinitions The array of root node definitions.\n * @param includesGlobalSubtrees A flag defining whether the array includes all global subtree root node definitions.\n */\nexport function validateBranchSubtreeLinks(rootNodeDefinitions: RootNodeDefinition[], includesGlobalSubtrees: boolean) {\n // Create a mapping of root node identifiers to other root nodes that they reference via branch nodes.\n // Below is an example of a mapping that includes a circular dependency (root => a => b => c => a)\n // [{ refs: [\"a\", \"b\"] }, { id: \"a\", refs: [\"b\"] }, { id: \"b\", refs: [\"c\"] }, { id: \"c\", refs: [\"a\"] }]\n const rootNodeMappings: { id: string | undefined; refs: string[] }[] = rootNodeDefinitions.map(\n (rootNodeDefinition) => ({\n id: rootNodeDefinition.id,\n refs: flattenDefinition(rootNodeDefinition)\n .filter(isBranchNode)\n .map(({ ref }) => ref)\n })\n );\n\n // A recursive function to walk through the mappings, keeping track of which root nodes we have visited in the form of a path of root node identifiers.\n const followRefs = (mapping: { id: string | undefined; refs: string[] }, path: (string | undefined)[] = []) => {\n // Have we found a circular dependency?\n if (path.includes(mapping.id)) {\n // We found a circular dependency! Get the bad path of root node identifiers.\n const badPath = [...path, mapping.id];\n\n // Create the formatted path value. [undefined, \"a\", \"b\", \"c\", \"a\"] would be formatted as \"a -> b -> c -> a\".\n const badPathFormatted = badPath.filter((element) => !!element).join(\" => \");\n\n // No need to continue, we found a circular dependency.\n throw new Error(`circular dependency found in branch node references: ${badPathFormatted}`);\n }\n\n for (const ref of mapping.refs) {\n // Find the mapping for the root node with an identifer matching the current ref.\n const subMapping = rootNodeMappings.find(({ id }) => id === ref);\n\n // We may not have a mapping for this ref, which is normal when we aren't considering all globally registered subtrees.\n if (subMapping) {\n followRefs(subMapping, [...path, mapping.id]);\n } else if (includesGlobalSubtrees) {\n // We found a reference to a root node that doesn't exist, which is a problem seeing as the root node definitons includes all globally registered subtrees.\n throw new Error(\n mapping.id\n ? `subtree '${mapping.id}' has branch node that references root node '${ref}' which has not been defined`\n : `primary tree has branch node that references root node '${ref}' which has not been defined`\n );\n }\n }\n };\n\n // Start looking for circular dependencies and broken references from the primary root node definition.\n followRefs(rootNodeMappings.find((mapping) => typeof mapping.id === \"undefined\")!);\n}\n\n/**\n * Validate an object that we expect to be a node definition.\n * @param definition An object that we expect to be a node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateNode(definition: any, depth: number): void {\n // Every node must be valid object and have a non-empty 'type' string property.\n if (typeof definition !== \"object\" || typeof definition.type !== \"string\" || definition.type.length === 0) {\n throw new Error(\n `node definition is not an object or 'type' property is not a non-empty string at depth '${depth}'`\n );\n }\n\n // If this node is at the very base of the definition then it MUST be a root node.\n if (depth === 0 && definition.type !== \"root\") {\n throw new Error(`expected root node at base of definition but got node of type '${definition.type}'`);\n }\n\n // How we validate this node definition will depend on its type.\n switch (definition.type) {\n case \"action\":\n validateActionNode(definition, depth);\n break;\n\n case \"condition\":\n validateConditionNode(definition, depth);\n break;\n\n case \"wait\":\n validateWaitNode(definition, depth);\n break;\n\n case \"branch\":\n validateBranchNode(definition, depth);\n break;\n\n case \"root\":\n validateRootNode(definition, depth);\n break;\n\n case \"succeed\":\n validateSucceedNode(definition, depth);\n break;\n\n case \"fail\":\n validateFailNode(definition, depth);\n break;\n\n case \"flip\":\n validateFlipNode(definition, depth);\n break;\n\n case \"repeat\":\n validateRepeatNode(definition, depth);\n break;\n\n case \"retry\":\n validateRetryNode(definition, depth);\n break;\n\n case \"sequence\":\n validateSequenceNode(definition, depth);\n break;\n\n case \"selector\":\n validateSelectorNode(definition, depth);\n break;\n\n case \"parallel\":\n validateParallelNode(definition, depth);\n break;\n\n case \"lotto\":\n validateLottoNode(definition, depth);\n break;\n\n default:\n throw new Error(`unexpected node type of '${definition.type}' at depth '${depth}'`);\n }\n}\n\n/**\n * Validate any attributes for a given node definition.\n * @param definition The node definition.\n * @param depth The depth of the node in the behaviour tree definition.\n */\nfunction validateNodeAttributes(definition: any, depth: number): void {\n // Validate each of the attribute types for this node.\n [\"while\", \"until\", \"entry\", \"exit\", \"step\"].forEach((attributeName) => {\n // Attempt to grab the definition for the current attribute from the node definition.\n const attributeDefinition = definition[attributeName];\n\n // All node attributes are optional, so there is nothing to do if the current attribute is not defined.\n if (typeof attributeDefinition === \"undefined\") {\n return;\n }\n\n // The attribute definition must be an object.\n if (typeof attributeDefinition !== \"object\") {\n throw new Error(\n `expected attribute '${attributeName}' to be an object for '${definition.type}' node at depth '${depth}'`\n );\n }\n\n // The 'call' property must be defined for any attribute definition.\n if (typeof attributeDefinition.call !== \"string\" || attributeDefinition.call.length === 0) {\n throw new Error(\n `expected 'call' property for attribute '${attributeName}' to be a non-empty string for '${definition.type}' node at depth '${depth}'`\n );\n }\n\n // If any node attribute arguments have been defined then they must have been defined in an array.\n if (typeof attributeDefinition.args !== \"undefined\" && !Array.isArray(attributeDefinition.args)) {\n throw new Error(\n `expected 'args' property for attribute '${attributeName}' to be an array for '${definition.type}' node at depth '${depth}'`\n );\n }\n });\n}\n\n/**\n * Validate an object that we expect to be a root node definition.\n * @param definition An object that we expect to be a root node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateRootNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"root\") {\n throw new Error(\"expected node type of 'root' for root node\");\n }\n\n // A root node cannot be the child of another node.\n if (depth > 0) {\n throw new Error(\"a root node cannot be the child of another node\");\n }\n\n // Check that, if the root node 'id' property is defined, it is a non-empty string.\n if (typeof definition.id !== \"undefined\" && (typeof definition.id !== \"string\" || definition.id.length === 0)) {\n throw new Error(\"expected non-empty string for 'id' property if defined for root node\");\n }\n\n // A root node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(\"expected property 'child' to be defined for root node\");\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a succeed node definition.\n * @param definition An object that we expect to be a succeed node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateSucceedNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"succeed\") {\n throw new Error(`expected node type of 'succeed' for succeed node at depth '${depth}'`);\n }\n\n // A succeed node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for succeed node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a fail node definition.\n * @param definition An object that we expect to be a fail node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateFailNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"fail\") {\n throw new Error(`expected node type of 'fail' for fail node at depth '${depth}'`);\n }\n\n // A fail node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for fail node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a flip node definition.\n * @param definition An object that we expect to be a flip node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateFlipNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"flip\") {\n throw new Error(`expected node type of 'flip' for flip node at depth '${depth}'`);\n }\n\n // A flip node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for flip node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a repeat node definition.\n * @param definition An object that we expect to be a repeat node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateRepeatNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"repeat\") {\n throw new Error(`expected node type of 'repeat' for repeat node at depth '${depth}'`);\n }\n\n // A repeat node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for repeat node at depth '${depth}'`);\n }\n\n // Check whether an 'iterations' property has been defined, it may not have been if this node is to repeat indefinitely.\n if (typeof definition.iterations !== \"undefined\") {\n if (Array.isArray(definition.iterations)) {\n // Check whether any elements of the array are not integer values.\n const containsNonInteger = !!definition.iterations.filter((value: unknown) => !isInteger(value)).length;\n\n // If the 'iterations' property is an array then it MUST contain two integer values.\n if (definition.iterations.length !== 2 || containsNonInteger) {\n throw new Error(\n `expected array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n\n // A repeat node must have a positive min and max iterations count if they are defined.\n if (definition.iterations[0] < 0 || definition.iterations[1] < 0) {\n throw new Error(\n `expected positive minimum and maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n\n // A repeat node must not have a minimum iterations count that exceeds the maximum iterations count.\n if (definition.iterations[0] > definition.iterations[1]) {\n throw new Error(\n `expected minimum iterations count that does not exceed the maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n } else if (isInteger(definition.iterations)) {\n // A repeat node must have a positive number of iterations if defined.\n if (definition.iterations < 0) {\n throw new Error(\n `expected positive iterations count for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n } else {\n throw new Error(\n `expected integer value or array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a retry node definition.\n * @param definition An object that we expect to be a retry node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateRetryNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"retry\") {\n throw new Error(`expected node type of 'retry' for retry node at depth '${depth}'`);\n }\n\n // A retry node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for retry node at depth '${depth}'`);\n }\n\n // Check whether an 'attempts' property has been defined, it may not have been if this node is to retry indefinitely.\n if (typeof definition.attempts !== \"undefined\") {\n if (Array.isArray(definition.attempts)) {\n // Check whether any elements of the array are not integer values.\n const containsNonInteger = !!definition.attempts.filter((value: unknown) => !isInteger(value)).length;\n\n // If the 'attempts' property is an array then it MUST contain two integer values.\n if (definition.attempts.length !== 2 || containsNonInteger) {\n throw new Error(\n `expected array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n\n // A retry node must have a positive min and max attempts count if they are defined.\n if (definition.attempts[0] < 0 || definition.attempts[1] < 0) {\n throw new Error(\n `expected positive minimum and maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n\n // A retry node must not have a minimum attempts count that exceeds the maximum attempts count.\n if (definition.attempts[0] > definition.attempts[1]) {\n throw new Error(\n `expected minimum attempts count that does not exceed the maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n } else if (isInteger(definition.attempts)) {\n // A retry node must have a positive number of attempts if defined.\n if (definition.attempts < 0) {\n throw new Error(\n `expected positive attempts count for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n } else {\n throw new Error(\n `expected integer value or array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a branch node definition.\n * @param definition An object that we expect to be a branch node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateBranchNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"branch\") {\n throw new Error(`expected node type of 'branch' for branch node at depth '${depth}'`);\n }\n\n // Check that the branch node 'ref' property is defined and is a non-empty string.\n if (typeof definition.ref !== \"string\" || definition.ref.length === 0) {\n throw new Error(`expected non-empty string for 'ref' property for branch node at depth '${depth}'`);\n }\n\n // It is invalid to define guard attributes for a branch node as they should be defined on the referenced root node.\n [\"while\", \"until\"].forEach((attributeName) => {\n if (typeof definition[attributeName] !== \"undefined\") {\n throw new Error(\n `guards should not be defined for branch nodes but guard '${attributeName}' was defined for branch node at depth '${depth}'`\n );\n }\n });\n\n // It is invalid to define callback attributes for a branch node as they should be defined on the referenced root node.\n [\"entry\", \"exit\", \"step\"].forEach((attributeName) => {\n if (typeof definition[attributeName] !== \"undefined\") {\n throw new Error(\n `callbacks should not be defined for branch nodes but callback '${attributeName}' was defined for branch node at depth '${depth}'`\n );\n }\n });\n}\n\n/**\n * Validate an object that we expect to be a action node definition.\n * @param definition An object that we expect to be a action node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateActionNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"action\") {\n throw new Error(`expected node type of 'action' for action node at depth '${depth}'`);\n }\n\n // The 'call' property must be defined for a action node definition.\n if (typeof definition.call !== \"string\" || definition.call.length === 0) {\n throw new Error(`expected non-empty string for 'call' property of action node at depth '${depth}'`);\n }\n\n // If any action function arguments have been defined then they must have been defined in an array.\n if (typeof definition.args !== \"undefined\" && !Array.isArray(definition.args)) {\n throw new Error(`expected array for 'args' property if defined for action node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n}\n\n/**\n * Validate an object that we expect to be a condition node definition.\n * @param definition An object that we expect to be a condition node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateConditionNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"condition\") {\n throw new Error(`expected node type of 'condition' for condition node at depth '${depth}'`);\n }\n\n // The 'call' property must be defined for a condition node definition.\n if (typeof definition.call !== \"string\" || definition.call.length === 0) {\n throw new Error(`expected non-empty string for 'call' property of condition node at depth '${depth}'`);\n }\n\n // If any condition function arguments have been defined then they must have been defined in an array.\n if (typeof definition.args !== \"undefined\" && !Array.isArray(definition.args)) {\n throw new Error(`expected array for 'args' property if defined for condition node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n}\n\n/**\n * Validate an object that we expect to be a wait node definition.\n * @param definition An object that we expect to be a wait node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateWaitNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"wait\") {\n throw new Error(`expected node type of 'wait' for wait node at depth '${depth}'`);\n }\n\n // Check whether a 'duration' property has been defined, it may not have been if this node is to wait indefinitely.\n if (typeof definition.duration !== \"undefined\") {\n if (Array.isArray(definition.duration)) {\n // Check whether any elements of the array are not integer values.\n const containsNonInteger = !!definition.duration.filter((value: unknown) => !isInteger(value)).length;\n\n // If the 'duration' property is an array then it MUST contain two integer values.\n if (definition.duration.length !== 2 || containsNonInteger) {\n throw new Error(\n `expected array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n\n // A wait node must have a positive min and max duration value if they are defined.\n if (definition.duration[0] < 0 || definition.duration[1] < 0) {\n throw new Error(\n `expected positive minimum and maximum duration for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n\n // A wait node must not have a minimum duration value that exceeds the maximum duration value.\n if (definition.duration[0] > definition.duration[1]) {\n throw new Error(\n `expected minimum duration value that does not exceed the maximum duration value for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n } else if (isInteger(definition.duration)) {\n // A wait node must have a positive duration value if defined.\n if (definition.duration < 0) {\n throw new Error(\n `expected positive duration value for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n } else {\n throw new Error(\n `expected integer value or array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n}\n\n/**\n * Validate an object that we expect to be a sequence node definition.\n * @param definition An object that we expect to be a sequence node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateSequenceNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"sequence\") {\n throw new Error(`expected node type of 'sequence' for sequence node at depth '${depth}'`);\n }\n\n // A sequence node is a composite node, so must have a children nodes array defined.\n if (!Array.isArray(definition.children) || definition.children.length === 0) {\n throw new Error(`expected non-empty 'children' array to be defined for sequence node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child nodes of this composite node.\n definition.children.forEach((child: any) => validateNode(child, depth + 1));\n}\n\n/**\n * Validate an object that we expect to be a selector node definition.\n * @param definition An object that we expect to be a selector node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateSelectorNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"selector\") {\n throw new Error(`expected node type of 'selector' for selector node at depth '${depth}'`);\n }\n\n // A selector node is a composite node, so must have a children nodes array defined.\n if (!Array.isArray(definition.children) || definition.children.length === 0) {\n throw new Error(`expected non-empty 'children' array to be defined for selector node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child nodes of this composite node.\n definition.children.forEach((child: any) => validateNode(child, depth + 1));\n}\n\n/**\n * Validate an object that we expect to be a parallel node definition.\n * @param definition An object that we expect to be a parallel node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateParallelNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"parallel\") {\n throw new Error(`expected node type of 'parallel' for parallel node at depth '${depth}'`);\n }\n\n // A parallel node is a composite node, so must have a children nodes array defined.\n if (!Array.isArray(definition.children) || definition.children.length === 0) {\n throw new Error(`expected non-empty 'children' array to be defined for parallel node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child nodes of this composite node.\n definition.children.forEach((child: any) => validateNode(child, depth + 1));\n}\n\n/**\n * Validate an object that we expect to be a lotto node definition.\n * @param definition An object that we expect to be a lotto node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateLottoNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"lotto\") {\n throw new Error(`expected node type of 'lotto' for lotto node at depth '${depth}'`);\n }\n\n // A lotto node is a composite node, so must have a children nodes array defined.\n if (!Array.isArray(definition.children) || definition.children.length === 0) {\n throw new Error(`expected non-empty 'children' array to be defined for lotto node at depth '${depth}'`);\n }\n\n // Check whether a 'weights' property has been defined, if it has we expect it to be an array of weights.\n if (typeof definition.weights !== \"undefined\") {\n // Check that the weights property is an array of positive integers with an element for each child node element.\n if (\n !Array.isArray(definition.weights) ||\n definition.weights.length !== definition.children.length ||\n definition.weights.filter((value: unknown) => !isInteger(value)).length ||\n definition.weights.filter((value: number) => value < 0).length\n ) {\n throw new Error(\n `expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '${depth}'`\n );\n }\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child nodes of this composite node.\n definition.children.forEach((child: any) => validateNode(child, depth + 1));\n}\n\n/**\n * A helper function to create a failure validation result with the given error message.\n * @param errorMessage The validation failure error message.\n * @returns A failure validation result with the given error message.\n */\nfunction createValidationFailureResult(errorMessage: string): DefinitionValidationResult {\n return { succeeded: false, errorMessage };\n}\n", "import { ActionResult, Agent, GlobalFunction } from \"./Agent\";\nimport { RootNodeDefinition } from \"./BehaviourTreeDefinition\";\n\nexport type InvokerFunction = (args: any[]) => ActionResult | boolean;\n\n/**\n * A singleton used to store and lookup registered functions and subtrees.\n */\nexport default class Lookup {\n /**\n * The object holding any registered functions keyed on function name.\n */\n private static registeredFunctions: { [key: string]: GlobalFunction } = {};\n /**\n * The object holding any registered subtree root node definitions keyed on tree name.\n */\n private static registeredSubtrees: { [key: string]: RootNodeDefinition } = {};\n\n /**\n * Gets the function with the specified name.\n * @param name The name of the function.\n * @returns The function with the specified name.\n */\n public static getFunc(name: string): GlobalFunction {\n return this.registeredFunctions[name];\n }\n\n /**\n * Sets the function with the specified name for later lookup.\n * @param name The name of the function.\n * @param func The function.\n */\n public static setFunc(name: string, func: GlobalFunction): void {\n this.registeredFunctions[name] = func;\n }\n\n /**\n * Gets the function invoker for the specified agent and function name.\n * If a function with the specified name exists on the agent object then it will\n * be returned, otherwise we will then check the registered functions for a match.\n * @param agent The agent instance that this behaviour tree is modelling behaviour for.\n * @param name The function name.\n * @returns The function invoker for the specified agent and function name.\n */\n static getFuncInvoker(agent: Agent, name: string): InvokerFunction | null {\n // Check whether the agent contains the specified function.\n const agentFunction = agent[name];\n if (agentFunction && typeof agentFunction === \"function\") {\n return (args: any[]) => agentFunction.apply(agent, args);\n }\n\n // The agent does not contain the specified function but it may have been registered at some point.\n if (this.registeredFunctions[name] && typeof this.registeredFunctions[name] === \"function\") {\n const registeredFunction = this.registeredFunctions[name];\n return (args: any[]) => registeredFunction(agent, ...args);\n }\n\n // We have no function to invoke.\n return null;\n }\n\n /**\n * Gets all registered subtree root node definitions.\n */\n static getSubtrees(): { [key: string]: RootNodeDefinition } {\n return this.registeredSubtrees;\n }\n\n /**\n * Sets the subtree with the specified name for later lookup.\n * @param name The name of the subtree.\n * @param subtree The subtree.\n */\n static setSubtree(name: string, subtree: RootNodeDefinition) {\n this.registeredSubtrees[name] = subtree;\n }\n\n /**\n * Removes the registered function or subtree with the specified name.\n * @param name The name of the registered function or subtree.\n */\n static remove(name: string) {\n delete this.registeredFunctions[name];\n delete this.registeredSubtrees[name];\n }\n\n /**\n * Remove all registered functions and subtrees.\n */\n static empty() {\n this.registeredFunctions = {};\n this.registeredSubtrees = {};\n }\n}\n", "import Node from \"../../nodes/Node\";\n\n/**\n * An exception thrown when evaluating node guard path conditions and a conditions fails.\n */\nexport default class GuardUnsatisifedException extends Error {\n /**\n * @param source The node at which a guard condition failed.\n */\n constructor(private source: Node) {\n super(\"A guard path condition has failed\");\n }\n\n /**\n * Gets whether the specified node is the node at which a guard condition failed.\n * @param node The node to check against the source node.\n * @returns Whether the specified node is the node at which a guard condition failed.\n */\n isSourceNode = (node: Node) => node === this.source;\n}\n", "import { Agent } from \"../../Agent\";\nimport Guard from \"./Guard\";\nimport Node from \"../../nodes/Node\";\nimport GuardUnsatisifedException from \"./GuardUnsatisifedException\";\n\nexport type GuardPathPart = {\n node: Node;\n guards: Guard[];\n};\n\n/**\n * Represents a path of node guards along a root-to-leaf tree path.\n */\nexport default class GuardPath {\n /**\n * @param nodes An array of objects defining a node instance -> guard link, ordered by node depth.\n */\n constructor(private nodes: GuardPathPart[]) {}\n\n /**\n * Evaluate guard conditions for all guards in the tree path, moving outwards from the root.\n * @param agent The agent, required for guard evaluation.\n * @returns An evaluation results object.\n */\n evaluate = (agent: Agent) => {\n // We need to evaluate guard conditions for nodes up the tree, moving outwards from the root.\n for (const details of this.nodes) {\n // There can be multiple guards per node.\n for (const guard of details.guards) {\n // Check whether the guard condition passes, and throw an exception if not.\n if (!guard.isSatisfied(agent)) {\n throw new GuardUnsatisifedException(details.node);\n }\n }\n }\n };\n}\n", "import { BehaviourTreeOptions } from \"../BehaviourTreeOptions\";\nimport State, { AnyState } from \"../State\";\nimport { Agent } from \"../Agent\";\nimport Leaf from \"./leaf/Leaf\";\nimport Attribute from \"../attributes/Attribute\";\nimport Entry from \"../attributes/callbacks/Entry\";\nimport Exit from \"../attributes/callbacks/Exit\";\nimport Step from \"../attributes/callbacks/Step\";\nimport Guard from \"../attributes/guards/Guard\";\nimport GuardPath from \"../attributes/guards/GuardPath\";\nimport GuardUnsatisifedException from \"../attributes/guards/GuardUnsatisifedException\";\n\n/**\n * A base node.\n */\nexport default abstract class Node {\n /**\n * The node uid.\n */\n private readonly uid: string = createNodeUid();\n /**\n * The node state.\n */\n private state: AnyState = State.READY;\n /**\n * The guard path to evaluate as part of a node update.\n */\n private guardPath: GuardPath | undefined;\n\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param args The node argument definitions.\n */\n constructor(private type: string, private attributes: Attribute[], private args: any[]) {}\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected abstract onUpdate(agent: Agent, options: BehaviourTreeOptions): void;\n\n /**\n * Gets the name of the node.\n */\n public abstract getName(): string;\n\n /**\n * Gets whether this node is a leaf node.\n */\n public abstract isLeafNode: () => this is Leaf;\n\n /**\n * Gets/Sets the state of the node.\n */\n getState = (): AnyState => this.state;\n setState = (value: AnyState): void => {\n this.state = value;\n };\n\n /**\n * Gets the unique id of the node.\n */\n getUid = () => this.uid;\n\n /**\n * Gets the type of the node.\n */\n getType = () => this.type;\n\n /**\n * Gets the node attributes.\n */\n getAttributes = () => this.attributes;\n\n /**\n * Gets the node arguments.\n */\n getArguments = () => this.args;\n\n /**\n * Gets the node attribute with the specified type, or null if it does not exist.\n */\n getAttribute(type: \"entry\" | \"ENTRY\"): Entry;\n getAttribute(type: \"exit\" | \"EXIT\"): Exit;\n getAttribute(type: \"step\" | \"STEP\"): Step;\n getAttribute(type: string): Attribute {\n return (\n this.getAttributes().filter((decorator) => decorator.type.toUpperCase() === type.toUpperCase())[0] || null\n );\n }\n\n /**\n * Gets the node attributes.\n */\n getGuardAttributes = (): Guard[] => this.getAttributes().filter((decorator) => decorator.isGuard()) as Guard[];\n\n /**\n * Sets the guard path to evaluate as part of a node update.\n */\n setGuardPath = (value: GuardPath) => (this.guardPath = value);\n\n /**\n * Gets whether a guard path is assigned to this node.\n */\n hasGuardPath = () => !!this.guardPath;\n\n /**\n * Gets whether this node is in the specified state.\n * @param value The value to compare to the node state.\n */\n public is(value: AnyState): boolean {\n return this.state === value;\n }\n\n /**\n * Reset the state of the node.\n */\n public reset(): void {\n this.setState(State.READY);\n }\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n public abort(agent: Agent): void {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n }\n\n /**\n * Update the node.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n * @returns The result of the update.\n */\n public update(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is already in a 'SUCCEEDED' or 'FAILED' state then there is nothing to do.\n if (this.is(State.SUCCEEDED) || this.is(State.FAILED)) {\n return;\n }\n\n try {\n // Evaluate all of the guard path conditions for the current tree path.\n this.guardPath!.evaluate(agent);\n\n // If this node is in the READY state then call the ENTRY for this node if it exists.\n if (this.is(State.READY)) {\n this.getAttribute(\"entry\")?.callAgentFunction(agent);\n }\n\n this.getAttribute(\"step\")?.callAgentFunction(agent);\n\n // Do the actual update.\n this.onUpdate(agent, options);\n\n // If this node is now in a 'SUCCEEDED' or 'FAILED' state then call the EXIT for this node if it exists.\n if (this.is(State.SUCCEEDED) || this.is(State.FAILED)) {\n this.getAttribute(\"exit\")?.callAgentFunction(agent, this.is(State.SUCCEEDED), false);\n }\n } catch (error) {\n // If the error is a GuardUnsatisfiedException then we need to determine if this node is the source.\n if (error instanceof GuardUnsatisifedException && error.isSourceNode(this)) {\n // Abort the current node.\n this.abort(agent);\n\n // Any node that is the source of an abort will be a failed node.\n this.setState(State.FAILED);\n } else {\n throw error;\n }\n }\n }\n}\n\n/**\n * Create a randomly generated node uid.\n * @returns A randomly generated node uid.\n */\nfunction createNodeUid(): string {\n var S4 = function () {\n return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);\n };\n return S4() + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + S4() + S4();\n}\n", "import Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A composite node that wraps child nodes.\n */\nexport default abstract class Composite extends Node {\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(type: string, attributes: Attribute[], protected children: Node[]) {\n super(type, attributes, []);\n }\n\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => false;\n\n /**\n * Gets the children of this node.\n */\n getChildren = () => this.children;\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the state of any child nodes.\n this.getChildren().forEach((child) => child.reset());\n };\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n abort = (agent: Agent) => {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Abort any child nodes.\n this.getChildren().forEach((child) => child.abort(agent));\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n };\n}\n", "import Composite from \"./Composite\";\nimport State from \"../../State\";\nimport Node from \"../Node\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A PARALLEL node.\n * The child nodes are executed concurrently until one fails or all succeed.\n */\nexport default class Parallel extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], children: Node[]) {\n super(\"parallel\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Keep a count of the number of succeeded child nodes.\n let succeededCount = 0;\n\n let hasChildFailed = false;\n\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then we should move on to the next child.\n if (child.getState() === State.SUCCEEDED) {\n // The child node has succeeded, keep track of this to determine if all children have.\n succeededCount++;\n\n // The child node succeeded, but we have not finished checking every child node yet.\n continue;\n }\n\n // If the current child has a state of 'FAILED' then this node is also a 'FAILED' node.\n if (child.getState() === State.FAILED) {\n hasChildFailed = true;\n\n // There is no need to check the rest of the children.\n break;\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() !== State.RUNNING) {\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n if (hasChildFailed) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // Abort every running child.\n for (const child of this.children) {\n if (child.getState() === State.RUNNING) {\n child.abort(agent);\n }\n }\n } else {\n // If all children have succeeded then this node has also succeeded, otherwise it is still running.\n this.setState(succeededCount === this.children.length ? State.SUCCEEDED : State.RUNNING);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"PARALLEL\";\n}\n", "import Composite from \"./Composite\";\nimport Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A SELECTOR node.\n * The child nodes are executed in sequence until one succeeds or all fail.\n */\nexport default class Selector extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], protected children: Node[]) {\n super(\"selector\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then this node is also a 'SUCCEEDED' node.\n if (child.getState() === State.SUCCEEDED) {\n // This node is a 'SUCCEEDED' node.\n this.setState(State.SUCCEEDED);\n\n // There is no need to check the rest of the selector nodes.\n return;\n }\n\n // If the current child has a state of 'FAILED' then we should move on to the next child.\n if (child.getState() === State.FAILED) {\n // Find out if the current child is the last one in the selector.\n // If it is then this sequence node has also failed.\n if (this.children.indexOf(child) === this.children.length - 1) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // There is no need to check the rest of the selector as we have completed it.\n return;\n } else {\n // The child node failed, try the next one.\n continue;\n }\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() === State.RUNNING) {\n // This node is a 'RUNNING' node.\n this.setState(State.RUNNING);\n\n // There is no need to check the rest of the selector as the current child is still running.\n return;\n }\n\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SELECTOR\";\n}\n", "import Composite from \"./Composite\";\nimport Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A SEQUENCE node.\n * The child nodes are executed in sequence until one fails or all succeed.\n */\nexport default class Sequence extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], protected children: Node[]) {\n super(\"sequence\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then we should move on to the next child.\n if (child.getState() === State.SUCCEEDED) {\n // Find out if the current child is the last one in the sequence.\n // If it is then this sequence node has also succeeded.\n if (this.children.indexOf(child) === this.children.length - 1) {\n // This node is a 'SUCCEEDED' node.\n this.setState(State.SUCCEEDED);\n\n // There is no need to check the rest of the sequence as we have completed it.\n return;\n } else {\n // The child node succeeded, but we have not finished the sequence yet.\n continue;\n }\n }\n\n // If the current child has a state of 'FAILED' then this node is also a 'FAILED' node.\n if (child.getState() === State.FAILED) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // There is no need to check the rest of the sequence.\n return;\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() === State.RUNNING) {\n // This node is a 'RUNNING' node.\n this.setState(State.RUNNING);\n\n // There is no need to check the rest of the sequence as the current child is still running.\n return;\n }\n\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SEQUENCE\";\n}\n", "import createLotto from \"lotto-draw\";\n\nimport Node from \"../Node\";\nimport Composite from \"./Composite\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A LOTTO node.\n * A winning child is picked on the initial update of this node, based on ticket weighting.\n * The state of this node will match the state of the winning child.\n */\nexport default class Lotto extends Composite {\n /**\n * @param attributes The node attributes.\n * @param weights The child node weights.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], private weights: number[] | undefined, children: Node[]) {\n super(\"lotto\", attributes, children);\n }\n\n /**\n * The child node selected to be the active one.\n */\n private selectedChild: Node | undefined;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to pick a winning child node.\n if (this.is(State.READY)) {\n // Create a lotto draw with which to randomly pick a child node to become the active one.\n const lottoDraw = createLotto({\n // Hook up the optional 'random' behaviour tree function option to the one used by 'lotto-draw'.\n random: options.random,\n // Pass in each child node as a participant in the lotto draw with their respective ticket count.\n participants: this.children.map((child, index) => [child, this.weights?.[index] || 1])\n });\n\n // Randomly pick a child based on ticket weighting, this will become the active child for this composite node.\n this.selectedChild = lottoDraw.draw() || undefined;\n }\n\n // If something went wrong and we don't have an active child then we should throw an error.\n if (!this.selectedChild) {\n throw new Error(\"failed to update lotto node as it has no active child\");\n }\n\n // If the selected child has never been updated or is running then we will need to update it now.\n if (this.selectedChild.getState() === State.READY || this.selectedChild.getState() === State.RUNNING) {\n this.selectedChild.update(agent, options);\n }\n\n // The state of the lotto node is the state of its selected child.\n this.setState(this.selectedChild.getState());\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => (this.weights ? `LOTTO [${this.weights.join(\",\")}]` : \"LOTTO\");\n}\n", "import Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A decorator node that wraps a single child node.\n */\nexport default abstract class Decorator extends Node {\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(type: string, attributes: Attribute[], protected child: Node) {\n super(type, attributes, []);\n }\n\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => false;\n\n /**\n * Gets the children of this node.\n */\n getChildren = () => [this.child];\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the state of the child node.\n this.child.reset();\n };\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n abort = (agent: Agent) => {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Abort the child node.\n this.child.abort(agent);\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Fail node.\n * This node wraps a single child and will always move to the 'FAILED' state when the child moves to a 'SUCCEEDED' or 'FAILED' state.\n */\nexport default class Fail extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"fail\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n case State.FAILED:\n this.setState(State.FAILED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"FAIL\";\n}\n", "import Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport Node from \"../Node\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Flip node.\n * This node wraps a single child and will flip the state of the child state.\n */\nexport default class Flip extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"flip\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n this.setState(State.FAILED);\n break;\n\n case State.FAILED:\n this.setState(State.SUCCEEDED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"FLIP\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A REPEAT node.\n * The node has a single child which can have:\n * -- A number of iterations for which to repeat the child node.\n * -- An infinite repeat loop if neither an iteration count or a condition function is defined.\n * The REPEAT node will stop and have a 'FAILED' state if its child is ever in a 'FAILED' state after an update.\n * The REPEAT node will attempt to move on to the next iteration if its child is ever in a 'SUCCEEDED' state.\n */\nexport default class Repeat extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param iterations The number of iterations to repeat the child node.\n * @param iterationsMin The minimum possible number of iterations to repeat the child node.\n * @param iterationsMax The maximum possible number of iterations to repeat the child node.\n * @param child The child node.\n */\n constructor(\n attributes: Attribute[],\n private iterations: number | null,\n private iterationsMin: number | null,\n private iterationsMax: number | null,\n child: Node\n ) {\n super(\"repeat\", attributes, child);\n }\n\n /**\n * The number of target iterations to make.\n */\n private targetIterationCount: number | null = null;\n\n /**\n * The current iteration count.\n */\n private currentIterationCount: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to reset the child and the target iteration count.\n if (this.is(State.READY)) {\n // Reset the child node.\n this.child.reset();\n\n // Reset the current iteration count.\n this.currentIterationCount = 0;\n\n // Set the target iteration count.\n this.setTargetIterationCount(options);\n }\n\n // Do a check to see if we can iterate. If we can then this node will move into the 'RUNNING' state.\n // If we cannot iterate then we have hit our target iteration count, which means that the node has succeeded.\n if (this.canIterate()) {\n // This node is in the running state and can do its initial iteration.\n this.setState(State.RUNNING);\n\n // We may have already completed an iteration, meaning that the child node will be in the SUCCEEDED state.\n // If this is the case then we will have to reset the child node now.\n if (this.child.getState() === State.SUCCEEDED) {\n this.child.reset();\n }\n\n // Update the child of this node.\n this.child.update(agent, options);\n\n // If the child moved into the FAILED state when we updated it then there is nothing left to do and this node has also failed.\n // If it has moved into the SUCCEEDED state then we have completed the current iteration.\n if (this.child.getState() === State.FAILED) {\n // The child has failed, meaning that this node has failed.\n this.setState(State.FAILED);\n\n return;\n } else if (this.child.getState() === State.SUCCEEDED) {\n // We have completed an iteration.\n this.currentIterationCount += 1;\n }\n } else {\n // This node is in the 'SUCCEEDED' state as we cannot iterate any more.\n this.setState(State.SUCCEEDED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.iterations !== null) {\n return `REPEAT ${this.iterations}x`;\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n return `REPEAT ${this.iterationsMin}x-${this.iterationsMax}x`;\n } else {\n return \"REPEAT\";\n }\n };\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the current iteration count.\n this.currentIterationCount = 0;\n\n // Reset the child node.\n this.child.reset();\n };\n\n /**\n * Gets whether an iteration can be made.\n * @returns Whether an iteration can be made.\n */\n private canIterate = () => {\n if (this.targetIterationCount !== null) {\n // We can iterate as long as we have not reached our target iteration count.\n return this.currentIterationCount < this.targetIterationCount;\n }\n\n // If neither an iteration count or a condition function were defined then we can iterate indefinitely.\n return true;\n };\n\n /**\n * Sets the target iteration count.\n * @param options The behaviour tree options object.\n */\n private setTargetIterationCount = (options: BehaviourTreeOptions) => {\n // Are we dealing with an explicit iteration count or will we be randomly picking a iteration count between the min and max iteration count.\n if (this.iterations !== null) {\n this.targetIterationCount = this.iterations;\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n // We will be picking a random iteration count between a min and max iteration count, if the optional 'random'\n // behaviour tree function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random iteration count between a min and max iteration count.\n this.targetIterationCount = Math.floor(\n random() * (this.iterationsMax - this.iterationsMin + 1) + this.iterationsMin\n );\n } else {\n this.targetIterationCount = null;\n }\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A RETRY node.\n * The node has a single child which can have:\n * -- A number of iterations for which to repeat the child node.\n * -- An infinite repeat loop if neither an iteration count or a condition function is defined.\n * The RETRY node will stop and have a 'SUCCEEDED' state if its child is ever in a 'SUCCEEDED' state after an update.\n * The RETRY node will attempt to move on to the next iteration if its child is ever in a 'FAILED' state.\n */\nexport default class Retry extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param attempts The number of attempts to retry the child node.\n * @param attemptsMin The minimum possible number of attempts to retry the child node.\n * @param attemptsMax The maximum possible number of attempts to retry the child node.\n * @param child The child node.\n */\n constructor(\n attributes: Attribute[],\n private attempts: number | null,\n private attemptsMin: number | null,\n private attemptsMax: number | null,\n child: Node\n ) {\n super(\"retry\", attributes, child);\n }\n\n /**\n * The number of target attempts to make.\n */\n private targetAttemptCount: number | null = null;\n\n /**\n * The current attempt count.\n */\n private currentAttemptCount: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to reset the child and the target attempt count.\n if (this.is(State.READY)) {\n // Reset the child node.\n this.child.reset();\n\n // Reset the current attempt count.\n this.currentAttemptCount = 0;\n\n // Set the target attempt count.\n this.setTargetAttemptCount(options);\n }\n\n // Do a check to see if we can attempt. If we can then this node will move into the 'RUNNING' state.\n // If we cannot attempt then we have hit our target attempt count, which means that the node has succeeded.\n if (this.canAttempt()) {\n // This node is in the running state and can do its initial attempt.\n this.setState(State.RUNNING);\n\n // We may have already completed an attempt, meaning that the child node will be in the FAILED state.\n // If this is the case then we will have to reset the child node now.\n if (this.child.getState() === State.FAILED) {\n this.child.reset();\n }\n\n // Update the child of this node.\n this.child.update(agent, options);\n\n // If the child moved into the SUCCEEDED state when we updated it then there is nothing left to do and this node has also succeeded.\n // If it has moved into the FAILED state then we have completed the current attempt.\n if (this.child.getState() === State.SUCCEEDED) {\n // The child has succeeded, meaning that this node has succeeded.\n this.setState(State.SUCCEEDED);\n\n return;\n } else if (this.child.getState() === State.FAILED) {\n // We have completed an attempt.\n this.currentAttemptCount += 1;\n }\n } else {\n // This node is in the 'FAILED' state as we cannot iterate any more.\n this.setState(State.FAILED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.attempts !== null) {\n return `RETRY ${this.attempts}x`;\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n return `RETRY ${this.attemptsMin}x-${this.attemptsMax}x`;\n } else {\n return \"RETRY\";\n }\n };\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the current attempt count.\n this.currentAttemptCount = 0;\n\n // Reset the child node.\n this.child.reset();\n };\n\n /**\n * Gets whether an attempt can be made.\n * @returns Whether an attempt can be made.\n */\n canAttempt = () => {\n if (this.targetAttemptCount !== null) {\n // We can attempt as long as we have not reached our target attempt count.\n return this.currentAttemptCount < this.targetAttemptCount;\n }\n\n // If neither an attempt count or a condition function were defined then we can attempt indefinitely.\n return true;\n };\n\n /**\n * Sets the target attempt count.\n * @param options The behaviour tree options object.\n */\n setTargetAttemptCount = (options: BehaviourTreeOptions) => {\n // Are we dealing with an explicit attempt count or will we be randomly picking an attempt count between the min and max attempt count.\n if (this.attempts !== null) {\n this.targetAttemptCount = this.attempts;\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n // We will be picking a random attempt count between a min and max attempt count, if the optional 'random'\n // behaviour tree function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random attempt count between a min and max attempt count.\n this.targetAttemptCount = Math.floor(\n random() * (this.attemptsMax - this.attemptsMin + 1) + this.attemptsMin\n );\n } else {\n this.targetAttemptCount = null;\n }\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Root node.\n * The root node will have a single child.\n */\nexport default class Root extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"root\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n // Update the child of this node.\n this.child.update(agent, options);\n }\n\n // The state of the root node is the state of its child.\n this.setState(this.child.getState());\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"ROOT\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Succeed node.\n * This node wraps a single child and will always move to the 'SUCCEEDED' state when the child moves to a 'SUCCEEDED' or 'FAILED' state.\n */\nexport default class Succeed extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"succeed\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n case State.FAILED:\n this.setState(State.SUCCEEDED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SUCCEED\";\n}\n", "import Node from \"../Node\";\n\n/**\n * A leaf node.\n */\nexport default abstract class Leaf extends Node {\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => true;\n}\n", "import { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\nimport State, { CompleteState } from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Leaf from \"./Leaf\";\nimport Lookup from \"../../Lookup\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * The type representing a resolved/rejected update promise.\n */\ntype UpdatePromiseResult = {\n /**\n * Whether the promise was resolved rather than rejected.\n */\n isResolved: boolean;\n\n /**\n * The promise resolved value or rejection reason.\n */\n value: any;\n};\n\n/**\n * An Action leaf node.\n * This represents an immediate or ongoing state of behaviour.\n */\nexport default class Action extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param actionName The action name.\n * @param actionArguments The array of action argument definitions.\n */\n constructor(attributes: Attribute[], private actionName: string, private actionArguments: any[]) {\n super(\"action\", attributes, actionArguments);\n }\n\n /**\n * Whether there is a pending update promise.\n */\n private isUsingUpdatePromise = false;\n\n /**\n * The finished state result of an update promise.\n */\n private updatePromiseResult: UpdatePromiseResult | null = null;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the result of this action depends on an update promise then there is nothing to do until it settles.\n if (this.isUsingUpdatePromise) {\n // Are we still waiting for our update promise to settle?\n if (!this.updatePromiseResult) {\n return;\n }\n\n const { isResolved, value } = this.updatePromiseResult;\n\n // Our update promise settled, was it resolved or rejected?\n if (isResolved) {\n // Our promise resolved so check to make sure the result is a valid finished state.\n if (value !== State.SUCCEEDED && value !== State.FAILED) {\n throw new Error(\n \"action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned\"\n );\n }\n\n // Set the state of this node to match the state returned by the promise.\n this.setState(value);\n\n return;\n } else {\n // The promise was rejected, which isn't great.\n throw new Error(`action function '${this.actionName}' promise rejected with '${value}'`);\n }\n }\n\n // Attempt to get the invoker for the action function.\n const actionFuncInvoker = Lookup.getFuncInvoker(agent, this.actionName);\n\n // The action function should be defined.\n if (actionFuncInvoker === null) {\n throw new Error(\n `cannot update action node as the action '${this.actionName}' function is not defined on the agent and has not been registered`\n );\n }\n\n let actionFunctionResult;\n\n try {\n // Call the action function, the result of which may be:\n // - The finished state of this action node.\n // - A promise to return a finished node state.\n // - Undefined if the node should remain in the running state.\n actionFunctionResult = actionFuncInvoker(this.actionArguments) as CompleteState | Promise;\n } catch (error) {\n // An uncaught error was thrown.\n if (error instanceof Error) {\n throw new Error(`action function '${this.actionName}' threw: ${error.stack}`);\n } else {\n throw new Error(`action function '${this.actionName}' threw: ${error}`);\n }\n }\n\n if (actionFunctionResult instanceof Promise) {\n actionFunctionResult.then(\n (result) => {\n // If 'isUpdatePromisePending' is not set then the promise was cleared as it was resolving, probably via an abort of reset.\n if (!this.isUsingUpdatePromise) {\n return;\n }\n\n // Set the resolved update promise result so that it can be handled on the next update of this node.\n this.updatePromiseResult = {\n isResolved: true,\n value: result\n };\n },\n (reason) => {\n // If 'isUpdatePromisePending' is not set then the promise was cleared as it was resolving, probably via an abort or reset.\n if (!this.isUsingUpdatePromise) {\n return;\n }\n\n // Set the rejected update promise result so that it can be handled on the next update of this node.\n this.updatePromiseResult = {\n isResolved: false,\n value: reason\n };\n }\n );\n\n // This node will be in the 'RUNNING' state until the update promise resolves.\n this.setState(State.RUNNING);\n\n // We are now waiting for the promise returned by the use to resolve before we know what state this node is in.\n this.isUsingUpdatePromise = true;\n } else {\n // Validate the returned value.\n this.validateUpdateResult(actionFunctionResult);\n\n // Set the state of this node, this may be undefined, which just means that the node is still in the 'RUNNING' state.\n this.setState(actionFunctionResult || State.RUNNING);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => this.actionName;\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // There is no longer an update promise that we care about.\n this.isUsingUpdatePromise = false;\n this.updatePromiseResult = null;\n };\n\n /**\n * Validate the result of an update function call.\n * @param result The result of an update function call.\n */\n private validateUpdateResult = (result: CompleteState | State.RUNNING) => {\n switch (result) {\n case State.SUCCEEDED:\n case State.FAILED:\n case State.RUNNING:\n case undefined:\n return;\n default:\n throw new Error(\n `expected action function '${this.actionName}' to return an optional State.SUCCEEDED or State.FAILED value but returned '${result}'`\n );\n }\n };\n}\n", "import { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Leaf from \"./Leaf\";\nimport Lookup from \"../../Lookup\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A Condition leaf node.\n * This will succeed or fail immediately based on an agent predicate, without moving to the 'RUNNING' state.\n */\nexport default class Condition extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param conditionName The name of the condition function.\n * @param conditionArguments The array of condition argument definitions.\n */\n constructor(attributes: Attribute[], private conditionName: string, private conditionArguments: any[]) {\n super(\"condition\", attributes, conditionArguments);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.conditionName);\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot update condition node as the condition '${this.conditionName}' function is not defined on the agent and has not been registered`\n );\n }\n\n let conditionFunctionResult;\n\n try {\n // Call the condition function to determine the state of this node, the result of which should be a boolean.\n conditionFunctionResult = conditionFuncInvoker(this.conditionArguments);\n } catch (error) {\n // An uncaught error was thrown.\n if (error instanceof Error) {\n throw new Error(`condition function '${this.conditionName}' threw: ${error.stack}`);\n } else {\n throw new Error(`condition function '${this.conditionName}' threw: ${error}`);\n }\n }\n\n // The result of calling the condition function must be a boolean value.\n if (typeof conditionFunctionResult !== \"boolean\") {\n throw new Error(\n `expected condition function '${this.conditionName}' to return a boolean but returned '${conditionFunctionResult}'`\n );\n }\n\n // Set the state of this node based on the result of calling the condition function.\n this.setState(!!conditionFunctionResult ? State.SUCCEEDED : State.FAILED);\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => this.conditionName;\n}\n", "import Leaf from \"./Leaf\";\nimport State from \"../../State\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { Agent } from \"../../Agent\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A WAIT node.\n * The state of this node will change to SUCCEEDED after a duration of time\n */\nexport default class Wait extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param duration The duration that this node will wait to succeed in milliseconds.\n * @param durationMin The minimum possible duration in milliseconds that this node will wait to succeed.\n * @param durationMax The maximum possible duration in milliseconds that this node will wait to succeed.\n */\n constructor(\n attributes: Attribute[],\n private duration: number | null,\n private durationMin: number | null,\n private durationMax: number | null\n ) {\n super(\"wait\", attributes, []);\n }\n\n /**\n * The time in milliseconds at which this node was first updated.\n */\n private initialUpdateTime: number = 0;\n\n /**\n * The total duration in milliseconds that this node will be waiting for.\n */\n private totalDuration: number | null = null;\n\n /**\n * The duration in milliseconds that this node has been waiting for.\n */\n private waitedDuration: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to set the initial update time.\n if (this.is(State.READY)) {\n // Set the initial update time.\n this.initialUpdateTime = new Date().getTime();\n\n // Set the initial waited duration.\n this.waitedDuration = 0;\n\n // Are we dealing with an explicit duration or will we be randomly picking a duration between the min and max duration.\n if (this.duration !== null) {\n this.totalDuration = this.duration;\n } else if (this.durationMin !== null && this.durationMax !== null) {\n // We will be picking a random duration between a min and max duration, if the optional 'random' behaviour tree\n // function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random duration between a min and max duration.\n this.totalDuration = Math.floor(\n random() * (this.durationMax - this.durationMin + 1) + this.durationMin\n );\n } else {\n this.totalDuration = null;\n }\n\n // The node is now running until we finish waiting.\n this.setState(State.RUNNING);\n }\n\n // If we have no total duration then this wait node will wait indefinitely until it is aborted.\n if (this.totalDuration === null) {\n return;\n }\n\n // If we have a 'getDeltaTime' function defined as part of our options then we will use it to figure out how long we have waited for.\n if (typeof options.getDeltaTime === \"function\") {\n // Get the delta time.\n const deltaTime = options.getDeltaTime();\n\n // Our delta time must be a valid number and cannot be NaN.\n if (typeof deltaTime !== \"number\" || isNaN(deltaTime)) {\n throw new Error(\"The delta time must be a valid number and not NaN.\");\n }\n\n // Update the amount of time that this node has been waiting for based on the delta time.\n this.waitedDuration += deltaTime * 1000;\n } else {\n // We are not using a delta time, so we will just work out hom much time has passed since the first update.\n this.waitedDuration = new Date().getTime() - this.initialUpdateTime;\n }\n\n // Have we waited long enough?\n if (this.waitedDuration >= this.totalDuration) {\n // We have finished waiting!\n this.setState(State.SUCCEEDED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.duration !== null) {\n return `WAIT ${this.duration}ms`;\n } else if (this.durationMin !== null && this.durationMax !== null) {\n return `WAIT ${this.durationMin}ms-${this.durationMax}ms`;\n } else {\n return \"WAIT\";\n }\n };\n}\n", "import Guard from \"./guards/Guard\";\n\nexport type AttributeDetails = {\n /** The attribute type. */\n type: string;\n\n /** The attribute arguments. */\n args: any[];\n};\n\n/**\n * A base node attribute.\n */\nexport default abstract class Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of attribute arguments.\n */\n constructor(public type: string, public args: any[]) {}\n\n /**\n * Gets the attribute details.\n */\n abstract getDetails(): TAttributeDetails;\n\n /**\n * Gets whether this attribute is a guard.\n */\n abstract isGuard: () => this is Guard;\n}\n", "import { Agent } from \"../../Agent\";\nimport Attribute, { AttributeDetails } from \"../Attribute\";\n\nexport type GuardAttributeDetails = {\n /** The name of the condition function that determines whether the guard is satisfied. */\n condition: string;\n} & AttributeDetails;\n\n/**\n * A base node guard attribute.\n */\nexport default abstract class Guard extends Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of decorator argument definitions.\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n */\n constructor(type: string, args: any[], private condition: string) {\n super(type, args);\n }\n\n /**\n * Gets the name of the condition function that determines whether the guard is satisfied.\n */\n getCondition = () => this.condition;\n\n /**\n * Gets whether this attribute is a guard.\n */\n isGuard = () => true;\n\n /**\n * Gets the attribute details.\n */\n getDetails(): GuardAttributeDetails {\n return {\n type: this.type,\n args: this.args,\n condition: this.getCondition()\n };\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n abstract isSatisfied(agent: Agent): boolean;\n}\n", "import Guard from \"./Guard\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * A WHILE guard which is satisfied as long as the given condition remains true.\n */\nexport default class While extends Guard {\n /**\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n * @param args The array of decorator argument definitions.\n */\n constructor(condition: string, args: any[]) {\n super(\"while\", args, condition);\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n isSatisfied = (agent: Agent) => {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.getCondition());\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered`\n );\n }\n\n let conditionFunctionResult;\n\n try {\n // Call the guard condition function to determine the state of this node, the result of which should be a boolean.\n conditionFunctionResult = conditionFuncInvoker(this.args);\n } catch (error) {\n // An uncaught error was thrown.\n if (error instanceof Error) {\n throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`);\n } else {\n throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`);\n }\n }\n\n // The result of calling the guard condition function must be a boolean value.\n if (typeof conditionFunctionResult !== \"boolean\") {\n throw new Error(\n `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'`\n );\n }\n\n // Return whether this guard is satisfied.\n return conditionFunctionResult;\n };\n}\n", "import Guard from \"./Guard\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * An UNTIL guard which is satisfied as long as the given condition remains false.\n */\nexport default class Until extends Guard {\n /**\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n * @param args The array of decorator argument definitions.\n */\n constructor(condition: string, args: any[]) {\n super(\"until\", args, condition);\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n isSatisfied = (agent: Agent) => {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.getCondition());\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered`\n );\n }\n\n let conditionFunctionResult;\n\n try {\n // Call the guard condition function to determine the state of this node, the result of which should be a boolean.\n conditionFunctionResult = conditionFuncInvoker(this.args);\n } catch (error) {\n // An uncaught error was thrown.\n if (error instanceof Error) {\n throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`);\n } else {\n throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`);\n }\n }\n\n // The result of calling the guard condition function must be a boolean value.\n if (typeof conditionFunctionResult !== \"boolean\") {\n throw new Error(\n `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'`\n );\n }\n\n // Return whether this guard is satisfied.\n return !conditionFunctionResult;\n };\n}\n", "import { Agent } from \"../../Agent\";\nimport Attribute, { AttributeDetails } from \"../Attribute\";\n\nexport type CallbackAttributeDetails = {\n /** The name of the agent function that is called. */\n functionName: string;\n} & AttributeDetails;\n\n/**\n * A base node callback attribute.\n */\nexport default abstract class Callback extends Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of decorator argument definitions.\n * @param functionName The name of the agent function to call.\n */\n constructor(type: string, args: any[], private functionName: string) {\n super(type, args);\n }\n\n /**\n * Gets the name of the agent function to call.\n */\n getFunctionName = () => this.functionName;\n\n /**\n * Gets whether this attribute is a guard.\n */\n isGuard = () => false;\n\n /**\n * Gets the attribute details.\n */\n getDetails(): CallbackAttributeDetails {\n return {\n type: this.type,\n args: this.args,\n functionName: this.getFunctionName()\n };\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n abstract callAgentFunction: (agent: Agent, isSuccess: boolean, isAborted: boolean) => void;\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * An ENTRY callback which defines an agent function to call when the associated node is updated and moves out of running state.\n */\nexport default class Entry extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: any[]) {\n super(\"entry\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n callAgentFunction = (agent: Agent) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call entry function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function.\n callbackFuncInvoker(this.args);\n };\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * A STEP callback which defines an agent function to call when the associated node is updated.\n */\nexport default class Step extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: any[]) {\n super(\"step\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n callAgentFunction = (agent: Agent) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call step function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function.\n callbackFuncInvoker(this.args);\n };\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * An EXIT callback which defines an agent function to call when the associated node is updated and moves to a finished state or is aborted.\n */\nexport default class Exit extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: any[]) {\n super(\"exit\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n * @param isSuccess Whether the decorated node was left with a success state.\n * @param isAborted Whether the decorated node was aborted.\n */\n callAgentFunction = (agent: Agent, isSuccess: boolean, isAborted: boolean) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call exit function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function\n callbackFuncInvoker([{ succeeded: isSuccess, aborted: isAborted }, ...this.args]);\n };\n}\n", "import { AnyNodeDefinition, RootNodeDefinition } from \"./BehaviourTreeDefinition\";\nimport GuardPath, { GuardPathPart } from \"./attributes/guards/GuardPath\";\nimport { validateBranchSubtreeLinks } from \"./BehaviourTreeDefinitionValidator\";\nimport { isInteger } from \"./BehaviourTreeDefinitionUtilities\";\nimport Node from \"./nodes/Node\";\nimport Composite from \"./nodes/composite/Composite\";\nimport Decorator from \"./nodes/decorator/Decorator\";\nimport Parallel from \"./nodes/composite/Parallel\";\nimport Selector from \"./nodes/composite/Selector\";\nimport Sequence from \"./nodes/composite/Sequence\";\nimport Lotto from \"./nodes/composite/Lotto\";\nimport Fail from \"./nodes/decorator/Fail\";\nimport Flip from \"./nodes/decorator/Flip\";\nimport Repeat from \"./nodes/decorator/Repeat\";\nimport Retry from \"./nodes/decorator/Retry\";\nimport Root from \"./nodes/decorator/Root\";\nimport Succeed from \"./nodes/decorator/Succeed\";\nimport Action from \"./nodes/leaf/Action\";\nimport Condition from \"./nodes/leaf/Condition\";\nimport Wait from \"./nodes/leaf/Wait\";\nimport Lookup from \"./Lookup\";\nimport Attribute from \"./attributes/Attribute\";\nimport While from \"./attributes/guards/While\";\nimport Until from \"./attributes/guards/Until\";\nimport Entry from \"./attributes/callbacks/Entry\";\nimport Step from \"./attributes/callbacks/Step\";\nimport Exit from \"./attributes/callbacks/Exit\";\n\n/**\n * A type representing any node instance in a behaviour tree.\n */\ntype AnyNode =\n | Root\n | Action\n | Condition\n | Wait\n | Sequence\n | Selector\n | Lotto\n | Parallel\n | Repeat\n | Retry\n | Flip\n | Succeed\n | Fail;\n\n/**\n * A type defining a mapping of root node identifiers to root node definitions.\n */\ntype RootNodeDefinitionMap = { [key: string | symbol]: RootNodeDefinition };\n\n/**\n * A symbol to use as the main root key in any root node mappings.\n */\nconst MAIN_ROOT_NODE_KEY = Symbol(\"__root__\");\n\n/**\n * Build and populate the root nodes based on the provided definition, assuming that the definition has been validated.\n * @param definition The root node definitions.\n * @returns The built and populated root node definitions.\n */\nexport default function buildRootNode(definition: RootNodeDefinition[]): Root {\n // Create a mapping of root node identifers to root node definitions, including globally registered subtree root node definitions.\n const rootNodeDefinitionMap = createRootNodeDefinitionMap(definition);\n\n // Now that we have all of our root node definitions (those part of the tree definition and those globally registered)\n // we should validate the branch-subtree links. This will also double-check that we dont have any circular dependencies\n // in our branch-subtree references and that we have no broken branch-subtree links.\n validateBranchSubtreeLinks(\n [rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], ...Object.values(rootNodeDefinitionMap)],\n true\n );\n\n // Create our populated tree of node instances, starting with our main root node.\n const rootNode = nodeFactory(rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], rootNodeDefinitionMap) as Root;\n\n // Set a guard path on every leaf of the tree to evaluate as part of each update.\n applyLeafNodeGuardPaths(rootNode);\n\n // We only need to return the main root node.\n return rootNode;\n}\n\n/**\n * A factory function which creates a node instance based on the specified definition.\n * @param definition The node definition.\n * @param rootNodeDefinitionMap The mapping of root node identifers to root node definitions, including globally registered subtree root node definitions.\n * @returns A node instance based on the specified definition.\n */\nfunction nodeFactory(definition: AnyNodeDefinition, rootNodeDefinitionMap: RootNodeDefinitionMap): AnyNode {\n // Get the attributes for the node.\n const attributes = nodeAttributesFactory(definition);\n\n // Create the node instance based on the definition type.\n switch (definition.type) {\n case \"root\":\n return new Root(attributes, nodeFactory(definition.child, rootNodeDefinitionMap));\n\n case \"repeat\":\n let iterations: number | null = null;\n let iterationsMin: number | null = null;\n let iterationsMax: number | null = null;\n\n if (Array.isArray(definition.iterations)) {\n iterationsMin = definition.iterations[0];\n iterationsMax = definition.iterations[1];\n } else if (isInteger(definition.iterations)) {\n iterations = definition.iterations!;\n }\n\n return new Repeat(\n attributes,\n iterations,\n iterationsMin,\n iterationsMax,\n nodeFactory(definition.child, rootNodeDefinitionMap)\n );\n\n case \"retry\":\n let attempts: number | null = null;\n let attemptsMin: number | null = null;\n let attemptsMax: number | null = null;\n\n if (Array.isArray(definition.attempts)) {\n attemptsMin = definition.attempts[0];\n attemptsMax = definition.attempts[1];\n } else if (isInteger(definition.attempts)) {\n attempts = definition.attempts!;\n }\n\n return new Retry(\n attributes,\n attempts,\n attemptsMin,\n attemptsMax,\n nodeFactory(definition.child, rootNodeDefinitionMap)\n );\n\n case \"flip\":\n return new Flip(attributes, nodeFactory(definition.child, rootNodeDefinitionMap));\n\n case \"succeed\":\n return new Succeed(attributes, nodeFactory(definition.child, rootNodeDefinitionMap));\n\n case \"fail\":\n return new Fail(attributes, nodeFactory(definition.child, rootNodeDefinitionMap));\n\n case \"sequence\":\n return new Sequence(\n attributes,\n definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap))\n );\n\n case \"selector\":\n return new Selector(\n attributes,\n definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap))\n );\n\n case \"parallel\":\n return new Parallel(\n attributes,\n definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap))\n );\n\n case \"lotto\":\n return new Lotto(\n attributes,\n definition.weights,\n definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap))\n );\n\n case \"branch\":\n return nodeFactory(rootNodeDefinitionMap[definition.ref].child, rootNodeDefinitionMap);\n\n case \"action\":\n return new Action(attributes, definition.call, definition.args || []);\n\n case \"condition\":\n return new Condition(attributes, definition.call, definition.args || []);\n\n case \"wait\":\n let duration: number | null = null;\n let durationMin: number | null = null;\n let durationMax: number | null = null;\n\n if (Array.isArray(definition.duration)) {\n durationMin = definition.duration[0];\n durationMax = definition.duration[1];\n } else if (isInteger(definition.duration)) {\n duration = definition.duration!;\n }\n\n return new Wait(attributes, duration, durationMin, durationMax);\n }\n}\n\n/**\n * Creates an array of node attribute instances based on the specified node definition.\n * @param definition The node definition.\n * @returns An array of node attribute instances based on the specified node definition.\n */\nfunction nodeAttributesFactory(definition: AnyNodeDefinition): Attribute[] {\n const attributes: Attribute[] = [];\n\n if (definition.while) {\n attributes.push(new While(definition.while.call, definition.while.args ?? []));\n }\n\n if (definition.until) {\n attributes.push(new Until(definition.until.call, definition.until.args ?? []));\n }\n\n if (definition.entry) {\n attributes.push(new Entry(definition.entry.call, definition.entry.args ?? []));\n }\n\n if (definition.step) {\n attributes.push(new Step(definition.step.call, definition.step.args ?? []));\n }\n\n if (definition.exit) {\n attributes.push(new Exit(definition.exit.call, definition.exit.args ?? []));\n }\n\n return attributes;\n}\n\n/**\n * Creates a mapping of root node identifers to root node definitions, mixing in globally registered subtree root node definitions.\n * @param definition The root node definitions.\n * @returns A mapping of root node identifers to root node definitions, including globally registered subtree root node definitions.\n */\nfunction createRootNodeDefinitionMap(definition: RootNodeDefinition[]): RootNodeDefinitionMap {\n // Create a mapping of root node identifers to root node definitions.\n const rootNodeMap: RootNodeDefinitionMap = {};\n\n // Add in any registered subtree root node definitions.\n for (const [name, rootNodeDefinition] of Object.entries(Lookup.getSubtrees())) {\n // The name used when registering the subtree will be used as the root node identifier.\n rootNodeMap[name] = { ...rootNodeDefinition, id: name };\n }\n\n // Populate the map with the root node definitions that were included with the tree definition.\n // We do this after adding any registered subtrees as we want these to take presedence.\n for (const rootNodeDefinition of definition) {\n rootNodeMap[rootNodeDefinition.id ?? MAIN_ROOT_NODE_KEY] = rootNodeDefinition;\n }\n\n return rootNodeMap;\n}\n\n/**\n * Applies a guard path to every leaf of the tree to evaluate as part of each update.\n * @param root The main root tree node.\n */\nfunction applyLeafNodeGuardPaths(root: Root) {\n const nodePaths: Node[][] = [];\n\n const findLeafNodes = (path: Node[], node: Node) => {\n // Add the current node to the path.\n path = path.concat(node);\n\n // Check whether the current node is a leaf node.\n if (node.isLeafNode()) {\n nodePaths.push(path);\n } else {\n (node as Composite | Decorator).getChildren().forEach((child) => findLeafNodes(path, child));\n }\n };\n\n // Find all leaf node paths, starting from the root.\n findLeafNodes([], root);\n\n nodePaths.forEach((path) => {\n // Each node in the current path will have to be assigned a guard path, working from the root outwards.\n for (let depth = 0; depth < path.length; depth++) {\n // Get the node in the path at the current depth.\n const currentNode = path[depth];\n\n // The node may already have been assigned a guard path, if so just skip it.\n if (currentNode.hasGuardPath()) {\n continue;\n }\n\n // Create the guard path for the current node.\n const guardPath = new GuardPath(\n path\n .slice(0, depth + 1)\n .map((node) => ({ node, guards: node.getGuardAttributes() }))\n .filter((details) => details.guards.length > 0)\n );\n\n // Assign the guard path to the current node.\n currentNode.setGuardPath(guardPath);\n }\n });\n}\n", "import State, { AnyState } from \"./State\";\nimport Lookup from \"./Lookup\";\nimport Node from \"./nodes/Node\";\nimport Root from \"./nodes/decorator/Root\";\nimport Composite from \"./nodes/composite/Composite\";\nimport Decorator from \"./nodes/decorator/Decorator\";\nimport { Agent, GlobalFunction } from \"./Agent\";\nimport { CallbackAttributeDetails } from \"./attributes/callbacks/Callback\";\nimport { GuardAttributeDetails } from \"./attributes/guards/Guard\";\nimport { BehaviourTreeOptions } from \"./BehaviourTreeOptions\";\nimport { convertMDSLToJSON } from \"./mdsl/MDSLDefinitionParser\";\nimport { RootNodeDefinition } from \"./BehaviourTreeDefinition\";\nimport { validateDefinition, validateJSONDefinition } from \"./BehaviourTreeDefinitionValidator\";\nimport buildRootNode from \"./BehaviourTreeBuilder\";\nimport { isNullOrUndefined } from \"./BehaviourTreeDefinitionUtilities\";\n\n// Purely for outside inspection of the tree.\nexport type FlattenedTreeNode = {\n id: string;\n type: string;\n caption: string;\n state: AnyState;\n guards: GuardAttributeDetails[];\n callbacks: CallbackAttributeDetails[];\n args: any[];\n parentId: string | null;\n};\n\n/**\n * A representation of a behaviour tree.\n */\nexport class BehaviourTree {\n /**\n * The main root tree node.\n */\n private readonly _rootNode: Root;\n\n /**\n * Creates a new instance of the BehaviourTree class.\n * @param definition The behaviour tree definition as either an MDSL string, root node definition object or array of root node definition objects.\n * @param agent The agent instance that this behaviour tree is modelling behaviour for.\n * @param options The behaviour tree options object.\n */\n constructor(\n definition: string | RootNodeDefinition | RootNodeDefinition[],\n private agent: Agent,\n private options: BehaviourTreeOptions = {}\n ) {\n // The tree definition must be defined.\n if (isNullOrUndefined(definition)) {\n throw new Error(\"tree definition not defined\");\n }\n\n // The agent must be defined and not null.\n if (typeof agent !== \"object\" || agent === null) {\n throw new Error(\"the agent must be an object and not null\");\n }\n\n // We should validate the definition before we try to build the tree nodes.\n const { succeeded, errorMessage, json } = validateDefinition(definition);\n\n // Did our validation fail without error?\n if (!succeeded) {\n throw new Error(`invalid definition: ${errorMessage}`);\n }\n\n // Double check that we did actually get our json definition as part of our definition validtion.\n if (!json) {\n throw new Error(\n \"expected json definition to be returned as part of successful definition validation response\"\n );\n }\n\n try {\n // Create the populated tree of behaviour tree nodes and get the root node.\n this._rootNode = buildRootNode(json);\n } catch (exception) {\n // There was an issue in trying build and populate the behaviour tree.\n throw new Error(`error building tree: ${(exception as Error).message}`);\n }\n }\n\n /**\n * Gets whether the tree is in the RUNNING state.\n * @returns true if the tree is in the RUNNING state, otherwise false.\n */\n isRunning() {\n return this._rootNode.getState() === State.RUNNING;\n }\n\n /**\n * Gets the current tree state of SUCCEEDED, FAILED, READY or RUNNING.\n * @returns The current tree state.\n */\n getState() {\n return this._rootNode.getState();\n }\n\n /**\n * Step the tree.\n * Carries out a node update that traverses the tree from the root node outwards to any child nodes, skipping those that are already in a resolved state of SUCCEEDED or FAILED.\n * After being updated, leaf nodes will have a state of SUCCEEDED, FAILED or RUNNING. Leaf nodes that are left in the RUNNING state as part of a tree step will be revisited each\n * subsequent step until they move into a resolved state of either SUCCEEDED or FAILED, after which execution will move through the tree to the next node with a state of READY.\n *\n * Calling this method when the tree is already in a resolved state of SUCCEEDED or FAILED will cause it to be reset before tree traversal begins.\n */\n step() {\n // If the root node has already been stepped to completion then we need to reset it.\n if (this._rootNode.getState() === State.SUCCEEDED || this._rootNode.getState() === State.FAILED) {\n this._rootNode.reset();\n }\n\n try {\n this._rootNode.update(this.agent, this.options);\n } catch (exception) {\n throw new Error(`error stepping tree: ${(exception as Error).message}`);\n }\n }\n\n /**\n * Resets the tree from the root node outwards to each nested node, giving each a state of READY.\n */\n reset() {\n this._rootNode.reset();\n }\n\n /**\n * Gets the flattened details of every node in the tree.\n * @returns The flattened details of every node in the tree.\n */\n getFlattenedNodeDetails(): FlattenedTreeNode[] {\n // Create an empty flattened array of tree nodes.\n const flattenedTreeNodes: FlattenedTreeNode[] = [];\n\n /**\n * Helper function to process a node instance and push details into the flattened tree nodes array.\n * @param node The current node.\n * @param parentUid The UID of the node parent, or null if the node is the main root node.\n */\n const processNode = (node: Node, parentUid: string | null) => {\n // Get the guard and callback attribute details for this node.\n const guards = node\n .getAttributes()\n .filter((attribute) => attribute.isGuard())\n .map((attribute) => attribute.getDetails()) as GuardAttributeDetails[];\n const callbacks = node\n .getAttributes()\n .filter((attribute) => !attribute.isGuard())\n .map((attribute) => attribute.getDetails()) as CallbackAttributeDetails[];\n\n // Push the current node into the flattened nodes array.\n flattenedTreeNodes.push({\n id: node.getUid(),\n type: node.getType(),\n caption: node.getName(),\n state: node.getState(),\n guards,\n callbacks,\n args: node.getArguments(),\n parentId: parentUid\n });\n\n // Process each of the nodes children if it is not a leaf node.\n if (!node.isLeafNode()) {\n (node as Composite | Decorator)\n .getChildren()\n .forEach((child) => processNode(child, (node as Composite | Decorator).getUid()));\n }\n };\n\n // Convert the nested node structure into a flattened array of node details.\n processNode(this._rootNode, null);\n\n return flattenedTreeNodes;\n }\n\n /**\n * Registers the action/condition/guard/callback function or subtree with the given name.\n * @param name The name of the function or subtree to register.\n * @param value The function or subtree definition to register.\n */\n static register(name: string, value: GlobalFunction | string | RootNodeDefinition) {\n // Are we going to register a action/condition/guard/callback function?\n if (typeof value === \"function\") {\n Lookup.setFunc(name, value);\n return;\n }\n\n // We are not registering an action/condition/guard/callback function, so we must be registering a subtree.\n if (typeof value === \"string\") {\n let rootNodeDefinitions: RootNodeDefinition[];\n\n // We will assume that any string passed in will be a mdsl definition.\n try {\n rootNodeDefinitions = convertMDSLToJSON(value);\n } catch (exception) {\n throw new Error(`error registering definition, invalid MDSL: ${(exception as Error).message}`);\n }\n\n // This function should only ever be called with a definition containing a single unnamed root node.\n if (rootNodeDefinitions.length != 1 || typeof rootNodeDefinitions[0].id !== \"undefined\") {\n throw new Error(\"error registering definition: expected a single unnamed root node\");\n }\n\n try {\n // We should validate the subtree as we don't want invalid subtrees available via the lookup.\n const { succeeded, errorMessage } = validateJSONDefinition(rootNodeDefinitions[0]);\n\n // Did our validation fail without error?\n if (!succeeded) {\n throw new Error(errorMessage);\n }\n } catch (exception) {\n throw new Error(`error registering definition: ${(exception as Error).message}`);\n }\n\n // Everything seems hunky-dory, register the subtree.\n Lookup.setSubtree(name, rootNodeDefinitions[0]);\n } else if (typeof value === \"object\" && !Array.isArray(value)) {\n // We will assume that any object passed in is a root node definition.\n\n try {\n // We should validate the subtree as we don't want invalid subtrees available via the lookup.\n const { succeeded, errorMessage } = validateJSONDefinition(value);\n\n // Did our validation fail without error?\n if (!succeeded) {\n throw new Error(errorMessage);\n }\n } catch (exception) {\n throw new Error(`error registering definition: ${(exception as Error).message}`);\n }\n\n // Everything seems hunky-dory, register the subtree.\n Lookup.setSubtree(name, value);\n } else {\n throw new Error(\"unexpected value, expected string mdsl definition, root node json definition or function\");\n }\n }\n\n /**\n * Unregisters the registered action/condition/guard/callback function or subtree with the given name.\n * @param name The name of the registered action/condition/guard/callback function or subtree to unregister.\n */\n static unregister(name: string): void {\n Lookup.remove(name);\n }\n\n /**\n * Unregister all registered action/condition/guard/callback functions and subtrees.\n */\n static unregisterAll(): void {\n Lookup.empty();\n }\n}\n"], + "mappings": "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", + "names": ["Participant", "isNullOrUndefined", "Lotto", "createLotto", "State", "createLotto"] } diff --git a/dist/index.d.ts b/dist/index.d.ts index 6adc674..c5ce7d0 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,4 +1,7 @@ -import { BehaviourTree, FlattenedTreeNode } from "./BehaviourTree"; import State from "./State"; -export { BehaviourTree, State }; -export type { FlattenedTreeNode }; +import { validateDefinition } from "./BehaviourTreeDefinitionValidator"; +import { convertMDSLToJSON } from "./mdsl/MDSLDefinitionParser"; +import { BehaviourTree, FlattenedTreeNode } from "./BehaviourTree"; +import { BehaviourTreeOptions } from "./BehaviourTreeOptions"; +export { BehaviourTree, State, convertMDSLToJSON, validateDefinition }; +export type { FlattenedTreeNode, BehaviourTreeOptions }; diff --git a/dist/index.js b/dist/index.js index 5915643..64e6b29 100644 --- a/dist/index.js +++ b/dist/index.js @@ -74,10 +74,10 @@ var require_Utilities = __commonJS({ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isNaturalNumber = exports.isNullOrUndefined = void 0; - function isNullOrUndefined(value) { + function isNullOrUndefined2(value) { return value === null || value === void 0; } - exports.isNullOrUndefined = isNullOrUndefined; + exports.isNullOrUndefined = isNullOrUndefined2; function isNaturalNumber(value) { return typeof value === "number" && value >= 1 && Math.floor(value) === value; } @@ -251,10 +251,1016 @@ var require_dist = __commonJS({ var src_exports = {}; __export(src_exports, { BehaviourTree: () => BehaviourTree, - State: () => State + State: () => State, + convertMDSLToJSON: () => convertMDSLToJSON, + validateDefinition: () => validateDefinition }); module.exports = __toCommonJS(src_exports); +// src/State.ts +var State = /* @__PURE__ */ ((State2) => { + State2["READY"] = "mistreevous.ready"; + State2["RUNNING"] = "mistreevous.running"; + State2["SUCCEEDED"] = "mistreevous.succeeded"; + State2["FAILED"] = "mistreevous.failed"; + return State2; +})(State || {}); + +// src/BehaviourTreeDefinitionUtilities.ts +function isRootNode(node) { + return node.type === "root"; +} +function isBranchNode(node) { + return node.type === "branch"; +} +function isLeafNode(node) { + return ["branch", "action", "condition", "wait"].includes(node.type); +} +function isDecoratorNode(node) { + return ["root", "repeat", "retry", "flip", "succeed", "fail"].includes(node.type); +} +function isCompositeNode(node) { + return ["sequence", "selector", "lotto", "parallel"].includes(node.type); +} +function flattenDefinition(nodeDefinition) { + const nodes = []; + const processNode = (currentNodeDefinition) => { + nodes.push(currentNodeDefinition); + if (isCompositeNode(currentNodeDefinition)) { + currentNodeDefinition.children.forEach(processNode); + } else if (isDecoratorNode(currentNodeDefinition)) { + processNode(currentNodeDefinition.child); + } + }; + processNode(nodeDefinition); + return nodes; +} +function isInteger(value) { + return typeof value === "number" && Math.floor(value) === value; +} +function isNullOrUndefined(value) { + return typeof value === "undefined" || value === null; +} + +// src/mdsl/MDSLUtilities.ts +function popAndCheck(tokens, expected) { + const popped = tokens.shift(); + if (popped === void 0) { + throw new Error("unexpected end of definition"); + } + if (expected != void 0) { + const expectedValues = typeof expected === "string" ? [expected] : expected; + var tokenMatchesExpectation = expectedValues.some((item) => popped.toUpperCase() === item.toUpperCase()); + if (!tokenMatchesExpectation) { + const expectationString = expectedValues.map((item) => "'" + item + "'").join(" or "); + throw new Error("unexpected token found. Expected " + expectationString + " but got '" + popped + "'"); + } + } + return popped; +} +function substituteStringLiterals(definition) { + const placeholders = {}; + const processedDefinition = definition.replace(/\"(\\.|[^"\\])*\"/g, (match) => { + var strippedMatch = match.substring(1, match.length - 1); + var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch); + if (!placeholder) { + placeholder = `@@${Object.keys(placeholders).length}@@`; + placeholders[placeholder] = strippedMatch; + } + return placeholder; + }); + return { placeholders, processedDefinition }; +} +function parseTokensFromDefinition(definition) { + definition = definition.replace(/\(/g, " ( "); + definition = definition.replace(/\)/g, " ) "); + definition = definition.replace(/\{/g, " { "); + definition = definition.replace(/\}/g, " } "); + definition = definition.replace(/\]/g, " ] "); + definition = definition.replace(/\[/g, " [ "); + definition = definition.replace(/\,/g, " , "); + return definition.replace(/\s+/g, " ").trim().split(" "); +} + +// src/mdsl/MDSLNodeArgumentParser.ts +function parseArgumentTokens(tokens, stringArgumentPlaceholders) { + const argumentList = []; + if (!["[", "("].includes(tokens[0])) { + return argumentList; + } + const closingToken = popAndCheck(tokens, ["[", "("]) === "[" ? "]" : ")"; + const argumentListTokens = []; + while (tokens.length && tokens[0] !== closingToken) { + argumentListTokens.push(tokens.shift()); + } + argumentListTokens.forEach((token, index) => { + const shouldBeArgumentToken = !(index & 1); + if (shouldBeArgumentToken) { + const argumentDefinition = getArgumentDefinition(token, stringArgumentPlaceholders); + argumentList.push(argumentDefinition); + } else { + if (token !== ",") { + throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`); + } + } + }); + popAndCheck(tokens, closingToken); + return argumentList; +} +function getArgumentDefinition(token, stringArgumentPlaceholders) { + if (token === "null") { + return { + value: null, + type: "null" + }; + } + if (token === "true" || token === "false") { + return { + value: token === "true", + type: "boolean" + }; + } + if (!isNaN(token)) { + return { + value: parseFloat(token), + isInteger: parseFloat(token) === parseInt(token, 10), + type: "number" + }; + } + if (token.match(/^@@\d+@@$/g)) { + return { + value: stringArgumentPlaceholders[token].replace('\\"', '"'), + type: "string" + }; + } + return { + value: token, + type: "identifier" + }; +} + +// src/mdsl/MDSLNodeAttributeParser.ts +function parseAttributeTokens(tokens, stringArgumentPlaceholders) { + const nodeAttributeNames = ["while", "until", "entry", "exit", "step"]; + const attributes = {}; + let nextAttributeName = tokens[0]?.toLowerCase(); + while (nodeAttributeNames.includes(nextAttributeName)) { + if (attributes[nextAttributeName]) { + throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`); + } + tokens.shift(); + const [attributeCallIdentifier, ...attributeArguments] = parseArgumentTokens( + tokens, + stringArgumentPlaceholders + ); + if (attributeCallIdentifier?.type !== "identifier") { + throw new Error("expected agent function or registered function name identifier argument for attribute"); + } + attributeArguments.filter((arg) => arg.type === "identifier").forEach((arg) => { + throw new Error( + `invalid attribute argument value '${arg.value}', must be string, number, boolean or null` + ); + }); + attributes[nextAttributeName] = { + call: attributeCallIdentifier.value, + args: attributeArguments.map(({ value }) => value) + }; + nextAttributeName = tokens[0]?.toLowerCase(); + } + return attributes; +} + +// src/mdsl/MDSLDefinitionParser.ts +function convertMDSLToJSON(definition) { + const { placeholders, processedDefinition } = substituteStringLiterals(definition); + const tokens = parseTokensFromDefinition(processedDefinition); + return convertTokensToJSONDefinition(tokens, placeholders); +} +function convertTokensToJSONDefinition(tokens, stringLiteralPlaceholders) { + if (tokens.length < 3) { + throw new Error("invalid token count"); + } + if (tokens.filter((token) => token === "{").length !== tokens.filter((token) => token === "}").length) { + throw new Error("scope character mismatch"); + } + const treeStacks = []; + const rootNodes = []; + const pushNode = (node) => { + if (isRootNode(node)) { + if (treeStacks[treeStacks.length - 1]?.length) { + throw new Error("a root node cannot be the child of another node"); + } + rootNodes.push(node); + treeStacks.push([node]); + return; + } + if (!treeStacks.length || !treeStacks[treeStacks.length - 1].length) { + throw new Error("expected root node at base of definition"); + } + const topTreeStack = treeStacks[treeStacks.length - 1]; + const topTreeStackTopNode = topTreeStack[topTreeStack.length - 1]; + if (isCompositeNode(topTreeStackTopNode)) { + topTreeStackTopNode.children = topTreeStackTopNode.children || []; + topTreeStackTopNode.children.push(node); + } else if (isDecoratorNode(topTreeStackTopNode)) { + if (topTreeStackTopNode.child) { + throw new Error("a decorator node must only have a single child node"); + } + topTreeStackTopNode.child = node; + } + if (!isLeafNode(node)) { + topTreeStack.push(node); + } + }; + const popNode = () => { + let poppedNode = null; + const topTreeStack = treeStacks[treeStacks.length - 1]; + if (topTreeStack.length) { + poppedNode = topTreeStack.pop(); + } + if (!topTreeStack.length) { + treeStacks.pop(); + } + return poppedNode; + }; + while (tokens.length) { + const token = tokens.shift(); + switch (token.toUpperCase()) { + case "ROOT": { + pushNode(createRootNode(tokens, stringLiteralPlaceholders)); + break; + } + case "SUCCEED": { + pushNode(createSucceedNode(tokens, stringLiteralPlaceholders)); + break; + } + case "FAIL": { + pushNode(createFailNode(tokens, stringLiteralPlaceholders)); + break; + } + case "FLIP": { + pushNode(createFlipNode(tokens, stringLiteralPlaceholders)); + break; + } + case "REPEAT": { + pushNode(createRepeatNode(tokens, stringLiteralPlaceholders)); + break; + } + case "RETRY": { + pushNode(createRetryNode(tokens, stringLiteralPlaceholders)); + break; + } + case "SEQUENCE": { + pushNode(createSequenceNode(tokens, stringLiteralPlaceholders)); + break; + } + case "SELECTOR": { + pushNode(createSelectorNode(tokens, stringLiteralPlaceholders)); + break; + } + case "PARALLEL": { + pushNode(createParallelNode(tokens, stringLiteralPlaceholders)); + break; + } + case "LOTTO": { + pushNode(createLottoNode(tokens, stringLiteralPlaceholders)); + break; + } + case "ACTION": { + pushNode(createActionNode(tokens, stringLiteralPlaceholders)); + break; + } + case "CONDITION": { + pushNode(createConditionNode(tokens, stringLiteralPlaceholders)); + break; + } + case "WAIT": { + pushNode(createWaitNode(tokens, stringLiteralPlaceholders)); + break; + } + case "BRANCH": { + pushNode(createBranchNode(tokens, stringLiteralPlaceholders)); + break; + } + case "}": { + const poppedNode = popNode(); + if (poppedNode) { + validatePoppedNode(poppedNode); + } + break; + } + default: { + throw new Error(`unexpected token: ${token}`); + } + } + } + return rootNodes; +} +function createRootNode(tokens, stringLiteralPlaceholders) { + let node = { + type: "root" + }; + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length) { + if (nodeArguments.length === 1 && nodeArguments[0].type === "identifier") { + node.id = nodeArguments[0].value; + } else { + throw new Error("expected single root name argument"); + } + } + node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + popAndCheck(tokens, "{"); + return node; +} +function createSucceedNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "succeed", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; +} +function createFailNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "fail", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; +} +function createFlipNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "flip", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; +} +function createRepeatNode(tokens, stringLiteralPlaceholders) { + let node = { type: "repeat" }; + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length) { + nodeArguments.filter((arg) => arg.type !== "number" || !arg.isInteger).forEach(() => { + throw new Error(`repeat node iteration counts must be integer values`); + }); + if (nodeArguments.length === 1) { + node.iterations = nodeArguments[0].value; + if (node.iterations < 0) { + throw new Error("a repeat node must have a positive number of iterations if defined"); + } + } else if (nodeArguments.length === 2) { + node.iterations = [nodeArguments[0].value, nodeArguments[1].value]; + if (node.iterations[0] < 0 || node.iterations[1] < 0) { + throw new Error("a repeat node must have a positive minimum and maximum iteration count if defined"); + } + if (node.iterations[0] > node.iterations[1]) { + throw new Error( + "a repeat node must not have a minimum iteration count that exceeds the maximum iteration count" + ); + } + } else { + throw new Error("invalid number of repeat node iteration count arguments defined"); + } + } + node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + popAndCheck(tokens, "{"); + return node; +} +function createRetryNode(tokens, stringLiteralPlaceholders) { + let node = { type: "retry" }; + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length) { + nodeArguments.filter((arg) => arg.type !== "number" || !arg.isInteger).forEach(() => { + throw new Error(`retry node attempt counts must be integer values`); + }); + if (nodeArguments.length === 1) { + node.attempts = nodeArguments[0].value; + if (node.attempts < 0) { + throw new Error("a retry node must have a positive number of attempts if defined"); + } + } else if (nodeArguments.length === 2) { + node.attempts = [nodeArguments[0].value, nodeArguments[1].value]; + if (node.attempts[0] < 0 || node.attempts[1] < 0) { + throw new Error("a retry node must have a positive minimum and maximum attempt count if defined"); + } + if (node.attempts[0] > node.attempts[1]) { + throw new Error( + "a retry node must not have a minimum attempt count that exceeds the maximum attempt count" + ); + } + } else { + throw new Error("invalid number of retry node attempt count arguments defined"); + } + } + node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + popAndCheck(tokens, "{"); + return node; +} +function createSequenceNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "sequence", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; +} +function createSelectorNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "selector", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; +} +function createParallelNode(tokens, stringLiteralPlaceholders) { + const node = { + type: "parallel", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + popAndCheck(tokens, "{"); + return node; +} +function createLottoNode(tokens, stringLiteralPlaceholders) { + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + nodeArguments.filter((arg) => arg.type !== "number" || !arg.isInteger || arg.value < 0).forEach(() => { + throw new Error(`lotto node weight arguments must be positive integer values`); + }); + const node = { + type: "lotto", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; + if (nodeArguments.length) { + node.weights = nodeArguments.map(({ value }) => value); + } + popAndCheck(tokens, "{"); + return node; +} +function createActionNode(tokens, stringLiteralPlaceholders) { + const [actionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (actionNameIdentifier?.type !== "identifier") { + throw new Error("expected action name identifier argument"); + } + agentFunctionArgs.filter((arg) => arg.type === "identifier").forEach((arg) => { + throw new Error( + `invalid action node argument value '${arg.value}', must be string, number, boolean or null` + ); + }); + return { + type: "action", + call: actionNameIdentifier.value, + args: agentFunctionArgs.map(({ value }) => value), + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; +} +function createConditionNode(tokens, stringLiteralPlaceholders) { + const [conditionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (conditionNameIdentifier?.type !== "identifier") { + throw new Error("expected condition name identifier argument"); + } + agentFunctionArgs.filter((arg) => arg.type === "identifier").forEach((arg) => { + throw new Error( + `invalid condition node argument value '${arg.value}', must be string, number, boolean or null` + ); + }); + return { + type: "condition", + call: conditionNameIdentifier.value, + args: agentFunctionArgs.map(({ value }) => value), + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; +} +function createWaitNode(tokens, stringLiteralPlaceholders) { + let node = { type: "wait" }; + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length) { + nodeArguments.filter((arg) => arg.type !== "number" || !arg.isInteger).forEach(() => { + throw new Error(`wait node durations must be integer values`); + }); + if (nodeArguments.length === 1) { + node.duration = nodeArguments[0].value; + if (node.duration < 0) { + throw new Error("a wait node must have a positive duration"); + } + } else if (nodeArguments.length === 2) { + node.duration = [nodeArguments[0].value, nodeArguments[1].value]; + if (node.duration[0] < 0 || node.duration[1] < 0) { + throw new Error("a wait node must have a positive minimum and maximum duration"); + } + if (node.duration[0] > node.duration[1]) { + throw new Error("a wait node must not have a minimum duration that exceeds the maximum duration"); + } + } else if (nodeArguments.length > 2) { + throw new Error("invalid number of wait node duration arguments defined"); + } + } + return { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; +} +function createBranchNode(tokens, stringLiteralPlaceholders) { + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + if (nodeArguments.length !== 1 || nodeArguments[0].type !== "identifier") { + throw new Error("expected single branch name argument"); + } + return { type: "branch", ref: nodeArguments[0].value }; +} +function validatePoppedNode(definition) { + if (isDecoratorNode(definition) && isNullOrUndefined(definition.child)) { + throw new Error(`a ${definition.type} node must have a single child node defined`); + } + if (isCompositeNode(definition) && !definition.children?.length) { + throw new Error(`a ${definition.type} node must have at least a single child node defined`); + } + if (definition.type === "lotto") { + if (typeof definition.weights !== "undefined") { + if (definition.weights.length !== definition.children.length) { + throw new Error( + "expected a number of weight arguments matching the number of child nodes for lotto node" + ); + } + } + } +} + +// src/BehaviourTreeDefinitionValidator.ts +function validateDefinition(definition) { + if (definition === null || typeof definition === "undefined") { + return createValidationFailureResult("definition is null or undefined"); + } + if (typeof definition === "string") { + return validateMDSLDefinition(definition); + } else if (typeof definition === "object") { + return validateJSONDefinition(definition); + } else { + return createValidationFailureResult(`unexpected definition type of '${typeof definition}'`); + } +} +function validateMDSLDefinition(definition) { + let rootNodeDefinitions; + try { + rootNodeDefinitions = convertMDSLToJSON(definition); + } catch (exception) { + return createValidationFailureResult(exception.message); + } + const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "undefined"); + const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "string" && id.length > 0); + if (mainRootNodeDefinitions.length !== 1) { + return createValidationFailureResult( + "expected single unnamed root node at base of definition to act as main root" + ); + } + const subRootNodeIdenitifers = []; + for (const { id } of subRootNodeDefinitions) { + if (subRootNodeIdenitifers.includes(id)) { + return createValidationFailureResult(`multiple root nodes found with duplicate name '${id}'`); + } + subRootNodeIdenitifers.push(id); + } + try { + validateBranchSubtreeLinks(rootNodeDefinitions, false); + } catch (exception) { + return createValidationFailureResult(exception.message); + } + return { + succeeded: true, + json: rootNodeDefinitions + }; +} +function validateJSONDefinition(definition) { + const rootNodeDefinitions = Array.isArray(definition) ? definition : [definition]; + try { + rootNodeDefinitions.forEach((rootNodeDefinition) => validateNode(rootNodeDefinition, 0)); + } catch (error) { + if (error instanceof Error) { + return createValidationFailureResult(error.message); + } + return createValidationFailureResult(`unexpected error: ${error}`); + } + const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "undefined"); + const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "string" && id.length > 0); + if (mainRootNodeDefinitions.length !== 1) { + return createValidationFailureResult( + "expected single root node without 'id' property defined to act as main root" + ); + } + const subRootNodeIdenitifers = []; + for (const { id } of subRootNodeDefinitions) { + if (subRootNodeIdenitifers.includes(id)) { + return createValidationFailureResult( + `multiple root nodes found with duplicate 'id' property value of '${id}'` + ); + } + subRootNodeIdenitifers.push(id); + } + try { + validateBranchSubtreeLinks(rootNodeDefinitions, false); + } catch (exception) { + return createValidationFailureResult(exception.message); + } + return { + succeeded: true, + json: rootNodeDefinitions + }; +} +function validateBranchSubtreeLinks(rootNodeDefinitions, includesGlobalSubtrees) { + const rootNodeMappings = rootNodeDefinitions.map( + (rootNodeDefinition) => ({ + id: rootNodeDefinition.id, + refs: flattenDefinition(rootNodeDefinition).filter(isBranchNode).map(({ ref }) => ref) + }) + ); + const followRefs = (mapping, path = []) => { + if (path.includes(mapping.id)) { + const badPath = [...path, mapping.id]; + const badPathFormatted = badPath.filter((element) => !!element).join(" => "); + throw new Error(`circular dependency found in branch node references: ${badPathFormatted}`); + } + for (const ref of mapping.refs) { + const subMapping = rootNodeMappings.find(({ id }) => id === ref); + if (subMapping) { + followRefs(subMapping, [...path, mapping.id]); + } else if (includesGlobalSubtrees) { + throw new Error( + mapping.id ? `subtree '${mapping.id}' has branch node that references root node '${ref}' which has not been defined` : `primary tree has branch node that references root node '${ref}' which has not been defined` + ); + } + } + }; + followRefs(rootNodeMappings.find((mapping) => typeof mapping.id === "undefined")); +} +function validateNode(definition, depth) { + if (typeof definition !== "object" || typeof definition.type !== "string" || definition.type.length === 0) { + throw new Error( + `node definition is not an object or 'type' property is not a non-empty string at depth '${depth}'` + ); + } + if (depth === 0 && definition.type !== "root") { + throw new Error(`expected root node at base of definition but got node of type '${definition.type}'`); + } + switch (definition.type) { + case "action": + validateActionNode(definition, depth); + break; + case "condition": + validateConditionNode(definition, depth); + break; + case "wait": + validateWaitNode(definition, depth); + break; + case "branch": + validateBranchNode(definition, depth); + break; + case "root": + validateRootNode(definition, depth); + break; + case "succeed": + validateSucceedNode(definition, depth); + break; + case "fail": + validateFailNode(definition, depth); + break; + case "flip": + validateFlipNode(definition, depth); + break; + case "repeat": + validateRepeatNode(definition, depth); + break; + case "retry": + validateRetryNode(definition, depth); + break; + case "sequence": + validateSequenceNode(definition, depth); + break; + case "selector": + validateSelectorNode(definition, depth); + break; + case "parallel": + validateParallelNode(definition, depth); + break; + case "lotto": + validateLottoNode(definition, depth); + break; + default: + throw new Error(`unexpected node type of '${definition.type}' at depth '${depth}'`); + } +} +function validateNodeAttributes(definition, depth) { + ["while", "until", "entry", "exit", "step"].forEach((attributeName) => { + const attributeDefinition = definition[attributeName]; + if (typeof attributeDefinition === "undefined") { + return; + } + if (typeof attributeDefinition !== "object") { + throw new Error( + `expected attribute '${attributeName}' to be an object for '${definition.type}' node at depth '${depth}'` + ); + } + if (typeof attributeDefinition.call !== "string" || attributeDefinition.call.length === 0) { + throw new Error( + `expected 'call' property for attribute '${attributeName}' to be a non-empty string for '${definition.type}' node at depth '${depth}'` + ); + } + if (typeof attributeDefinition.args !== "undefined" && !Array.isArray(attributeDefinition.args)) { + throw new Error( + `expected 'args' property for attribute '${attributeName}' to be an array for '${definition.type}' node at depth '${depth}'` + ); + } + }); +} +function validateRootNode(definition, depth) { + if (definition.type !== "root") { + throw new Error("expected node type of 'root' for root node"); + } + if (depth > 0) { + throw new Error("a root node cannot be the child of another node"); + } + if (typeof definition.id !== "undefined" && (typeof definition.id !== "string" || definition.id.length === 0)) { + throw new Error("expected non-empty string for 'id' property if defined for root node"); + } + if (typeof definition.child === "undefined") { + throw new Error("expected property 'child' to be defined for root node"); + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); +} +function validateSucceedNode(definition, depth) { + if (definition.type !== "succeed") { + throw new Error(`expected node type of 'succeed' for succeed node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for succeed node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); +} +function validateFailNode(definition, depth) { + if (definition.type !== "fail") { + throw new Error(`expected node type of 'fail' for fail node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for fail node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); +} +function validateFlipNode(definition, depth) { + if (definition.type !== "flip") { + throw new Error(`expected node type of 'flip' for flip node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for flip node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); +} +function validateRepeatNode(definition, depth) { + if (definition.type !== "repeat") { + throw new Error(`expected node type of 'repeat' for repeat node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for repeat node at depth '${depth}'`); + } + if (typeof definition.iterations !== "undefined") { + if (Array.isArray(definition.iterations)) { + const containsNonInteger = !!definition.iterations.filter((value) => !isInteger(value)).length; + if (definition.iterations.length !== 2 || containsNonInteger) { + throw new Error( + `expected array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + if (definition.iterations[0] < 0 || definition.iterations[1] < 0) { + throw new Error( + `expected positive minimum and maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + if (definition.iterations[0] > definition.iterations[1]) { + throw new Error( + `expected minimum iterations count that does not exceed the maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + } else if (isInteger(definition.iterations)) { + if (definition.iterations < 0) { + throw new Error( + `expected positive iterations count for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + } else { + throw new Error( + `expected integer value or array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); +} +function validateRetryNode(definition, depth) { + if (definition.type !== "retry") { + throw new Error(`expected node type of 'retry' for retry node at depth '${depth}'`); + } + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for retry node at depth '${depth}'`); + } + if (typeof definition.attempts !== "undefined") { + if (Array.isArray(definition.attempts)) { + const containsNonInteger = !!definition.attempts.filter((value) => !isInteger(value)).length; + if (definition.attempts.length !== 2 || containsNonInteger) { + throw new Error( + `expected array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + if (definition.attempts[0] < 0 || definition.attempts[1] < 0) { + throw new Error( + `expected positive minimum and maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + if (definition.attempts[0] > definition.attempts[1]) { + throw new Error( + `expected minimum attempts count that does not exceed the maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + } else if (isInteger(definition.attempts)) { + if (definition.attempts < 0) { + throw new Error( + `expected positive attempts count for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + } else { + throw new Error( + `expected integer value or array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + } + validateNodeAttributes(definition, depth); + validateNode(definition.child, depth + 1); +} +function validateBranchNode(definition, depth) { + if (definition.type !== "branch") { + throw new Error(`expected node type of 'branch' for branch node at depth '${depth}'`); + } + if (typeof definition.ref !== "string" || definition.ref.length === 0) { + throw new Error(`expected non-empty string for 'ref' property for branch node at depth '${depth}'`); + } + ["while", "until"].forEach((attributeName) => { + if (typeof definition[attributeName] !== "undefined") { + throw new Error( + `guards should not be defined for branch nodes but guard '${attributeName}' was defined for branch node at depth '${depth}'` + ); + } + }); + ["entry", "exit", "step"].forEach((attributeName) => { + if (typeof definition[attributeName] !== "undefined") { + throw new Error( + `callbacks should not be defined for branch nodes but callback '${attributeName}' was defined for branch node at depth '${depth}'` + ); + } + }); +} +function validateActionNode(definition, depth) { + if (definition.type !== "action") { + throw new Error(`expected node type of 'action' for action node at depth '${depth}'`); + } + if (typeof definition.call !== "string" || definition.call.length === 0) { + throw new Error(`expected non-empty string for 'call' property of action node at depth '${depth}'`); + } + if (typeof definition.args !== "undefined" && !Array.isArray(definition.args)) { + throw new Error(`expected array for 'args' property if defined for action node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); +} +function validateConditionNode(definition, depth) { + if (definition.type !== "condition") { + throw new Error(`expected node type of 'condition' for condition node at depth '${depth}'`); + } + if (typeof definition.call !== "string" || definition.call.length === 0) { + throw new Error(`expected non-empty string for 'call' property of condition node at depth '${depth}'`); + } + if (typeof definition.args !== "undefined" && !Array.isArray(definition.args)) { + throw new Error(`expected array for 'args' property if defined for condition node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); +} +function validateWaitNode(definition, depth) { + if (definition.type !== "wait") { + throw new Error(`expected node type of 'wait' for wait node at depth '${depth}'`); + } + if (typeof definition.duration !== "undefined") { + if (Array.isArray(definition.duration)) { + const containsNonInteger = !!definition.duration.filter((value) => !isInteger(value)).length; + if (definition.duration.length !== 2 || containsNonInteger) { + throw new Error( + `expected array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + if (definition.duration[0] < 0 || definition.duration[1] < 0) { + throw new Error( + `expected positive minimum and maximum duration for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + if (definition.duration[0] > definition.duration[1]) { + throw new Error( + `expected minimum duration value that does not exceed the maximum duration value for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + } else if (isInteger(definition.duration)) { + if (definition.duration < 0) { + throw new Error( + `expected positive duration value for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + } else { + throw new Error( + `expected integer value or array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + } + validateNodeAttributes(definition, depth); +} +function validateSequenceNode(definition, depth) { + if (definition.type !== "sequence") { + throw new Error(`expected node type of 'sequence' for sequence node at depth '${depth}'`); + } + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for sequence node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + definition.children.forEach((child) => validateNode(child, depth + 1)); +} +function validateSelectorNode(definition, depth) { + if (definition.type !== "selector") { + throw new Error(`expected node type of 'selector' for selector node at depth '${depth}'`); + } + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for selector node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + definition.children.forEach((child) => validateNode(child, depth + 1)); +} +function validateParallelNode(definition, depth) { + if (definition.type !== "parallel") { + throw new Error(`expected node type of 'parallel' for parallel node at depth '${depth}'`); + } + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for parallel node at depth '${depth}'`); + } + validateNodeAttributes(definition, depth); + definition.children.forEach((child) => validateNode(child, depth + 1)); +} +function validateLottoNode(definition, depth) { + if (definition.type !== "lotto") { + throw new Error(`expected node type of 'lotto' for lotto node at depth '${depth}'`); + } + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for lotto node at depth '${depth}'`); + } + if (typeof definition.weights !== "undefined") { + if (!Array.isArray(definition.weights) || definition.weights.length !== definition.children.length || definition.weights.filter((value) => !isInteger(value)).length || definition.weights.filter((value) => value < 0).length) { + throw new Error( + `expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '${depth}'` + ); + } + } + validateNodeAttributes(definition, depth); + definition.children.forEach((child) => validateNode(child, depth + 1)); +} +function createValidationFailureResult(errorMessage) { + return { succeeded: false, errorMessage }; +} + +// src/Lookup.ts +var Lookup = class { + static getFunc(name) { + return this.registeredFunctions[name]; + } + static setFunc(name, func) { + this.registeredFunctions[name] = func; + } + static getFuncInvoker(agent, name) { + const agentFunction = agent[name]; + if (agentFunction && typeof agentFunction === "function") { + return (args) => agentFunction.apply(agent, args); + } + if (this.registeredFunctions[name] && typeof this.registeredFunctions[name] === "function") { + const registeredFunction = this.registeredFunctions[name]; + return (args) => registeredFunction(agent, ...args); + } + return null; + } + static getSubtrees() { + return this.registeredSubtrees; + } + static setSubtree(name, subtree) { + this.registeredSubtrees[name] = subtree; + } + static remove(name) { + delete this.registeredFunctions[name]; + delete this.registeredSubtrees[name]; + } + static empty() { + this.registeredFunctions = {}; + this.registeredSubtrees = {}; + } +}; +__publicField(Lookup, "registeredFunctions", {}); +__publicField(Lookup, "registeredSubtrees", {}); + // src/attributes/guards/GuardUnsatisifedException.ts var GuardUnsatisifedException = class extends Error { constructor(source) { @@ -280,15 +1286,6 @@ var GuardPath = class { }; }; -// src/State.ts -var State = /* @__PURE__ */ ((State2) => { - State2["READY"] = "mistreevous.ready"; - State2["RUNNING"] = "mistreevous.running"; - State2["SUCCEEDED"] = "mistreevous.succeeded"; - State2["FAILED"] = "mistreevous.failed"; - return State2; -})(State || {}); - // src/nodes/Node.ts var Node = class { constructor(type, attributes, args) { @@ -308,7 +1305,7 @@ var Node = class { getAttributes = () => this.attributes; getArguments = () => this.args; getAttribute(type) { - return this.getAttributes().filter((decorator) => decorator.getType().toUpperCase() === type.toUpperCase())[0] || null; + return this.getAttributes().filter((decorator) => decorator.type.toUpperCase() === type.toUpperCase())[0] || null; } getGuardAttributes = () => this.getAttributes().filter((decorator) => decorator.isGuard()); setGuardPath = (value) => this.guardPath = value; @@ -357,191 +1354,157 @@ function createNodeUid() { return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4(); } -// src/nodes/leaf/Leaf.ts -var Leaf = class extends Node { - isLeafNode = () => true; -}; - -// src/Lookup.ts -var Lookup = class { - static getFunc(name) { - return this.functionTable[name]; - } - static setFunc(name, func) { - this.functionTable[name] = func; +// src/nodes/composite/Composite.ts +var Composite = class extends Node { + constructor(type, attributes, children) { + super(type, attributes, []); + this.children = children; } - static getFuncInvoker(agent, name) { - const foundOnAgent = agent[name]; - if (foundOnAgent && typeof foundOnAgent === "function") { - return (args) => foundOnAgent.apply( - agent, - args.map((arg) => arg.value) - ); - } - if (this.functionTable[name] && typeof this.functionTable[name] === "function") { - return (args) => this.functionTable[name](agent, ...args.map((arg) => arg.value)); + isLeafNode = () => false; + getChildren = () => this.children; + reset = () => { + this.setState("mistreevous.ready" /* READY */); + this.getChildren().forEach((child) => child.reset()); + }; + abort = (agent) => { + if (!this.is("mistreevous.running" /* RUNNING */)) { + return; } - return null; - } - static getSubtree(name) { - return this.subtreeTable[name]; - } - static setSubtree(name, subtree) { - this.subtreeTable[name] = subtree; - } - static remove(name) { - delete this.functionTable[name]; - delete this.subtreeTable[name]; - } - static empty() { - this.functionTable = {}; - this.subtreeTable = {}; - } + this.getChildren().forEach((child) => child.abort(agent)); + this.reset(); + this.getAttribute("exit")?.callAgentFunction(agent, false, true); + }; }; -__publicField(Lookup, "functionTable", {}); -__publicField(Lookup, "subtreeTable", {}); -// src/nodes/leaf/Action.ts -var Action = class extends Leaf { - constructor(attributes, actionName, actionArguments) { - super("action", attributes, actionArguments); - this.actionName = actionName; - this.actionArguments = actionArguments; +// src/nodes/composite/Parallel.ts +var Parallel = class extends Composite { + constructor(attributes, children) { + super("parallel", attributes, children); } - isUsingUpdatePromise = false; - updatePromiseStateResult = null; onUpdate(agent, options) { - if (this.isUsingUpdatePromise) { - if (this.updatePromiseStateResult) { - this.setState(this.updatePromiseStateResult); + let succeededCount = 0; + let hasChildFailed = false; + for (const child of this.children) { + if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { + child.update(agent, options); + } + if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { + succeededCount++; + continue; + } + if (child.getState() === "mistreevous.failed" /* FAILED */) { + hasChildFailed = true; + break; + } + if (child.getState() !== "mistreevous.running" /* RUNNING */) { + throw new Error("child node was not in an expected state."); } - return; - } - const actionFuncInvoker = Lookup.getFuncInvoker(agent, this.actionName); - if (actionFuncInvoker === null) { - throw new Error( - `cannot update action node as the action '${this.actionName}' function is not defined on the agent and has not been registered` - ); } - const updateResult = actionFuncInvoker(this.actionArguments); - if (updateResult instanceof Promise) { - updateResult.then( - (result) => { - if (!this.isUsingUpdatePromise) { - return; - } - if (result !== "mistreevous.succeeded" /* SUCCEEDED */ && result !== "mistreevous.failed" /* FAILED */) { - throw new Error( - "action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned" - ); - } - this.updatePromiseStateResult = result; - }, - (reason) => { - if (!this.isUsingUpdatePromise) { - return; - } - throw new Error(reason); + if (hasChildFailed) { + this.setState("mistreevous.failed" /* FAILED */); + for (const child of this.children) { + if (child.getState() === "mistreevous.running" /* RUNNING */) { + child.abort(agent); } - ); - this.setState("mistreevous.running" /* RUNNING */); - this.isUsingUpdatePromise = true; + } } else { - this.validateUpdateResult(updateResult); - this.setState(updateResult || "mistreevous.running" /* RUNNING */); + this.setState(succeededCount === this.children.length ? "mistreevous.succeeded" /* SUCCEEDED */ : "mistreevous.running" /* RUNNING */); } } - getName = () => this.actionName; - reset = () => { - this.setState("mistreevous.ready" /* READY */); - this.isUsingUpdatePromise = false; - this.updatePromiseStateResult = null; - }; - validateUpdateResult = (result) => { - switch (result) { - case "mistreevous.succeeded" /* SUCCEEDED */: - case "mistreevous.failed" /* FAILED */: - case void 0: + getName = () => "PARALLEL"; +}; + +// src/nodes/composite/Selector.ts +var Selector = class extends Composite { + constructor(attributes, children) { + super("selector", attributes, children); + this.children = children; + } + onUpdate(agent, options) { + for (const child of this.children) { + if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { + child.update(agent, options); + } + if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { + this.setState("mistreevous.succeeded" /* SUCCEEDED */); + return; + } + if (child.getState() === "mistreevous.failed" /* FAILED */) { + if (this.children.indexOf(child) === this.children.length - 1) { + this.setState("mistreevous.failed" /* FAILED */); + return; + } else { + continue; + } + } + if (child.getState() === "mistreevous.running" /* RUNNING */) { + this.setState("mistreevous.running" /* RUNNING */); return; - default: - throw new Error( - `action '${this.actionName}' 'onUpdate' returned an invalid response, expected an optional State.SUCCEEDED or State.FAILED value to be returned` - ); + } + throw new Error("child node was not in an expected state."); } - }; + } + getName = () => "SELECTOR"; }; -// src/nodes/leaf/Condition.ts -var Condition = class extends Leaf { - constructor(attributes, conditionName, conditionArguments) { - super("condition", attributes, conditionArguments); - this.conditionName = conditionName; - this.conditionArguments = conditionArguments; +// src/nodes/composite/Sequence.ts +var Sequence = class extends Composite { + constructor(attributes, children) { + super("sequence", attributes, children); + this.children = children; } onUpdate(agent, options) { - const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.conditionName); - if (conditionFuncInvoker === null) { - throw new Error( - `cannot update condition node as the condition '${this.conditionName}' function is not defined on the agent and has not been registered` - ); + for (const child of this.children) { + if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { + child.update(agent, options); + } + if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { + if (this.children.indexOf(child) === this.children.length - 1) { + this.setState("mistreevous.succeeded" /* SUCCEEDED */); + return; + } else { + continue; + } + } + if (child.getState() === "mistreevous.failed" /* FAILED */) { + this.setState("mistreevous.failed" /* FAILED */); + return; + } + if (child.getState() === "mistreevous.running" /* RUNNING */) { + this.setState("mistreevous.running" /* RUNNING */); + return; + } + throw new Error("child node was not in an expected state."); } - this.setState(!!conditionFuncInvoker(this.conditionArguments) ? "mistreevous.succeeded" /* SUCCEEDED */ : "mistreevous.failed" /* FAILED */); } - getName = () => this.conditionName; + getName = () => "SEQUENCE"; }; -// src/nodes/leaf/Wait.ts -var Wait = class extends Leaf { - constructor(attributes, duration, durationMin, durationMax) { - super("wait", attributes, []); - this.duration = duration; - this.durationMin = durationMin; - this.durationMax = durationMax; +// src/nodes/composite/Lotto.ts +var import_lotto_draw = __toESM(require_dist()); +var Lotto = class extends Composite { + constructor(attributes, weights, children) { + super("lotto", attributes, children); + this.weights = weights; } - initialUpdateTime = 0; - totalDuration = null; - waitedDuration = 0; + selectedChild; onUpdate(agent, options) { if (this.is("mistreevous.ready" /* READY */)) { - this.initialUpdateTime = new Date().getTime(); - this.waitedDuration = 0; - if (this.duration !== null) { - this.totalDuration = this.duration; - } else if (this.durationMin !== null && this.durationMax !== null) { - const random = typeof options.random === "function" ? options.random : Math.random; - this.totalDuration = Math.floor( - random() * (this.durationMax - this.durationMin + 1) + this.durationMin - ); - } else { - this.totalDuration = null; - } - this.setState("mistreevous.running" /* RUNNING */); - } - if (this.totalDuration === null) { - return; + const lottoDraw = (0, import_lotto_draw.default)({ + random: options.random, + participants: this.children.map((child, index) => [child, this.weights?.[index] || 1]) + }); + this.selectedChild = lottoDraw.draw() || void 0; } - if (typeof options.getDeltaTime === "function") { - const deltaTime = options.getDeltaTime(); - if (typeof deltaTime !== "number" || isNaN(deltaTime)) { - throw new Error("The delta time must be a valid number and not NaN."); - } - this.waitedDuration += deltaTime * 1e3; - } else { - this.waitedDuration = new Date().getTime() - this.initialUpdateTime; + if (!this.selectedChild) { + throw new Error("failed to update lotto node as it has no active child"); } - if (this.waitedDuration >= this.totalDuration) { - this.setState("mistreevous.succeeded" /* SUCCEEDED */); + if (this.selectedChild.getState() === "mistreevous.ready" /* READY */ || this.selectedChild.getState() === "mistreevous.running" /* RUNNING */) { + this.selectedChild.update(agent, options); } + this.setState(this.selectedChild.getState()); } - getName = () => { - if (this.duration !== null) { - return `WAIT ${this.duration}ms`; - } else if (this.durationMin !== null && this.durationMax !== null) { - return `WAIT ${this.durationMin}ms-${this.durationMax}ms`; - } else { - return "WAIT"; - } - }; + getName = () => this.weights ? `LOTTO [${this.weights.join(",")}]` : "LOTTO"; }; // src/nodes/decorator/Decorator.ts @@ -566,18 +1529,54 @@ var Decorator = class extends Node { }; }; -// src/nodes/decorator/Root.ts -var Root = class extends Decorator { +// src/nodes/decorator/Fail.ts +var Fail = class extends Decorator { constructor(attributes, child) { - super("root", attributes, child); + super("fail", attributes, child); } onUpdate(agent, options) { if (this.child.getState() === "mistreevous.ready" /* READY */ || this.child.getState() === "mistreevous.running" /* RUNNING */) { this.child.update(agent, options); } - this.setState(this.child.getState()); + switch (this.child.getState()) { + case "mistreevous.running" /* RUNNING */: + this.setState("mistreevous.running" /* RUNNING */); + break; + case "mistreevous.succeeded" /* SUCCEEDED */: + case "mistreevous.failed" /* FAILED */: + this.setState("mistreevous.failed" /* FAILED */); + break; + default: + this.setState("mistreevous.ready" /* READY */); + } } - getName = () => "ROOT"; + getName = () => "FAIL"; +}; + +// src/nodes/decorator/Flip.ts +var Flip = class extends Decorator { + constructor(attributes, child) { + super("flip", attributes, child); + } + onUpdate(agent, options) { + if (this.child.getState() === "mistreevous.ready" /* READY */ || this.child.getState() === "mistreevous.running" /* RUNNING */) { + this.child.update(agent, options); + } + switch (this.child.getState()) { + case "mistreevous.running" /* RUNNING */: + this.setState("mistreevous.running" /* RUNNING */); + break; + case "mistreevous.succeeded" /* SUCCEEDED */: + this.setState("mistreevous.failed" /* FAILED */); + break; + case "mistreevous.failed" /* FAILED */: + this.setState("mistreevous.succeeded" /* SUCCEEDED */); + break; + default: + this.setState("mistreevous.ready" /* READY */); + } + } + getName = () => "FLIP"; }; // src/nodes/decorator/Repeat.ts @@ -712,30 +1711,18 @@ var Retry = class extends Decorator { }; }; -// src/nodes/decorator/Flip.ts -var Flip = class extends Decorator { +// src/nodes/decorator/Root.ts +var Root = class extends Decorator { constructor(attributes, child) { - super("flip", attributes, child); + super("root", attributes, child); } onUpdate(agent, options) { if (this.child.getState() === "mistreevous.ready" /* READY */ || this.child.getState() === "mistreevous.running" /* RUNNING */) { this.child.update(agent, options); } - switch (this.child.getState()) { - case "mistreevous.running" /* RUNNING */: - this.setState("mistreevous.running" /* RUNNING */); - break; - case "mistreevous.succeeded" /* SUCCEEDED */: - this.setState("mistreevous.failed" /* FAILED */); - break; - case "mistreevous.failed" /* FAILED */: - this.setState("mistreevous.succeeded" /* SUCCEEDED */); - break; - default: - this.setState("mistreevous.ready" /* READY */); - } + this.setState(this.child.getState()); } - getName = () => "FLIP"; + getName = () => "ROOT"; }; // src/nodes/decorator/Succeed.ts @@ -762,183 +1749,189 @@ var Succeed = class extends Decorator { getName = () => "SUCCEED"; }; -// src/nodes/decorator/Fail.ts -var Fail = class extends Decorator { - constructor(attributes, child) { - super("fail", attributes, child); - } - onUpdate(agent, options) { - if (this.child.getState() === "mistreevous.ready" /* READY */ || this.child.getState() === "mistreevous.running" /* RUNNING */) { - this.child.update(agent, options); - } - switch (this.child.getState()) { - case "mistreevous.running" /* RUNNING */: - this.setState("mistreevous.running" /* RUNNING */); - break; - case "mistreevous.succeeded" /* SUCCEEDED */: - case "mistreevous.failed" /* FAILED */: - this.setState("mistreevous.failed" /* FAILED */); - break; - default: - this.setState("mistreevous.ready" /* READY */); - } - } - getName = () => "FAIL"; -}; - -// src/nodes/composite/Lotto.ts -var import_lotto_draw = __toESM(require_dist()); - -// src/nodes/composite/Composite.ts -var Composite = class extends Node { - constructor(type, attributes, children) { - super(type, attributes, []); - this.children = children; - } - isLeafNode = () => false; - getChildren = () => this.children; - reset = () => { - this.setState("mistreevous.ready" /* READY */); - this.getChildren().forEach((child) => child.reset()); - }; - abort = (agent) => { - if (!this.is("mistreevous.running" /* RUNNING */)) { - return; - } - this.getChildren().forEach((child) => child.abort(agent)); - this.reset(); - this.getAttribute("exit")?.callAgentFunction(agent, false, true); - }; -}; - -// src/nodes/composite/Lotto.ts -var Lotto = class extends Composite { - constructor(attributes, tickets, children) { - super("lotto", attributes, children); - this.tickets = tickets; - } - selectedChild; - onUpdate(agent, options) { - if (this.is("mistreevous.ready" /* READY */)) { - const lottoDraw = (0, import_lotto_draw.default)({ - random: options.random, - participants: this.children.map((child, index) => [child, this.tickets[index] || 1]) - }); - this.selectedChild = lottoDraw.draw() || void 0; - } - if (!this.selectedChild) { - throw new Error("failed to update lotto node as it has no active child"); - } - if (this.selectedChild.getState() === "mistreevous.ready" /* READY */ || this.selectedChild.getState() === "mistreevous.running" /* RUNNING */) { - this.selectedChild.update(agent, options); - } - this.setState(this.selectedChild.getState()); - } - getName = () => this.tickets.length ? `LOTTO [${this.tickets.join(",")}]` : "LOTTO"; -}; - -// src/nodes/composite/Selector.ts -var Selector = class extends Composite { - constructor(attributes, children) { - super("selector", attributes, children); - this.children = children; +// src/nodes/leaf/Leaf.ts +var Leaf = class extends Node { + isLeafNode = () => true; +}; + +// src/nodes/leaf/Action.ts +var Action = class extends Leaf { + constructor(attributes, actionName, actionArguments) { + super("action", attributes, actionArguments); + this.actionName = actionName; + this.actionArguments = actionArguments; } + isUsingUpdatePromise = false; + updatePromiseResult = null; onUpdate(agent, options) { - for (const child of this.children) { - if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { - child.update(agent, options); - } - if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { - this.setState("mistreevous.succeeded" /* SUCCEEDED */); + if (this.isUsingUpdatePromise) { + if (!this.updatePromiseResult) { return; } - if (child.getState() === "mistreevous.failed" /* FAILED */) { - if (this.children.indexOf(child) === this.children.length - 1) { - this.setState("mistreevous.failed" /* FAILED */); - return; - } else { - continue; + const { isResolved, value } = this.updatePromiseResult; + if (isResolved) { + if (value !== "mistreevous.succeeded" /* SUCCEEDED */ && value !== "mistreevous.failed" /* FAILED */) { + throw new Error( + "action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned" + ); } - } - if (child.getState() === "mistreevous.running" /* RUNNING */) { - this.setState("mistreevous.running" /* RUNNING */); + this.setState(value); return; + } else { + throw new Error(`action function '${this.actionName}' promise rejected with '${value}'`); } - throw new Error("child node was not in an expected state."); + } + const actionFuncInvoker = Lookup.getFuncInvoker(agent, this.actionName); + if (actionFuncInvoker === null) { + throw new Error( + `cannot update action node as the action '${this.actionName}' function is not defined on the agent and has not been registered` + ); + } + let actionFunctionResult; + try { + actionFunctionResult = actionFuncInvoker(this.actionArguments); + } catch (error) { + if (error instanceof Error) { + throw new Error(`action function '${this.actionName}' threw: ${error.stack}`); + } else { + throw new Error(`action function '${this.actionName}' threw: ${error}`); + } + } + if (actionFunctionResult instanceof Promise) { + actionFunctionResult.then( + (result) => { + if (!this.isUsingUpdatePromise) { + return; + } + this.updatePromiseResult = { + isResolved: true, + value: result + }; + }, + (reason) => { + if (!this.isUsingUpdatePromise) { + return; + } + this.updatePromiseResult = { + isResolved: false, + value: reason + }; + } + ); + this.setState("mistreevous.running" /* RUNNING */); + this.isUsingUpdatePromise = true; + } else { + this.validateUpdateResult(actionFunctionResult); + this.setState(actionFunctionResult || "mistreevous.running" /* RUNNING */); } } - getName = () => "SELECTOR"; + getName = () => this.actionName; + reset = () => { + this.setState("mistreevous.ready" /* READY */); + this.isUsingUpdatePromise = false; + this.updatePromiseResult = null; + }; + validateUpdateResult = (result) => { + switch (result) { + case "mistreevous.succeeded" /* SUCCEEDED */: + case "mistreevous.failed" /* FAILED */: + case "mistreevous.running" /* RUNNING */: + case void 0: + return; + default: + throw new Error( + `expected action function '${this.actionName}' to return an optional State.SUCCEEDED or State.FAILED value but returned '${result}'` + ); + } + }; }; -// src/nodes/composite/Sequence.ts -var Sequence = class extends Composite { - constructor(attributes, children) { - super("sequence", attributes, children); - this.children = children; +// src/nodes/leaf/Condition.ts +var Condition = class extends Leaf { + constructor(attributes, conditionName, conditionArguments) { + super("condition", attributes, conditionArguments); + this.conditionName = conditionName; + this.conditionArguments = conditionArguments; } onUpdate(agent, options) { - for (const child of this.children) { - if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { - child.update(agent, options); - } - if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { - if (this.children.indexOf(child) === this.children.length - 1) { - this.setState("mistreevous.succeeded" /* SUCCEEDED */); - return; - } else { - continue; - } - } - if (child.getState() === "mistreevous.failed" /* FAILED */) { - this.setState("mistreevous.failed" /* FAILED */); - return; - } - if (child.getState() === "mistreevous.running" /* RUNNING */) { - this.setState("mistreevous.running" /* RUNNING */); - return; + const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.conditionName); + if (conditionFuncInvoker === null) { + throw new Error( + `cannot update condition node as the condition '${this.conditionName}' function is not defined on the agent and has not been registered` + ); + } + let conditionFunctionResult; + try { + conditionFunctionResult = conditionFuncInvoker(this.conditionArguments); + } catch (error) { + if (error instanceof Error) { + throw new Error(`condition function '${this.conditionName}' threw: ${error.stack}`); + } else { + throw new Error(`condition function '${this.conditionName}' threw: ${error}`); } - throw new Error("child node was not in an expected state."); } + if (typeof conditionFunctionResult !== "boolean") { + throw new Error( + `expected condition function '${this.conditionName}' to return a boolean but returned '${conditionFunctionResult}'` + ); + } + this.setState(!!conditionFunctionResult ? "mistreevous.succeeded" /* SUCCEEDED */ : "mistreevous.failed" /* FAILED */); } - getName = () => "SEQUENCE"; + getName = () => this.conditionName; }; -// src/nodes/composite/Parallel.ts -var Parallel = class extends Composite { - constructor(attributes, children) { - super("parallel", attributes, children); +// src/nodes/leaf/Wait.ts +var Wait = class extends Leaf { + constructor(attributes, duration, durationMin, durationMax) { + super("wait", attributes, []); + this.duration = duration; + this.durationMin = durationMin; + this.durationMax = durationMax; } + initialUpdateTime = 0; + totalDuration = null; + waitedDuration = 0; onUpdate(agent, options) { - let succeededCount = 0; - let hasChildFailed = false; - for (const child of this.children) { - if (child.getState() === "mistreevous.ready" /* READY */ || child.getState() === "mistreevous.running" /* RUNNING */) { - child.update(agent, options); - } - if (child.getState() === "mistreevous.succeeded" /* SUCCEEDED */) { - succeededCount++; - continue; - } - if (child.getState() === "mistreevous.failed" /* FAILED */) { - hasChildFailed = true; - break; - } - if (child.getState() !== "mistreevous.running" /* RUNNING */) { - throw new Error("child node was not in an expected state."); + if (this.is("mistreevous.ready" /* READY */)) { + this.initialUpdateTime = new Date().getTime(); + this.waitedDuration = 0; + if (this.duration !== null) { + this.totalDuration = this.duration; + } else if (this.durationMin !== null && this.durationMax !== null) { + const random = typeof options.random === "function" ? options.random : Math.random; + this.totalDuration = Math.floor( + random() * (this.durationMax - this.durationMin + 1) + this.durationMin + ); + } else { + this.totalDuration = null; } + this.setState("mistreevous.running" /* RUNNING */); } - if (hasChildFailed) { - this.setState("mistreevous.failed" /* FAILED */); - for (const child of this.children) { - if (child.getState() === "mistreevous.running" /* RUNNING */) { - child.abort(agent); - } + if (this.totalDuration === null) { + return; + } + if (typeof options.getDeltaTime === "function") { + const deltaTime = options.getDeltaTime(); + if (typeof deltaTime !== "number" || isNaN(deltaTime)) { + throw new Error("The delta time must be a valid number and not NaN."); } + this.waitedDuration += deltaTime * 1e3; } else { - this.setState(succeededCount === this.children.length ? "mistreevous.succeeded" /* SUCCEEDED */ : "mistreevous.running" /* RUNNING */); + this.waitedDuration = new Date().getTime() - this.initialUpdateTime; + } + if (this.waitedDuration >= this.totalDuration) { + this.setState("mistreevous.succeeded" /* SUCCEEDED */); } } - getName = () => "PARALLEL"; + getName = () => { + if (this.duration !== null) { + return `WAIT ${this.duration}ms`; + } else if (this.durationMin !== null && this.durationMax !== null) { + return `WAIT ${this.durationMin}ms-${this.durationMax}ms`; + } else { + return "WAIT"; + } + }; }; // src/attributes/Attribute.ts @@ -947,8 +1940,6 @@ var Attribute = class { this.type = type; this.args = args; } - getType = () => this.type; - getArguments = () => this.args; }; // src/attributes/guards/Guard.ts @@ -961,8 +1952,8 @@ var Guard = class extends Attribute { isGuard = () => true; getDetails() { return { - type: this.getType(), - args: this.getArguments(), + type: this.type, + args: this.args, condition: this.getCondition() }; } @@ -980,7 +1971,22 @@ var While = class extends Guard { `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered` ); } - return !!conditionFuncInvoker(this.args); + let conditionFunctionResult; + try { + conditionFunctionResult = conditionFuncInvoker(this.args); + } catch (error) { + if (error instanceof Error) { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`); + } else { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`); + } + } + if (typeof conditionFunctionResult !== "boolean") { + throw new Error( + `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'` + ); + } + return conditionFunctionResult; }; }; @@ -996,7 +2002,22 @@ var Until = class extends Guard { `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered` ); } - return !!!conditionFuncInvoker(this.args); + let conditionFunctionResult; + try { + conditionFunctionResult = conditionFuncInvoker(this.args); + } catch (error) { + if (error instanceof Error) { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`); + } else { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`); + } + } + if (typeof conditionFunctionResult !== "boolean") { + throw new Error( + `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'` + ); + } + return !conditionFunctionResult; }; }; @@ -1010,8 +2031,8 @@ var Callback = class extends Attribute { isGuard = () => false; getDetails() { return { - type: this.getType(), - args: this.getArguments(), + type: this.type, + args: this.args, functionName: this.getFunctionName() }; } @@ -1033,22 +2054,6 @@ var Entry = class extends Callback { }; }; -// src/attributes/callbacks/Exit.ts -var Exit = class extends Callback { - constructor(functionName, args) { - super("exit", args, functionName); - } - callAgentFunction = (agent, isSuccess, isAborted) => { - const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName()); - if (callbackFuncInvoker === null) { - throw new Error( - `cannot call exit function '${this.getFunctionName()}' as is not defined on the agent and has not been registered` - ); - } - callbackFuncInvoker([{ value: { succeeded: isSuccess, aborted: isAborted } }, ...this.args]); - }; -}; - // src/attributes/callbacks/Step.ts var Step = class extends Callback { constructor(functionName, args) { @@ -1065,668 +2070,171 @@ var Step = class extends Callback { }; }; -// src/RootAstNodesBuilder.ts -var AttributeFactories = { - WHILE: (condition, attributeArguments) => new While(condition, attributeArguments), - UNTIL: (condition, attributeArguments) => new Until(condition, attributeArguments), - ENTRY: (functionName, attributeArguments) => new Entry(functionName, attributeArguments), - EXIT: (functionName, attributeArguments) => new Exit(functionName, attributeArguments), - STEP: (functionName, attributeArguments) => new Step(functionName, attributeArguments) -}; -var ASTNodeFactories = { - ROOT: () => ({ - type: "root", - attributes: [], - name: null, - children: [], - validate(depth) { - if (depth > 1) { - throw new Error("a root node cannot be the child of another node"); - } - if (this.children.length !== 1) { - throw new Error("a root node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Root( - this.attributes, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) - ); - } - }), - BRANCH: () => ({ - type: "branch", - branchName: "", - validate() { - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - const targetRootNode = namedRootNodeProvider(this.branchName); - if (visitedBranches.indexOf(this.branchName) !== -1) { - throw new Error(`circular dependency found in branch node references for branch '${this.branchName}'`); - } - if (targetRootNode) { - return targetRootNode.createNodeInstance(namedRootNodeProvider, visitedBranches.concat(this.branchName)).getChildren()[0]; - } else { - throw new Error(`branch references root node '${this.branchName}' which has not been defined`); - } - } - }), - SELECTOR: () => ({ - type: "selector", - attributes: [], - children: [], - validate() { - if (this.children.length < 1) { - throw new Error("a selector node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Selector( - this.attributes, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - SEQUENCE: () => ({ - type: "sequence", - attributes: [], - children: [], - validate() { - if (this.children.length < 1) { - throw new Error("a sequence node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Sequence( - this.attributes, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - PARALLEL: () => ({ - type: "parallel", - attributes: [], - children: [], - validate() { - if (this.children.length < 1) { - throw new Error("a parallel node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Parallel( - this.attributes, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - LOTTO: () => ({ - type: "lotto", - attributes: [], - children: [], - tickets: [], - validate() { - if (this.children.length < 1) { - throw new Error("a lotto node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Lotto( - this.attributes, - this.tickets, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) +// src/attributes/callbacks/Exit.ts +var Exit = class extends Callback { + constructor(functionName, args) { + super("exit", args, functionName); + } + callAgentFunction = (agent, isSuccess, isAborted) => { + const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName()); + if (callbackFuncInvoker === null) { + throw new Error( + `cannot call exit function '${this.getFunctionName()}' as is not defined on the agent and has not been registered` ); } - }), - REPEAT: () => ({ - type: "repeat", - attributes: [], - iterations: null, - iterationsMin: null, - iterationsMax: null, - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a repeat node must have a single child"); - } - if (this.iterations !== null) { - if (this.iterations < 0) { - throw new Error("a repeat node must have a positive number of iterations if defined"); - } - } else if (this.iterationsMin !== null && this.iterationsMax !== null) { - if (this.iterationsMin < 0 || this.iterationsMax < 0) { - throw new Error( - "a repeat node must have a positive minimum and maximum iteration count if defined" - ); - } - if (this.iterationsMin > this.iterationsMax) { - throw new Error( - "a repeat node must not have a minimum iteration count that exceeds the maximum iteration count" - ); - } - } else { + callbackFuncInvoker([{ succeeded: isSuccess, aborted: isAborted }, ...this.args]); + }; +}; + +// src/BehaviourTreeBuilder.ts +var MAIN_ROOT_NODE_KEY = Symbol("__root__"); +function buildRootNode(definition) { + const rootNodeDefinitionMap = createRootNodeDefinitionMap(definition); + validateBranchSubtreeLinks( + [rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], ...Object.values(rootNodeDefinitionMap)], + true + ); + const rootNode = nodeFactory(rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], rootNodeDefinitionMap); + applyLeafNodeGuardPaths(rootNode); + return rootNode; +} +function nodeFactory(definition, rootNodeDefinitionMap) { + const attributes = nodeAttributesFactory(definition); + switch (definition.type) { + case "root": + return new Root(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + case "repeat": + let iterations = null; + let iterationsMin = null; + let iterationsMax = null; + if (Array.isArray(definition.iterations)) { + iterationsMin = definition.iterations[0]; + iterationsMax = definition.iterations[1]; + } else if (isInteger(definition.iterations)) { + iterations = definition.iterations; } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { return new Repeat( - this.attributes, - this.iterations, - this.iterationsMin, - this.iterationsMax, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + attributes, + iterations, + iterationsMin, + iterationsMax, + nodeFactory(definition.child, rootNodeDefinitionMap) ); - } - }), - RETRY: () => ({ - type: "retry", - attributes: [], - attempts: null, - attemptsMin: null, - attemptsMax: null, - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a retry node must have a single child"); - } - if (this.attempts !== null) { - if (this.attempts < 0) { - throw new Error("a retry node must have a positive number of attempts if defined"); - } - } else if (this.attemptsMin !== null && this.attemptsMax !== null) { - if (this.attemptsMin < 0 || this.attemptsMax < 0) { - throw new Error("a retry node must have a positive minimum and maximum attempt count if defined"); - } - if (this.attemptsMin > this.attemptsMax) { - throw new Error( - "a retry node must not have a minimum attempt count that exceeds the maximum attempt count" - ); - } - } else { + case "retry": + let attempts = null; + let attemptsMin = null; + let attemptsMax = null; + if (Array.isArray(definition.attempts)) { + attemptsMin = definition.attempts[0]; + attemptsMax = definition.attempts[1]; + } else if (isInteger(definition.attempts)) { + attempts = definition.attempts; } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { return new Retry( - this.attributes, - this.attempts, - this.attemptsMin, - this.attemptsMax, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + attributes, + attempts, + attemptsMin, + attemptsMax, + nodeFactory(definition.child, rootNodeDefinitionMap) ); - } - }), - FLIP: () => ({ - type: "flip", - attributes: [], - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a flip node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Flip( - this.attributes, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + case "flip": + return new Flip(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + case "succeed": + return new Succeed(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + case "fail": + return new Fail(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + case "sequence": + return new Sequence( + attributes, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) ); - } - }), - SUCCEED: () => ({ - type: "succeed", - attributes: [], - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a succeed node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Succeed( - this.attributes, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + case "selector": + return new Selector( + attributes, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) ); - } - }), - FAIL: () => ({ - type: "fail", - attributes: [], - children: [], - validate() { - if (this.children.length !== 1) { - throw new Error("a fail node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Fail( - this.attributes, - this.children[0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) + case "parallel": + return new Parallel( + attributes, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) ); - } - }), - WAIT: () => ({ - type: "wait", - attributes: [], - duration: null, - durationMin: null, - durationMax: null, - validate() { - if (this.duration !== null) { - if (this.duration < 0) { - throw new Error("a wait node must have a positive duration"); - } - } else if (this.durationMin !== null && this.durationMax !== null) { - if (this.durationMin < 0 || this.durationMax < 0) { - throw new Error("a wait node must have a positive minimum and maximum duration"); - } - if (this.durationMin > this.durationMax) { - throw new Error("a wait node must not have a minimum duration that exceeds the maximum duration"); - } - } else { - } - }, - createNodeInstance() { - return new Wait(this.attributes, this.duration, this.durationMin, this.durationMax); - } - }), - ACTION: () => ({ - type: "action", - attributes: [], - actionName: "", - actionArguments: [], - validate() { - }, - createNodeInstance() { - return new Action(this.attributes, this.actionName, this.actionArguments); - } - }), - CONDITION: () => ({ - type: "condition", - attributes: [], - conditionName: "", - conditionArguments: [], - validate() { - }, - createNodeInstance() { - return new Condition(this.attributes, this.conditionName, this.conditionArguments); - } - }) -}; -function buildRootASTNodes(definition) { - const { placeholders, processedDefinition } = substituteStringLiterals(definition); - const tokens = parseTokensFromDefinition(processedDefinition); - if (tokens.length < 3) { - throw new Error("invalid token count"); - } - if (tokens.filter((token) => token === "{").length !== tokens.filter((token) => token === "}").length) { - throw new Error("scope character mismatch"); - } - const stack = [[]]; - const rootScope = stack[0]; - while (tokens.length) { - const token = tokens.shift(); - const currentScope = stack[stack.length - 1]; - switch (token.toUpperCase()) { - case "ROOT": { - const node = ASTNodeFactories.ROOT(); - rootScope.push(node); - if (tokens[0] === "[") { - const rootArguments = getArguments(tokens, placeholders); - if (rootArguments.length === 1 && rootArguments[0].type === "identifier") { - node.name = rootArguments[0].value; - } else { - throw new Error("expected single root name argument"); - } - } - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "BRANCH": { - const node = ASTNodeFactories.BRANCH(); - currentScope.push(node); - if (tokens[0] !== "[") { - throw new Error("expected single branch name argument"); - } - const branchArguments = getArguments(tokens, placeholders); - if (branchArguments.length === 1 && branchArguments[0].type === "identifier") { - node.branchName = branchArguments[0].value; - } else { - throw new Error("expected single branch name argument"); - } - break; - } - case "SELECTOR": { - const node = ASTNodeFactories.SELECTOR(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "SEQUENCE": { - const node = ASTNodeFactories.SEQUENCE(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "PARALLEL": { - const node = ASTNodeFactories.PARALLEL(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "LOTTO": { - const node = ASTNodeFactories.LOTTO(); - currentScope.push(node); - if (tokens[0] === "[") { - node.tickets = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "lotto node ticket counts must be integer values" - ).map((argument) => argument.value); - } - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "CONDITION": { - const node = ASTNodeFactories.CONDITION(); - currentScope.push(node); - if (tokens[0] !== "[") { - throw new Error("expected condition name identifier argument"); - } - const conditionArguments = getArguments(tokens, placeholders); - if (conditionArguments.length && conditionArguments[0].type === "identifier") { - node.conditionName = conditionArguments.shift().value; - } else { - throw new Error("expected condition name identifier argument"); - } - conditionArguments.filter((arg) => arg.type === "identifier").forEach((arg) => { - throw new Error( - "invalid condition node argument value '" + arg.value + "', must be string, number, boolean or null" - ); - }); - node.conditionArguments = conditionArguments; - node.attributes = getAttributes(tokens, placeholders); - break; - } - case "FLIP": { - const node = ASTNodeFactories.FLIP(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "SUCCEED": { - const node = ASTNodeFactories.SUCCEED(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "FAIL": { - const node = ASTNodeFactories.FAIL(); - currentScope.push(node); - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "WAIT": { - const node = ASTNodeFactories.WAIT(); - currentScope.push(node); - if (tokens[0] === "[") { - const nodeArguments = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "wait node durations must be integer values" - ).map((argument) => argument.value); - if (nodeArguments.length === 1) { - node.duration = nodeArguments[0]; - } else if (nodeArguments.length === 2) { - node.durationMin = nodeArguments[0]; - node.durationMax = nodeArguments[1]; - } else if (nodeArguments.length > 2) { - throw new Error("invalid number of wait node duration arguments defined"); - } - } - node.attributes = getAttributes(tokens, placeholders); - break; - } - case "REPEAT": { - const node = ASTNodeFactories.REPEAT(); - currentScope.push(node); - if (tokens[0] === "[") { - const nodeArguments = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "repeat node iteration counts must be integer values" - ).map((argument) => argument.value); - if (nodeArguments.length === 1) { - node.iterations = nodeArguments[0]; - } else if (nodeArguments.length === 2) { - node.iterationsMin = nodeArguments[0]; - node.iterationsMax = nodeArguments[1]; - } else { - throw new Error("invalid number of repeat node iteration count arguments defined"); - } - } - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "RETRY": { - const node = ASTNodeFactories.RETRY(); - currentScope.push(node); - if (tokens[0] === "[") { - const nodeArguments = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "retry node attempt counts must be integer values" - ).map((argument) => argument.value); - if (nodeArguments.length === 1) { - node.attempts = nodeArguments[0]; - } else if (nodeArguments.length === 2) { - node.attemptsMin = nodeArguments[0]; - node.attemptsMax = nodeArguments[1]; - } else { - throw new Error("invalid number of retry node attempt count arguments defined"); - } - } - node.attributes = getAttributes(tokens, placeholders); - popAndCheck(tokens, "{"); - stack.push(node.children); - break; - } - case "ACTION": { - const node = ASTNodeFactories.ACTION(); - currentScope.push(node); - if (tokens[0] !== "[") { - throw new Error("expected action name identifier argument"); - } - const actionArguments = getArguments(tokens, placeholders); - if (actionArguments.length && actionArguments[0].type === "identifier") { - node.actionName = actionArguments.shift().value; - } else { - throw new Error("expected action name identifier argument"); - } - actionArguments.filter((arg) => arg.type === "identifier").forEach((arg) => { - throw new Error( - "invalid action node argument value '" + arg.value + "', must be string, number, boolean or null" - ); - }); - node.actionArguments = actionArguments; - node.attributes = getAttributes(tokens, placeholders); - break; - } - case "}": { - stack.pop(); - break; - } - default: { - throw new Error(`unexpected token '${token}'`); - } - } + case "lotto": + return new Lotto( + attributes, + definition.weights, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) + ); + case "branch": + return nodeFactory(rootNodeDefinitionMap[definition.ref].child, rootNodeDefinitionMap); + case "action": + return new Action(attributes, definition.call, definition.args || []); + case "condition": + return new Condition(attributes, definition.call, definition.args || []); + case "wait": + let duration = null; + let durationMin = null; + let durationMax = null; + if (Array.isArray(definition.duration)) { + durationMin = definition.duration[0]; + durationMax = definition.duration[1]; + } else if (isInteger(definition.duration)) { + duration = definition.duration; + } + return new Wait(attributes, duration, durationMin, durationMax); } - const validateASTNode = (node, depth) => { - node.validate(depth); - (node.children || []).forEach((child) => validateASTNode(child, depth + 1)); - }; - validateASTNode( - { - children: stack[0], - validate() { - if (this.children.length === 0) { - throw new Error("expected root node to have been defined"); - } - for (const definitionLevelNode of this.children) { - if (definitionLevelNode.type !== "root") { - throw new Error("expected root node at base of definition"); - } - } - if (this.children.filter((definitionLevelNode) => definitionLevelNode.name === null).length !== 1) { - throw new Error("expected single unnamed root node at base of definition to act as main root"); - } - const rootNodeNames = []; - for (const definitionLevelNode of this.children) { - if (rootNodeNames.indexOf(definitionLevelNode.name) !== -1) { - throw new Error(`multiple root nodes found with duplicate name '${definitionLevelNode.name}'`); - } else { - rootNodeNames.push(definitionLevelNode.name); - } - } - } - }, - 0 - ); - return stack[0]; } -function popAndCheck(tokens, expected) { - const popped = tokens.shift(); - if (popped === void 0) { - throw new Error("unexpected end of definition"); +function nodeAttributesFactory(definition) { + const attributes = []; + if (definition.while) { + attributes.push(new While(definition.while.call, definition.while.args ?? [])); } - if (expected !== void 0) { - var tokenMatchesExpectation = [].concat(expected).some((item) => popped.toUpperCase() === item.toUpperCase()); - if (!tokenMatchesExpectation) { - const expectationString = [].concat(expected).map((item) => "'" + item + "'").join(" or "); - throw new Error(`unexpected token found. Expected '${expectationString}' but got '${popped}'`); - } + if (definition.until) { + attributes.push(new Until(definition.until.call, definition.until.args ?? [])); } - return popped; -} -function getArguments(tokens, stringArgumentPlaceholders, argumentValidator, validationFailedMessage) { - const closer = popAndCheck(tokens, ["[", "("]) === "[" ? "]" : ")"; - const argumentListTokens = []; - const argumentList = []; - while (tokens.length && tokens[0] !== closer) { - argumentListTokens.push(tokens.shift()); + if (definition.entry) { + attributes.push(new Entry(definition.entry.call, definition.entry.args ?? [])); } - argumentListTokens.forEach((token, index) => { - const shouldBeArgumentToken = !(index & 1); - if (shouldBeArgumentToken) { - const argumentDefinition = getArgumentDefinition(token, stringArgumentPlaceholders); - if (argumentValidator && !argumentValidator(argumentDefinition)) { - throw new Error(validationFailedMessage); - } - argumentList.push(argumentDefinition); - } else { - if (token !== ",") { - throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`); - } - } - }); - popAndCheck(tokens, closer); - return argumentList; -} -function getArgumentDefinition(token, stringArgumentPlaceholders) { - if (token === "null") { - return { - value: null, - type: "null" - }; + if (definition.step) { + attributes.push(new Step(definition.step.call, definition.step.args ?? [])); } - if (token === "true" || token === "false") { - return { - value: token === "true", - type: "boolean" - }; + if (definition.exit) { + attributes.push(new Exit(definition.exit.call, definition.exit.args ?? [])); } - if (!isNaN(token)) { - return { - value: parseFloat(token), - isInteger: parseFloat(token) === parseInt(token, 10), - type: "number" - }; + return attributes; +} +function createRootNodeDefinitionMap(definition) { + const rootNodeMap = {}; + for (const [name, rootNodeDefinition] of Object.entries(Lookup.getSubtrees())) { + rootNodeMap[name] = { ...rootNodeDefinition, id: name }; } - if (token.match(/^@@\d+@@$/g)) { - return { - value: stringArgumentPlaceholders[token].replace('\\"', '"'), - type: "string" - }; + for (const rootNodeDefinition of definition) { + rootNodeMap[rootNodeDefinition.id ?? MAIN_ROOT_NODE_KEY] = rootNodeDefinition; } - return { - value: token, - type: "identifier" - }; + return rootNodeMap; } -function getAttributes(tokens, stringArgumentPlaceholders) { - const attributes = []; - const attributesFound = []; - let attributeFactory = AttributeFactories[(tokens[0] || "").toUpperCase()]; - while (attributeFactory) { - if (attributesFound.indexOf(tokens[0].toUpperCase()) !== -1) { - throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`); - } - attributesFound.push(tokens.shift().toUpperCase()); - const attributeArguments = getArguments(tokens, stringArgumentPlaceholders); - if (attributeArguments.length === 0 || attributeArguments[0].type !== "identifier") { - throw new Error("expected agent function name identifier argument for attribute"); +function applyLeafNodeGuardPaths(root) { + const nodePaths = []; + const findLeafNodes = (path, node) => { + path = path.concat(node); + if (node.isLeafNode()) { + nodePaths.push(path); + } else { + node.getChildren().forEach((child) => findLeafNodes(path, child)); } - const attributeFunctionName = attributeArguments.shift(); - attributeArguments.filter((arg) => arg.type === "identifier").forEach((arg) => { - throw new Error( - "invalid attribute argument value '" + arg.value + "', must be string, number, boolean or null" + }; + findLeafNodes([], root); + nodePaths.forEach((path) => { + for (let depth = 0; depth < path.length; depth++) { + const currentNode = path[depth]; + if (currentNode.hasGuardPath()) { + continue; + } + const guardPath = new GuardPath( + path.slice(0, depth + 1).map((node) => ({ node, guards: node.getGuardAttributes() })).filter((details) => details.guards.length > 0) ); - }); - attributes.push(attributeFactory(attributeFunctionName.value, attributeArguments)); - attributeFactory = AttributeFactories[(tokens[0] || "").toUpperCase()]; - } - return attributes; -} -function substituteStringLiterals(definition) { - const placeholders = {}; - const processedDefinition = definition.replace(/\"(\\.|[^"\\])*\"/g, (match) => { - var strippedMatch = match.substring(1, match.length - 1); - var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch); - if (!placeholder) { - placeholder = `@@${Object.keys(placeholders).length}@@`; - placeholders[placeholder] = strippedMatch; + currentNode.setGuardPath(guardPath); } - return placeholder; }); - return { placeholders, processedDefinition }; -} -function parseTokensFromDefinition(definition) { - definition = definition.replace(/\(/g, " ( "); - definition = definition.replace(/\)/g, " ) "); - definition = definition.replace(/\{/g, " { "); - definition = definition.replace(/\}/g, " } "); - definition = definition.replace(/\]/g, " ] "); - definition = definition.replace(/\[/g, " [ "); - definition = definition.replace(/\,/g, " , "); - return definition.replace(/\s+/g, " ").trim().split(" "); } // src/BehaviourTree.ts @@ -1734,33 +2242,46 @@ var BehaviourTree = class { constructor(definition, agent, options = {}) { this.agent = agent; this.options = options; - if (typeof definition !== "string") { - throw new Error("the tree definition must be a string"); + if (isNullOrUndefined(definition)) { + throw new Error("tree definition not defined"); } if (typeof agent !== "object" || agent === null) { - throw new Error("the agent must be defined and not null"); + throw new Error("the agent must be an object and not null"); + } + const { succeeded, errorMessage, json } = validateDefinition(definition); + if (!succeeded) { + throw new Error(`invalid definition: ${errorMessage}`); + } + if (!json) { + throw new Error( + "expected json definition to be returned as part of successful definition validation response" + ); + } + try { + this._rootNode = buildRootNode(json); + } catch (exception) { + throw new Error(`error building tree: ${exception.message}`); } - this.rootNode = BehaviourTree.createRootNode(definition); } - rootNode; + _rootNode; isRunning() { - return this.rootNode.getState() === "mistreevous.running" /* RUNNING */; + return this._rootNode.getState() === "mistreevous.running" /* RUNNING */; } getState() { - return this.rootNode.getState(); + return this._rootNode.getState(); } step() { - if (this.rootNode.getState() === "mistreevous.succeeded" /* SUCCEEDED */ || this.rootNode.getState() === "mistreevous.failed" /* FAILED */) { - this.rootNode.reset(); + if (this._rootNode.getState() === "mistreevous.succeeded" /* SUCCEEDED */ || this._rootNode.getState() === "mistreevous.failed" /* FAILED */) { + this._rootNode.reset(); } try { - this.rootNode.update(this.agent, this.options); + this._rootNode.update(this.agent, this.options); } catch (exception) { throw new Error(`error stepping tree: ${exception.message}`); } } reset() { - this.rootNode.reset(); + this._rootNode.reset(); } getFlattenedNodeDetails() { const flattenedTreeNodes = []; @@ -1781,25 +2302,45 @@ var BehaviourTree = class { node.getChildren().forEach((child) => processNode(child, node.getUid())); } }; - processNode(this.rootNode, null); + processNode(this._rootNode, null); return flattenedTreeNodes; } static register(name, value) { if (typeof value === "function") { Lookup.setFunc(name, value); - } else if (typeof value === "string") { - let rootASTNodes; + return; + } + if (typeof value === "string") { + let rootNodeDefinitions; try { - rootASTNodes = buildRootASTNodes(value); + rootNodeDefinitions = convertMDSLToJSON(value); } catch (exception) { - throw new Error(`error registering definition: ${exception.message}`); + throw new Error(`error registering definition, invalid MDSL: ${exception.message}`); } - if (rootASTNodes.length != 1 || rootASTNodes[0].name !== null) { + if (rootNodeDefinitions.length != 1 || typeof rootNodeDefinitions[0].id !== "undefined") { throw new Error("error registering definition: expected a single unnamed root node"); } - Lookup.setSubtree(name, rootASTNodes[0]); + try { + const { succeeded, errorMessage } = validateJSONDefinition(rootNodeDefinitions[0]); + if (!succeeded) { + throw new Error(errorMessage); + } + } catch (exception) { + throw new Error(`error registering definition: ${exception.message}`); + } + Lookup.setSubtree(name, rootNodeDefinitions[0]); + } else if (typeof value === "object" && !Array.isArray(value)) { + try { + const { succeeded, errorMessage } = validateJSONDefinition(value); + if (!succeeded) { + throw new Error(errorMessage); + } + } catch (exception) { + throw new Error(`error registering definition: ${exception.message}`); + } + Lookup.setSubtree(name, value); } else { - throw new Error("unexpected value, expected string definition or function"); + throw new Error("unexpected value, expected string mdsl definition, root node json definition or function"); } } static unregister(name) { @@ -1808,52 +2349,12 @@ var BehaviourTree = class { static unregisterAll() { Lookup.empty(); } - static createRootNode(definition) { - try { - const rootASTNodes = buildRootASTNodes(definition); - const mainRootNodeKey = Symbol("__root__"); - const rootNodeMap = {}; - for (const rootASTNode of rootASTNodes) { - rootNodeMap[rootASTNode.name === null ? mainRootNodeKey : rootASTNode.name] = rootASTNode; - } - const rootNode = rootNodeMap[mainRootNodeKey].createNodeInstance( - (name) => rootNodeMap[name] ? rootNodeMap[name] : Lookup.getSubtree(name), - [] - ); - BehaviourTree.applyLeafNodeGuardPaths(rootNode); - return rootNode; - } catch (exception) { - throw new Error(`error parsing tree: ${exception.message}`); - } - } - static applyLeafNodeGuardPaths(rootNode) { - const nodePaths = []; - const findLeafNodes = (path, node) => { - path = path.concat(node); - if (node.isLeafNode()) { - nodePaths.push(path); - } else { - node.getChildren().forEach((child) => findLeafNodes(path, child)); - } - }; - findLeafNodes([], rootNode); - nodePaths.forEach((path) => { - for (let depth = 0; depth < path.length; depth++) { - const currentNode = path[depth]; - if (currentNode.hasGuardPath()) { - continue; - } - const guardPath = new GuardPath( - path.slice(0, depth + 1).map((node) => ({ node, guards: node.getGuardAttributes() })).filter((details) => details.guards.length > 0) - ); - currentNode.setGuardPath(guardPath); - } - }); - } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BehaviourTree, - State + State, + convertMDSLToJSON, + validateDefinition }); //# sourceMappingURL=index.js.map diff --git a/dist/index.js.map b/dist/index.js.map index 32e48d6..ae867f4 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../node_modules/lotto-draw/dist/Participant.js", "../node_modules/lotto-draw/dist/Utilities.js", "../node_modules/lotto-draw/dist/Lotto.js", "../node_modules/lotto-draw/dist/createLotto.js", "../node_modules/lotto-draw/dist/index.js", "../src/index.ts", "../src/attributes/guards/GuardUnsatisifedException.ts", "../src/attributes/guards/GuardPath.ts", "../src/State.ts", "../src/nodes/Node.ts", "../src/nodes/leaf/Leaf.ts", "../src/Lookup.ts", "../src/nodes/leaf/Action.ts", "../src/nodes/leaf/Condition.ts", "../src/nodes/leaf/Wait.ts", "../src/nodes/decorator/Decorator.ts", "../src/nodes/decorator/Root.ts", "../src/nodes/decorator/Repeat.ts", "../src/nodes/decorator/Retry.ts", "../src/nodes/decorator/Flip.ts", "../src/nodes/decorator/Succeed.ts", "../src/nodes/decorator/Fail.ts", "../src/nodes/composite/Lotto.ts", "../src/nodes/composite/Composite.ts", "../src/nodes/composite/Selector.ts", "../src/nodes/composite/Sequence.ts", "../src/nodes/composite/Parallel.ts", "../src/attributes/Attribute.ts", "../src/attributes/guards/Guard.ts", "../src/attributes/guards/While.ts", "../src/attributes/guards/Until.ts", "../src/attributes/callbacks/Callback.ts", "../src/attributes/callbacks/Entry.ts", "../src/attributes/callbacks/Exit.ts", "../src/attributes/callbacks/Step.ts", "../src/RootAstNodesBuilder.ts", "../src/BehaviourTree.ts"], - "sourcesContent": ["\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.Participant = void 0;\r\n/**\r\n * A participant that holds a number of tickets.\r\n */\r\nvar Participant = /** @class */ (function () {\r\n /**\r\n * Creates an instance of the Participant class.\r\n * @param participant The actual participant.\r\n * @param tickets The number of tickets held by the participant.\r\n */\r\n function Participant(participant, tickets) {\r\n if (tickets === void 0) { tickets = 1; }\r\n this._participant = participant;\r\n this._tickets = tickets;\r\n }\r\n Object.defineProperty(Participant.prototype, \"participant\", {\r\n /** Gets the actual participant. */\r\n get: function () {\r\n return this._participant;\r\n },\r\n enumerable: false,\r\n configurable: true\r\n });\r\n Object.defineProperty(Participant.prototype, \"tickets\", {\r\n /** Gets or sets the number of tickets held by the participant. */\r\n get: function () {\r\n return this._tickets;\r\n },\r\n set: function (value) {\r\n this._tickets = value;\r\n },\r\n enumerable: false,\r\n configurable: true\r\n });\r\n return Participant;\r\n}());\r\nexports.Participant = Participant;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.isNaturalNumber = exports.isNullOrUndefined = void 0;\r\n/**\r\n * Gets whether the value provided is null or undefined.\r\n * @param value The value to check.\r\n * @returns Whether the value provided is null or undefined.\r\n */\r\nfunction isNullOrUndefined(value) {\r\n return value === null || value === undefined;\r\n}\r\nexports.isNullOrUndefined = isNullOrUndefined;\r\n/**\r\n * Gets whether the value provided is a natural number.\r\n * @param value The value to check.\r\n * @returns Whether the value provided is a natural number.\r\n */\r\nfunction isNaturalNumber(value) {\r\n return typeof value === \"number\" && value >= 1 && Math.floor(value) === value;\r\n}\r\nexports.isNaturalNumber = isNaturalNumber;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.Lotto = void 0;\r\nvar Participant_1 = require(\"./Participant\");\r\nvar Utilities_1 = require(\"./Utilities\");\r\n/**\r\n * Represents a lotto consisting of a number of pickable ticket-holding participants.\r\n */\r\nvar Lotto = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of Lotto.\r\n * @param customRandom The custom RNG to use in place of Math.random().\r\n */\r\n function Lotto(customRandom) {\r\n /** The array of participants that are holding tickets in the lotto. */\r\n this._participants = [];\r\n this._customRandom = customRandom;\r\n }\r\n /**\r\n * Adds a participant with the specified number of tickets, or adds to the participant ticket count if the participant already holds tickets.\r\n * @param participant The participant to add or to increase the ticket count for if they already hold tickets.\r\n * @param tickets The number of tickets, defaults to 1.\r\n * @returns The Lotto instance.\r\n */\r\n Lotto.prototype.add = function (participant, tickets) {\r\n if (tickets === void 0) { tickets = 1; }\r\n // Check that we have a valid ticket count.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n // Check whether this participant has already been added.\r\n var existingParticipant = this._participants.find(function (part) { return part.participant === participant; });\r\n if (existingParticipant) {\r\n // The participant has already been added to the lotto so just add to their ticket count.\r\n existingParticipant.tickets += tickets;\r\n }\r\n else {\r\n // The participant is not part of the lotto so we should add them.\r\n this._participants.push(new Participant_1.Participant(participant, tickets));\r\n }\r\n return this;\r\n };\r\n /**\r\n * Removes the specified number of tickets for the given participant from the draw, or all tickets if a ticket number is not defined.\r\n * @param participant The participant to remove tickets for.\r\n * @param tickets The number of tickets to remove, or undefined if all tickets are to be removed.\r\n * @returns The Lotto instance.\r\n */\r\n Lotto.prototype.remove = function (participant, tickets) {\r\n // Attempt to get the existing participant.\r\n var existingParticipant = this._participants.find(function (part) { return part.participant === participant; });\r\n // There is nothing to do if the specified participant isn't even part of the lotto.\r\n if (!existingParticipant) {\r\n return this;\r\n }\r\n // Check whether a tickets value was given.\r\n if (tickets !== undefined) {\r\n // Check that we have a valid ticket count.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n existingParticipant.tickets -= tickets;\r\n // If the participant no longer holds any tickets then they should be removed.\r\n if (existingParticipant.tickets < 1) {\r\n this._participants = this._participants.filter(function (part) { return part !== existingParticipant; });\r\n }\r\n }\r\n else {\r\n // We are removing all tickets for the participant so just remove them from the lotto.\r\n this._participants = this._participants.filter(function (part) { return part !== existingParticipant; });\r\n }\r\n return this;\r\n };\r\n /**\r\n * Draw a winning ticket and return the participant that holds the ticket.\r\n * @param options The draw options.\r\n * @returns The participant that holds the winning ticket.\r\n */\r\n Lotto.prototype.draw = function (options) {\r\n if (options === void 0) { options = {}; }\r\n // If we have no participants then just return null.\r\n if (this._participants.length === 0) {\r\n return null;\r\n }\r\n var redrawable = (0, Utilities_1.isNullOrUndefined)(options.redrawable) ? true : options.redrawable;\r\n var pickable = [];\r\n this._participants.forEach(function (_a) {\r\n var participant = _a.participant, tickets = _a.tickets;\r\n for (var ticketCount = 0; ticketCount < tickets; ticketCount++) {\r\n pickable.push(participant);\r\n }\r\n });\r\n var random;\r\n // We need a random floating-point number between 0 (inclusive) and 1 to scale up to pick our winner.\r\n // If a custom random function exists then we should use that or fall back to Math.random().\r\n if (this._customRandom) {\r\n // Call our custom random function to get a random floating-point number.\r\n random = this._customRandom();\r\n // Verify that the result of calling our custom random function is a number between 0 (inclusive) and 1.\r\n if (typeof random !== \"number\" || random < 0 || random >= 1) {\r\n throw new Error(\"the 'random' function provided did not return a number between 0 (inclusive) and 1\");\r\n }\r\n }\r\n else {\r\n // No custom random function was defined so just use good ol' Math.random().\r\n random = Math.random();\r\n }\r\n // Pick a winning participant.\r\n var winner = pickable[Math.floor(random * pickable.length)];\r\n // If the ticket isn't redrawable then we should remove a ticket from the winning participants ticket count.\r\n if (!redrawable) {\r\n this.remove(winner, 1);\r\n }\r\n // Return the winning participant.\r\n return winner;\r\n };\r\n /**\r\n * Draws multiple winning tickets and return an array of the participants that hold the winning tickets.\r\n * @param tickets The number of winning tickets to draw.\r\n * @param options The draw multiple options.\r\n * @returns An array of the participants that hold the winning tickets.\r\n */\r\n Lotto.prototype.drawMultiple = function (tickets, options) {\r\n if (options === void 0) { options = {}; }\r\n var uniqueResults = (0, Utilities_1.isNullOrUndefined)(options.unique) ? false : options.unique;\r\n // Handle cases where the user has asked for zero tickets (no idea why they would do this be we should trust them).\r\n if (tickets === 0) {\r\n return [];\r\n }\r\n // Now that we know out tickets value is not zero we should check that it is a valid natural number.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n var result = [];\r\n // Keep drawing tickets until we either reach the number of required tickets or we simply run out of tickets to draw.\r\n // We can run out of tickets to draw if 'options.redrawable' is explicity 'false' or we just had no participants when 'drawMultiple' was called.\r\n while (result.length < tickets && this._participants.length > 0) {\r\n result.push(this.draw(options));\r\n }\r\n // If the 'unique' draw option is set then we need to remove duplicates from the result list.\r\n if (uniqueResults) {\r\n // Create an array to store our unique results.\r\n var unique = [];\r\n // Iterate over all of our participants (with potential duplicates) and populate our array of unique values.\r\n for (var _i = 0, result_1 = result; _i < result_1.length; _i++) {\r\n var participant = result_1[_i];\r\n if (unique.indexOf(participant) === -1) {\r\n unique.push(participant);\r\n }\r\n }\r\n result = unique;\r\n }\r\n return result;\r\n };\r\n return Lotto;\r\n}());\r\nexports.Lotto = Lotto;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.createLotto = void 0;\r\nvar Lotto_1 = require(\"./Lotto\");\r\n/**\r\n * A function that creates and returns a Lotto instance.\r\n * @param participantsOrOptions An array of initial participants or options relating to the creation of a Lotto instance.\r\n * @returns A new Lotto instance.\r\n */\r\nfunction createLotto(participantsOrOptions) {\r\n // If no initial participants or lotto options were provided as an argument then we can just return a new lotto instance now.\r\n if (!participantsOrOptions) {\r\n return new Lotto_1.Lotto();\r\n }\r\n // Check whether we were provided with an array of initial participants or a lotto options object.\r\n if (Array.isArray(participantsOrOptions)) {\r\n // We are dealing with a pre-defined array of participants.\r\n var participants = participantsOrOptions;\r\n var lotto_1 = new Lotto_1.Lotto();\r\n // If the lotto participants have been defined upfront then we will need to add them all to our lotto instance now.\r\n participants.forEach(function (_a) {\r\n var participant = _a[0], tokens = _a[1];\r\n return lotto_1.add(participant, tokens);\r\n });\r\n // Return the Lotto instance.\r\n return lotto_1;\r\n }\r\n else {\r\n // We are dealing with some lotto options.\r\n var random = participantsOrOptions.random, participants = participantsOrOptions.participants;\r\n // Create a Lotto instance passing the custom RNG function to use in place of Math.random() (which could be undefined).\r\n var lotto_2 = new Lotto_1.Lotto(random);\r\n // If the lotto participants have been defined upfront as part of the options then we will need to add them all to our lotto instance now.\r\n if (participants) {\r\n participants.forEach(function (_a) {\r\n var participant = _a[0], tokens = _a[1];\r\n return lotto_2.add(participant, tokens);\r\n });\r\n }\r\n // Return the Lotto instance.\r\n return lotto_2;\r\n }\r\n}\r\nexports.createLotto = createLotto;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nvar createLotto_1 = require(\"./createLotto\");\r\nexports.default = createLotto_1.createLotto;\r\n", "import { BehaviourTree, FlattenedTreeNode } from \"./BehaviourTree\";\nimport State from \"./State\";\n\nexport { BehaviourTree, State };\nexport type { FlattenedTreeNode };\n", "import Node from \"../../nodes/Node\";\n\n/**\n * An exception thrown when evaluating node guard path conditions and a conditions fails.\n */\nexport default class GuardUnsatisifedException extends Error {\n /**\n * @param source The node at which a guard condition failed.\n */\n constructor(private source: Node) {\n super(\"A guard path condition has failed\");\n }\n\n /**\n * Gets whether the specified node is the node at which a guard condition failed.\n * @param node The node to check against the source node.\n * @returns Whether the specified node is the node at which a guard condition failed.\n */\n isSourceNode = (node: Node) => node === this.source;\n}\n", "import { Agent } from \"../../Agent\";\nimport Guard from \"./Guard\";\nimport Node from \"../../nodes/Node\";\nimport GuardUnsatisifedException from \"./GuardUnsatisifedException\";\n\nexport type GuardPathPart = {\n node: Node;\n guards: Guard[];\n};\n\n/**\n * Represents a path of node guards along a root-to-leaf tree path.\n */\nexport default class GuardPath {\n /**\n * @param nodes An array of objects defining a node instance -> guard link, ordered by node depth.\n */\n constructor(private nodes: GuardPathPart[]) {}\n\n /**\n * Evaluate guard conditions for all guards in the tree path, moving outwards from the root.\n * @param agent The agent, required for guard evaluation.\n * @returns An evaluation results object.\n */\n evaluate = (agent: Agent) => {\n // We need to evaluate guard conditions for nodes up the tree, moving outwards from the root.\n for (const details of this.nodes) {\n // There can be multiple guards per node.\n for (const guard of details.guards) {\n // Check whether the guard condition passes, and throw an exception if not.\n if (!guard.isSatisfied(agent)) {\n throw new GuardUnsatisifedException(details.node);\n }\n }\n }\n };\n}\n", "/**\n * Enumeration of node state types.\n */\nexport enum State {\n READY = \"mistreevous.ready\",\n RUNNING = \"mistreevous.running\",\n SUCCEEDED = \"mistreevous.succeeded\",\n FAILED = \"mistreevous.failed\"\n}\n\nexport { State as default };\n\nexport type CompleteState = State.SUCCEEDED | State.FAILED;\nexport type AnyState = State.READY | State.RUNNING | CompleteState;\n", "import { Agent } from \"../Agent\";\nimport Attribute from \"../attributes/Attribute\";\nimport Entry from \"../attributes/callbacks/Entry\";\nimport Exit from \"../attributes/callbacks/Exit\";\nimport Step from \"../attributes/callbacks/Step\";\nimport Guard from \"../attributes/guards/Guard\";\nimport GuardPath from \"../attributes/guards/GuardPath\";\nimport GuardUnsatisifedException from \"../attributes/guards/GuardUnsatisifedException\";\nimport { BehaviourTreeOptions } from \"../BehaviourTreeOptions\";\nimport { AnyArgument } from \"../RootAstNodesBuilder\";\nimport State, { AnyState } from \"../State\";\nimport Leaf from \"./leaf/Leaf\";\n\n/**\n * A base node.\n */\nexport default abstract class Node {\n /**\n * The node uid.\n */\n private readonly uid: string = createNodeUid();\n /**\n * The node state.\n */\n private state: AnyState = State.READY;\n /**\n * The guard path to evaluate as part of a node update.\n */\n private guardPath: GuardPath | undefined;\n\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param args The node argument definitions.\n */\n constructor(private type: string, private attributes: Attribute[], private args: AnyArgument[]) {}\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected abstract onUpdate(agent: Agent, options: BehaviourTreeOptions): void;\n\n /**\n * Gets the name of the node.\n */\n public abstract getName(): string;\n\n /**\n * Gets whether this node is a leaf node.\n */\n public abstract isLeafNode: () => this is Leaf;\n\n /**\n * Gets/Sets the state of the node.\n */\n getState = (): AnyState => this.state;\n setState = (value: AnyState): void => {\n this.state = value;\n };\n\n /**\n * Gets the unique id of the node.\n */\n getUid = () => this.uid;\n\n /**\n * Gets the type of the node.\n */\n getType = () => this.type;\n\n /**\n * Gets the node attributes.\n */\n getAttributes = () => this.attributes;\n\n /**\n * Gets the node arguments.\n */\n getArguments = () => this.args;\n\n /**\n * Gets the node attribute with the specified type, or null if it does not exist.\n */\n getAttribute(type: \"entry\" | \"ENTRY\"): Entry;\n getAttribute(type: \"exit\" | \"EXIT\"): Exit;\n getAttribute(type: \"step\" | \"STEP\"): Step;\n getAttribute(type: string): Attribute {\n return (\n this.getAttributes().filter((decorator) => decorator.getType().toUpperCase() === type.toUpperCase())[0] ||\n null\n );\n }\n\n /**\n * Gets the node attributes.\n */\n getGuardAttributes = (): Guard[] => this.getAttributes().filter((decorator) => decorator.isGuard()) as Guard[];\n\n /**\n * Sets the guard path to evaluate as part of a node update.\n */\n setGuardPath = (value: GuardPath) => (this.guardPath = value);\n\n /**\n * Gets whether a guard path is assigned to this node.\n */\n hasGuardPath = () => !!this.guardPath;\n\n /**\n * Gets whether this node is in the specified state.\n * @param value The value to compare to the node state.\n */\n public is(value: AnyState): boolean {\n return this.state === value;\n }\n\n /**\n * Reset the state of the node.\n */\n public reset(): void {\n this.setState(State.READY);\n }\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n public abort(agent: Agent): void {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n }\n\n /**\n * Update the node.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n * @returns The result of the update.\n */\n public update(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is already in a 'SUCCEEDED' or 'FAILED' state then there is nothing to do.\n if (this.is(State.SUCCEEDED) || this.is(State.FAILED)) {\n return;\n }\n\n try {\n // Evaluate all of the guard path conditions for the current tree path.\n this.guardPath!.evaluate(agent);\n\n // If this node is in the READY state then call the ENTRY for this node if it exists.\n if (this.is(State.READY)) {\n this.getAttribute(\"entry\")?.callAgentFunction(agent);\n }\n\n this.getAttribute(\"step\")?.callAgentFunction(agent);\n\n // Do the actual update.\n this.onUpdate(agent, options);\n\n // If this node is now in a 'SUCCEEDED' or 'FAILED' state then call the EXIT for this node if it exists.\n if (this.is(State.SUCCEEDED) || this.is(State.FAILED)) {\n this.getAttribute(\"exit\")?.callAgentFunction(agent, this.is(State.SUCCEEDED), false);\n }\n } catch (error) {\n // If the error is a GuardUnsatisfiedException then we need to determine if this node is the source.\n if (error instanceof GuardUnsatisifedException && error.isSourceNode(this)) {\n // Abort the current node.\n this.abort(agent);\n\n // Any node that is the source of an abort will be a failed node.\n this.setState(State.FAILED);\n } else {\n throw error;\n }\n }\n }\n}\n\n/**\n * Create a randomly generated node uid.\n * @returns A randomly generated node uid.\n */\nfunction createNodeUid(): string {\n var S4 = function () {\n return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);\n };\n return S4() + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + S4() + S4();\n}\n", "import Node from \"../Node\";\n\n/**\n * A leaf node.\n */\nexport default abstract class Leaf extends Node {\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => true;\n}\n", "import { ActionResult, Agent, ExitFunctionArg, FunctionArg, GlobalFunction } from \"./Agent\";\nimport { AnyArgument, RootAstNode } from \"./RootAstNodesBuilder\";\n\n// Exit callbacks receive their own special type of argument.\n// There's probably stricter ways to represent this but it feels overly complex right now.\ntype ExitResultArg = { value: ExitFunctionArg };\nexport type AnyExitArgument = AnyArgument | ExitResultArg;\n\nexport type InvokerFunction = (args: AnyExitArgument[]) => ActionResult;\n\n/**\n * A singleton used to store and lookup registered functions and subtrees.\n */\nexport default class Lookup {\n /**\n * The object holding any registered functions keyed on function name.\n */\n private static functionTable: { [key: string]: GlobalFunction } = {};\n /**\n * The object holding any registered sub-trees keyed on tree name.\n */\n private static subtreeTable: { [key: string]: RootAstNode } = {};\n\n /**\n * Gets the function with the specified name.\n * @param name The name of the function.\n * @returns The function with the specified name.\n */\n public static getFunc(name: string): GlobalFunction {\n return this.functionTable[name];\n }\n\n /**\n * Sets the function with the specified name for later lookup.\n * @param name The name of the function.\n * @param func The function.\n */\n public static setFunc(name: string, func: GlobalFunction): void {\n this.functionTable[name] = func;\n }\n\n /**\n * Gets the function invoker for the specified agent and function name.\n * If a function with the specified name exists on the agent object then it will\n * be returned, otherwise we will then check the registered functions for a match.\n * @param agent The agent instance that this behaviour tree is modelling behaviour for.\n * @param name The function name.\n * @returns The function invoker for the specified agent and function name.\n */\n static getFuncInvoker(agent: Agent, name: string): InvokerFunction | null {\n // Check whether the agent contains the specified function.\n const foundOnAgent = agent[name];\n if (foundOnAgent && typeof foundOnAgent === \"function\") {\n return (args: AnyExitArgument[]): boolean | ActionResult =>\n foundOnAgent.apply(\n agent,\n args.map((arg) => arg.value)\n );\n }\n\n // The agent does not contain the specified function but it may have been registered at some point.\n if (this.functionTable[name] && typeof this.functionTable[name] === \"function\") {\n return (args: AnyExitArgument[]) => this.functionTable[name](agent, ...args.map((arg) => arg.value));\n }\n\n // We have no function to invoke.\n return null;\n }\n\n /**\n * Gets the subtree with the specified name.\n * @param name The name of the subtree.\n * @returns The subtree with the specified name.\n */\n static getSubtree(name: string): RootAstNode {\n return this.subtreeTable[name];\n }\n\n /**\n * Sets the subtree with the specified name for later lookup.\n * @param name The name of the subtree.\n * @param subtree The subtree.\n */\n static setSubtree(name: string, subtree: RootAstNode) {\n this.subtreeTable[name] = subtree;\n }\n\n /**\n * Removes the registered function or subtree with the specified name.\n * @param name The name of the registered function or subtree.\n */\n static remove(name: string) {\n delete this.functionTable[name];\n delete this.subtreeTable[name];\n }\n\n /**\n * Remove all registered functions and subtrees.\n */\n static empty() {\n this.functionTable = {};\n this.subtreeTable = {};\n }\n}\n", "import Leaf from \"./Leaf\";\nimport State, { CompleteState } from \"../../State\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * An Action leaf node.\n * This represents an immediate or ongoing state of behaviour.\n */\nexport default class Action extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param actionName The action name.\n * @param actionArguments The array of action argument definitions.\n */\n constructor(attributes: Attribute[], private actionName: string, private actionArguments: AnyArgument[]) {\n super(\"action\", attributes, actionArguments);\n }\n\n /**\n * Whether there is a pending update promise.\n */\n private isUsingUpdatePromise = false;\n\n /**\n * The finished state result of an update promise.\n */\n private updatePromiseStateResult: CompleteState | null = null;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the result of this action depends on an update promise then there is nothing to do until\n // it resolves, unless there has been a value set as a result of the update promise resolving.\n if (this.isUsingUpdatePromise) {\n // Check whether the update promise has resolved with a state value.\n if (this.updatePromiseStateResult) {\n // Set the state of this node to match the state returned by the promise.\n this.setState(this.updatePromiseStateResult);\n }\n\n return;\n }\n\n // Attempt to get the invoker for the action function.\n const actionFuncInvoker = Lookup.getFuncInvoker(agent, this.actionName);\n\n // The action function should be defined.\n if (actionFuncInvoker === null) {\n throw new Error(\n `cannot update action node as the action '${this.actionName}' function is not defined on the agent and has not been registered`\n );\n }\n\n // Call the action function, the result of which may be:\n // - The finished state of this action node.\n // - A promise to return a finished node state.\n // - Undefined if the node should remain in the running state.\n const updateResult = actionFuncInvoker(this.actionArguments) as CompleteState | Promise;\n\n if (updateResult instanceof Promise) {\n updateResult.then(\n (result) => {\n // If 'isUpdatePromisePending' is null then the promise was cleared as it was resolving, probably via an abort of reset.\n if (!this.isUsingUpdatePromise) {\n return;\n }\n\n // Check to make sure the result is a valid finished state.\n if (result !== State.SUCCEEDED && result !== State.FAILED) {\n throw new Error(\n \"action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned\"\n );\n }\n\n // Set pending update promise state result to be processed on next update.\n this.updatePromiseStateResult = result;\n },\n (reason) => {\n // If 'isUpdatePromisePending' is null then the promise was cleared as it was resolving, probably via an abort of reset.\n if (!this.isUsingUpdatePromise) {\n return;\n }\n\n // Just throw whatever was returned as the rejection argument.\n throw new Error(reason);\n }\n );\n\n // This node will be in the 'RUNNING' state until the update promise resolves.\n this.setState(State.RUNNING);\n\n // We are now waiting for the promise returned by the use to resolve before we know what state this node is in.\n this.isUsingUpdatePromise = true;\n } else {\n // Validate the returned value.\n this.validateUpdateResult(updateResult);\n\n // Set the state of this node, this may be undefined, which just means that the node is still in the 'RUNNING' state.\n this.setState(updateResult || State.RUNNING);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => this.actionName;\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // There is no longer an update promise that we care about.\n this.isUsingUpdatePromise = false;\n this.updatePromiseStateResult = null;\n };\n\n /**\n * Validate the result of an update function call.\n * @param result The result of an update function call.\n */\n private validateUpdateResult = (result: CompleteState | boolean) => {\n switch (result) {\n case State.SUCCEEDED:\n case State.FAILED:\n case undefined:\n return;\n default:\n throw new Error(\n `action '${this.actionName}' 'onUpdate' returned an invalid response, expected an optional State.SUCCEEDED or State.FAILED value to be returned`\n );\n }\n };\n}\n", "import Leaf from \"./Leaf\";\nimport State from \"../../State\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Condition leaf node.\n * This will succeed or fail immediately based on an agent predicate, without moving to the 'RUNNING' state.\n */\nexport default class Condition extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param conditionName The name of the condition function.\n * @param conditionArguments The array of condition argument definitions.\n */\n constructor(attributes: Attribute[], private conditionName: string, private conditionArguments: AnyArgument[]) {\n super(\"condition\", attributes, conditionArguments);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.conditionName);\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot update condition node as the condition '${this.conditionName}' function is not defined on the agent and has not been registered`\n );\n }\n\n // Call the condition function to determine the state of this node.\n this.setState(!!conditionFuncInvoker(this.conditionArguments) ? State.SUCCEEDED : State.FAILED);\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => this.conditionName;\n}\n", "import Leaf from \"./Leaf\";\nimport State from \"../../State\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { Agent } from \"../../Agent\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A WAIT node.\n * The state of this node will change to SUCCEEDED after a duration of time\n */\nexport default class Wait extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param duration The duration that this node will wait to succeed in milliseconds.\n * @param durationMin The minimum possible duration in milliseconds that this node will wait to succeed.\n * @param durationMax The maximum possible duration in milliseconds that this node will wait to succeed.\n */\n constructor(\n attributes: Attribute[],\n private duration: number | null,\n private durationMin: number | null,\n private durationMax: number | null\n ) {\n super(\"wait\", attributes, []);\n }\n\n /**\n * The time in milliseconds at which this node was first updated.\n */\n private initialUpdateTime: number = 0;\n\n /**\n * The total duration in milliseconds that this node will be waiting for.\n */\n private totalDuration: number | null = null;\n\n /**\n * The duration in milliseconds that this node has been waiting for.\n */\n private waitedDuration: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to set the initial update time.\n if (this.is(State.READY)) {\n // Set the initial update time.\n this.initialUpdateTime = new Date().getTime();\n\n // Set the initial waited duration.\n this.waitedDuration = 0;\n\n // Are we dealing with an explicit duration or will we be randomly picking a duration between the min and max duration.\n if (this.duration !== null) {\n this.totalDuration = this.duration;\n } else if (this.durationMin !== null && this.durationMax !== null) {\n // We will be picking a random duration between a min and max duration, if the optional 'random' behaviour tree\n // function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random duration between a min and max duration.\n this.totalDuration = Math.floor(\n random() * (this.durationMax - this.durationMin + 1) + this.durationMin\n );\n } else {\n this.totalDuration = null;\n }\n\n // The node is now running until we finish waiting.\n this.setState(State.RUNNING);\n }\n\n // If we have no total duration then this wait node will wait indefinitely until it is aborted.\n if (this.totalDuration === null) {\n return;\n }\n\n // If we have a 'getDeltaTime' function defined as part of our options then we will use it to figure out how long we have waited for.\n if (typeof options.getDeltaTime === \"function\") {\n // Get the delta time.\n const deltaTime = options.getDeltaTime();\n\n // Our delta time must be a valid number and cannot be NaN.\n if (typeof deltaTime !== \"number\" || isNaN(deltaTime)) {\n throw new Error(\"The delta time must be a valid number and not NaN.\");\n }\n\n // Update the amount of time that this node has been waiting for based on the delta time.\n this.waitedDuration += deltaTime * 1000;\n } else {\n // We are not using a delta time, so we will just work out hom much time has passed since the first update.\n this.waitedDuration = new Date().getTime() - this.initialUpdateTime;\n }\n\n // Have we waited long enough?\n if (this.waitedDuration >= this.totalDuration) {\n // We have finished waiting!\n this.setState(State.SUCCEEDED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.duration !== null) {\n return `WAIT ${this.duration}ms`;\n } else if (this.durationMin !== null && this.durationMax !== null) {\n return `WAIT ${this.durationMin}ms-${this.durationMax}ms`;\n } else {\n return \"WAIT\";\n }\n };\n}\n", "import Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A decorator node that wraps a single child node.\n */\nexport default abstract class Decorator extends Node {\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(type: string, attributes: Attribute[], protected child: Node) {\n super(type, attributes, []);\n }\n\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => false;\n\n /**\n * Gets the children of this node.\n */\n getChildren = () => [this.child];\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the state of the child node.\n this.child.reset();\n };\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n abort = (agent: Agent) => {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Abort the child node.\n this.child.abort(agent);\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Root node.\n * The root node will have a single child.\n */\nexport default class Root extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"root\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n // Update the child of this node.\n this.child.update(agent, options);\n }\n\n // The state of the root node is the state of its child.\n this.setState(this.child.getState());\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"ROOT\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A REPEAT node.\n * The node has a single child which can have:\n * -- A number of iterations for which to repeat the child node.\n * -- An infinite repeat loop if neither an iteration count or a condition function is defined.\n * The REPEAT node will stop and have a 'FAILED' state if its child is ever in a 'FAILED' state after an update.\n * The REPEAT node will attempt to move on to the next iteration if its child is ever in a 'SUCCEEDED' state.\n */\nexport default class Repeat extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param iterations The number of iterations to repeat the child node.\n * @param iterationsMin The minimum possible number of iterations to repeat the child node.\n * @param iterationsMax The maximum possible number of iterations to repeat the child node.\n * @param child The child node.\n */\n constructor(\n attributes: Attribute[],\n private iterations: number | null,\n private iterationsMin: number | null,\n private iterationsMax: number | null,\n child: Node\n ) {\n super(\"repeat\", attributes, child);\n }\n\n /**\n * The number of target iterations to make.\n */\n private targetIterationCount: number | null = null;\n\n /**\n * The current iteration count.\n */\n private currentIterationCount: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to reset the child and the target iteration count.\n if (this.is(State.READY)) {\n // Reset the child node.\n this.child.reset();\n\n // Reset the current iteration count.\n this.currentIterationCount = 0;\n\n // Set the target iteration count.\n this.setTargetIterationCount(options);\n }\n\n // Do a check to see if we can iterate. If we can then this node will move into the 'RUNNING' state.\n // If we cannot iterate then we have hit our target iteration count, which means that the node has succeeded.\n if (this.canIterate()) {\n // This node is in the running state and can do its initial iteration.\n this.setState(State.RUNNING);\n\n // We may have already completed an iteration, meaning that the child node will be in the SUCCEEDED state.\n // If this is the case then we will have to reset the child node now.\n if (this.child.getState() === State.SUCCEEDED) {\n this.child.reset();\n }\n\n // Update the child of this node.\n this.child.update(agent, options);\n\n // If the child moved into the FAILED state when we updated it then there is nothing left to do and this node has also failed.\n // If it has moved into the SUCCEEDED state then we have completed the current iteration.\n if (this.child.getState() === State.FAILED) {\n // The child has failed, meaning that this node has failed.\n this.setState(State.FAILED);\n\n return;\n } else if (this.child.getState() === State.SUCCEEDED) {\n // We have completed an iteration.\n this.currentIterationCount += 1;\n }\n } else {\n // This node is in the 'SUCCEEDED' state as we cannot iterate any more.\n this.setState(State.SUCCEEDED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.iterations !== null) {\n return `REPEAT ${this.iterations}x`;\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n return `REPEAT ${this.iterationsMin}x-${this.iterationsMax}x`;\n } else {\n return \"REPEAT\";\n }\n };\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the current iteration count.\n this.currentIterationCount = 0;\n\n // Reset the child node.\n this.child.reset();\n };\n\n /**\n * Gets whether an iteration can be made.\n * @returns Whether an iteration can be made.\n */\n private canIterate = () => {\n if (this.targetIterationCount !== null) {\n // We can iterate as long as we have not reached our target iteration count.\n return this.currentIterationCount < this.targetIterationCount;\n }\n\n // If neither an iteration count or a condition function were defined then we can iterate indefinitely.\n return true;\n };\n\n /**\n * Sets the target iteration count.\n * @param options The behaviour tree options object.\n */\n private setTargetIterationCount = (options: BehaviourTreeOptions) => {\n // Are we dealing with an explicit iteration count or will we be randomly picking a iteration count between the min and max iteration count.\n if (this.iterations !== null) {\n this.targetIterationCount = this.iterations;\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n // We will be picking a random iteration count between a min and max iteration count, if the optional 'random'\n // behaviour tree function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random iteration count between a min and max iteration count.\n this.targetIterationCount = Math.floor(\n random() * (this.iterationsMax - this.iterationsMin + 1) + this.iterationsMin\n );\n } else {\n this.targetIterationCount = null;\n }\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A RETRY node.\n * The node has a single child which can have:\n * -- A number of iterations for which to repeat the child node.\n * -- An infinite repeat loop if neither an iteration count or a condition function is defined.\n * The RETRY node will stop and have a 'SUCCEEDED' state if its child is ever in a 'SUCCEEDED' state after an update.\n * The RETRY node will attempt to move on to the next iteration if its child is ever in a 'FAILED' state.\n */\nexport default class Retry extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param attempts The number of attempts to retry the child node.\n * @param attemptsMin The minimum possible number of attempts to retry the child node.\n * @param attemptsMax The maximum possible number of attempts to retry the child node.\n * @param child The child node.\n */\n constructor(\n attributes: Attribute[],\n private attempts: number | null,\n private attemptsMin: number | null,\n private attemptsMax: number | null,\n child: Node\n ) {\n super(\"retry\", attributes, child);\n }\n\n /**\n * The number of target attempts to make.\n */\n private targetAttemptCount: number | null = null;\n\n /**\n * The current attempt count.\n */\n private currentAttemptCount: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to reset the child and the target attempt count.\n if (this.is(State.READY)) {\n // Reset the child node.\n this.child.reset();\n\n // Reset the current attempt count.\n this.currentAttemptCount = 0;\n\n // Set the target attempt count.\n this.setTargetAttemptCount(options);\n }\n\n // Do a check to see if we can attempt. If we can then this node will move into the 'RUNNING' state.\n // If we cannot attempt then we have hit our target attempt count, which means that the node has succeeded.\n if (this.canAttempt()) {\n // This node is in the running state and can do its initial attempt.\n this.setState(State.RUNNING);\n\n // We may have already completed an attempt, meaning that the child node will be in the FAILED state.\n // If this is the case then we will have to reset the child node now.\n if (this.child.getState() === State.FAILED) {\n this.child.reset();\n }\n\n // Update the child of this node.\n this.child.update(agent, options);\n\n // If the child moved into the SUCCEEDED state when we updated it then there is nothing left to do and this node has also succeeded.\n // If it has moved into the FAILED state then we have completed the current attempt.\n if (this.child.getState() === State.SUCCEEDED) {\n // The child has succeeded, meaning that this node has succeeded.\n this.setState(State.SUCCEEDED);\n\n return;\n } else if (this.child.getState() === State.FAILED) {\n // We have completed an attempt.\n this.currentAttemptCount += 1;\n }\n } else {\n // This node is in the 'FAILED' state as we cannot iterate any more.\n this.setState(State.FAILED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.attempts !== null) {\n return `RETRY ${this.attempts}x`;\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n return `RETRY ${this.attemptsMin}x-${this.attemptsMax}x`;\n } else {\n return \"RETRY\";\n }\n };\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the current attempt count.\n this.currentAttemptCount = 0;\n\n // Reset the child node.\n this.child.reset();\n };\n\n /**\n * Gets whether an attempt can be made.\n * @returns Whether an attempt can be made.\n */\n canAttempt = () => {\n if (this.targetAttemptCount !== null) {\n // We can attempt as long as we have not reached our target attempt count.\n return this.currentAttemptCount < this.targetAttemptCount;\n }\n\n // If neither an attempt count or a condition function were defined then we can attempt indefinitely.\n return true;\n };\n\n /**\n * Sets the target attempt count.\n * @param options The behaviour tree options object.\n */\n setTargetAttemptCount = (options: BehaviourTreeOptions) => {\n // Are we dealing with an explicit attempt count or will we be randomly picking an attempt count between the min and max attempt count.\n if (this.attempts !== null) {\n this.targetAttemptCount = this.attempts;\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n // We will be picking a random attempt count between a min and max attempt count, if the optional 'random'\n // behaviour tree function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random attempt count between a min and max attempt count.\n this.targetAttemptCount = Math.floor(\n random() * (this.attemptsMax - this.attemptsMin + 1) + this.attemptsMin\n );\n } else {\n this.targetAttemptCount = null;\n }\n };\n}\n", "import Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport Node from \"../Node\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Flip node.\n * This node wraps a single child and will flip the state of the child state.\n */\nexport default class Flip extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"flip\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n this.setState(State.FAILED);\n break;\n\n case State.FAILED:\n this.setState(State.SUCCEEDED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"FLIP\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Succeed node.\n * This node wraps a single child and will always move to the 'SUCCEEDED' state when the child moves to a 'SUCCEEDED' or 'FAILED' state.\n */\nexport default class Succeed extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"succeed\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n case State.FAILED:\n this.setState(State.SUCCEEDED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SUCCEED\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Fail node.\n * This node wraps a single child and will always move to the 'FAILED' state when the child moves to a 'SUCCEEDED' or 'FAILED' state.\n */\nexport default class Fail extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"fail\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n case State.FAILED:\n this.setState(State.FAILED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"FAIL\";\n}\n", "import createLotto from \"lotto-draw\";\n\nimport Node from \"../Node\";\nimport Composite from \"./Composite\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A LOTTO node.\n * A winning child is picked on the initial update of this node, based on ticket weighting.\n * The state of this node will match the state of the winning child.\n */\nexport default class Lotto extends Composite {\n /**\n * @param attributes The node attributes.\n * @param tickets The child node tickets.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], private tickets: number[], children: Node[]) {\n super(\"lotto\", attributes, children);\n }\n\n /**\n * The child node selected to be the active one.\n */\n private selectedChild: Node | undefined;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to pick a winning child node.\n if (this.is(State.READY)) {\n // Create a lotto draw with which to randomly pick a child node to become the active one.\n const lottoDraw = createLotto({\n // Hook up the optional 'random' behaviour tree function option to the one used by 'lotto-draw'.\n random: options.random,\n // Pass in each child node as a participant in the lotto draw with their respective ticket count.\n participants: this.children.map((child, index) => [child, this.tickets[index] || 1])\n });\n\n // Randomly pick a child based on ticket weighting, this will become the active child for this composite node.\n this.selectedChild = lottoDraw.draw() || undefined;\n }\n\n // If something went wrong and we don't have an active child then we should throw an error.\n if (!this.selectedChild) {\n throw new Error(\"failed to update lotto node as it has no active child\");\n }\n\n // If the selected child has never been updated or is running then we will need to update it now.\n if (this.selectedChild.getState() === State.READY || this.selectedChild.getState() === State.RUNNING) {\n this.selectedChild.update(agent, options);\n }\n\n // The state of the lotto node is the state of its selected child.\n this.setState(this.selectedChild.getState());\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => (this.tickets.length ? `LOTTO [${this.tickets.join(\",\")}]` : \"LOTTO\");\n}\n", "import Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A composite node that wraps child nodes.\n */\nexport default abstract class Composite extends Node {\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(type: string, attributes: Attribute[], protected children: Node[]) {\n super(type, attributes, []);\n }\n\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => false;\n\n /**\n * Gets the children of this node.\n */\n getChildren = () => this.children;\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the state of any child nodes.\n this.getChildren().forEach((child) => child.reset());\n };\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n abort = (agent: Agent) => {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Abort any child nodes.\n this.getChildren().forEach((child) => child.abort(agent));\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n };\n}\n", "import Composite from \"./Composite\";\nimport Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A SELECTOR node.\n * The child nodes are executed in sequence until one succeeds or all fail.\n */\nexport default class Selector extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], protected children: Node[]) {\n super(\"selector\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then this node is also a 'SUCCEEDED' node.\n if (child.getState() === State.SUCCEEDED) {\n // This node is a 'SUCCEEDED' node.\n this.setState(State.SUCCEEDED);\n\n // There is no need to check the rest of the selector nodes.\n return;\n }\n\n // If the current child has a state of 'FAILED' then we should move on to the next child.\n if (child.getState() === State.FAILED) {\n // Find out if the current child is the last one in the selector.\n // If it is then this sequence node has also failed.\n if (this.children.indexOf(child) === this.children.length - 1) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // There is no need to check the rest of the selector as we have completed it.\n return;\n } else {\n // The child node failed, try the next one.\n continue;\n }\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() === State.RUNNING) {\n // This node is a 'RUNNING' node.\n this.setState(State.RUNNING);\n\n // There is no need to check the rest of the selector as the current child is still running.\n return;\n }\n\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SELECTOR\";\n}\n", "import Composite from \"./Composite\";\nimport Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A SEQUENCE node.\n * The child nodes are executed in sequence until one fails or all succeed.\n */\nexport default class Sequence extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], protected children: Node[]) {\n super(\"sequence\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then we should move on to the next child.\n if (child.getState() === State.SUCCEEDED) {\n // Find out if the current child is the last one in the sequence.\n // If it is then this sequence node has also succeeded.\n if (this.children.indexOf(child) === this.children.length - 1) {\n // This node is a 'SUCCEEDED' node.\n this.setState(State.SUCCEEDED);\n\n // There is no need to check the rest of the sequence as we have completed it.\n return;\n } else {\n // The child node succeeded, but we have not finished the sequence yet.\n continue;\n }\n }\n\n // If the current child has a state of 'FAILED' then this node is also a 'FAILED' node.\n if (child.getState() === State.FAILED) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // There is no need to check the rest of the sequence.\n return;\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() === State.RUNNING) {\n // This node is a 'RUNNING' node.\n this.setState(State.RUNNING);\n\n // There is no need to check the rest of the sequence as the current child is still running.\n return;\n }\n\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SEQUENCE\";\n}\n", "import Composite from \"./Composite\";\nimport State from \"../../State\";\nimport Node from \"../Node\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A PARALLEL node.\n * The child nodes are executed concurrently until one fails or all succeed.\n */\nexport default class Parallel extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], children: Node[]) {\n super(\"parallel\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Keep a count of the number of succeeded child nodes.\n let succeededCount = 0;\n\n let hasChildFailed = false;\n\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then we should move on to the next child.\n if (child.getState() === State.SUCCEEDED) {\n // The child node has succeeded, keep track of this to determine if all children have.\n succeededCount++;\n\n // The child node succeeded, but we have not finished checking every child node yet.\n continue;\n }\n\n // If the current child has a state of 'FAILED' then this node is also a 'FAILED' node.\n if (child.getState() === State.FAILED) {\n hasChildFailed = true;\n\n // There is no need to check the rest of the children.\n break;\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() !== State.RUNNING) {\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n if (hasChildFailed) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // Abort every running child.\n for (const child of this.children) {\n if (child.getState() === State.RUNNING) {\n child.abort(agent);\n }\n }\n } else {\n // If all children have succeeded then this node has also succeeded, otherwise it is still running.\n this.setState(succeededCount === this.children.length ? State.SUCCEEDED : State.RUNNING);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"PARALLEL\";\n}\n", "import { AnyArgument } from \"../RootAstNodesBuilder\";\nimport Guard from \"./guards/Guard\";\n\nexport type AttributeDetails = {\n /** The attribute type. */\n type: string;\n\n /** The attribute arguments. */\n args: AnyArgument[];\n};\n\n/**\n * A base node attribute.\n */\nexport default abstract class Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of attribute argument definitions.\n */\n constructor(protected type: string, protected args: AnyArgument[]) {}\n\n /**\n * Gets the type of the attribute.\n */\n getType = () => this.type;\n\n /**\n * Gets the array of attribute argument definitions.\n */\n getArguments = () => this.args;\n\n /**\n * Gets the attribute details.\n */\n abstract getDetails(): TAttributeDetails;\n\n /**\n * Gets whether this attribute is a guard.\n */\n abstract isGuard: () => this is Guard;\n}\n", "import { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\nimport Attribute, { AttributeDetails } from \"../Attribute\";\n\nexport type GuardAttributeDetails = {\n /** The name of the condition function that determines whether the guard is satisfied. */\n condition: string;\n} & AttributeDetails;\n\n/**\n * A base node guard attribute.\n */\nexport default abstract class Guard extends Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of decorator argument definitions.\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n */\n constructor(type: string, args: AnyArgument[], private condition: string) {\n super(type, args);\n }\n\n /**\n * Gets the name of the condition function that determines whether the guard is satisfied.\n */\n getCondition = () => this.condition;\n\n /**\n * Gets whether this attribute is a guard.\n */\n isGuard = () => true;\n\n /**\n * Gets the attribute details.\n */\n getDetails(): GuardAttributeDetails {\n return {\n type: this.getType(),\n args: this.getArguments(),\n condition: this.getCondition()\n };\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n abstract isSatisfied(agent: Agent): boolean;\n}\n", "import Guard from \"./Guard\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * A WHILE guard which is satisfied as long as the given condition remains true.\n */\nexport default class While extends Guard {\n /**\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n * @param args The array of decorator argument definitions.\n */\n constructor(condition: string, args: AnyArgument[]) {\n super(\"while\", args, condition);\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n isSatisfied = (agent: Agent) => {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.getCondition());\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered`\n );\n }\n\n // Call the condition function to determine whether this guard is satisfied.\n return !!conditionFuncInvoker(this.args);\n };\n}\n", "import Guard from \"./Guard\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * An UNTIL guard which is satisfied as long as the given condition remains false.\n */\nexport default class Until extends Guard {\n /**\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n * @param args The array of decorator argument definitions.\n */\n constructor(condition: string, args: AnyArgument[]) {\n super(\"until\", args, condition);\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n isSatisfied = (agent: Agent) => {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.getCondition());\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered`\n );\n }\n\n // Call the condition function to determine whether this guard is satisfied.\n return !!!conditionFuncInvoker(this.args);\n };\n}\n", "import { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\nimport Attribute, { AttributeDetails } from \"../Attribute\";\n\nexport type CallbackAttributeDetails = {\n /** The name of the agent function that is called. */\n functionName: string;\n} & AttributeDetails;\n\n/**\n * A base node callback attribute.\n */\nexport default abstract class Callback extends Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of decorator argument definitions.\n * @param functionName The name of the agent function to call.\n */\n constructor(type: string, args: AnyArgument[], private functionName: string) {\n super(type, args);\n }\n\n /**\n * Gets the name of the agent function to call.\n */\n getFunctionName = () => this.functionName;\n\n /**\n * Gets whether this attribute is a guard.\n */\n isGuard = () => false;\n\n /**\n * Gets the attribute details.\n */\n getDetails(): CallbackAttributeDetails {\n return {\n type: this.getType(),\n args: this.getArguments(),\n functionName: this.getFunctionName()\n };\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n abstract callAgentFunction: (agent: Agent, isSuccess: boolean, isAborted: boolean) => void;\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * An ENTRY callback which defines an agent function to call when the associated node is updated and moves out of running state.\n */\nexport default class Entry extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: AnyArgument[]) {\n super(\"entry\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n callAgentFunction = (agent: Agent) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call entry function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function.\n callbackFuncInvoker(this.args);\n };\n}\n", "import Callback from \"./Callback\";\nimport Lookup, { AnyExitArgument } from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * An EXIT callback which defines an agent function to call when the associated node is updated and moves to a finished state or is aborted.\n */\nexport default class Exit extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: AnyArgument[]) {\n super(\"exit\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n * @param isSuccess Whether the decorated node was left with a success state.\n * @param isAborted Whether the decorated node was aborted.\n */\n callAgentFunction = (agent: Agent, isSuccess: boolean, isAborted: boolean) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call exit function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function\n callbackFuncInvoker([{ value: { succeeded: isSuccess, aborted: isAborted } }, ...this.args]);\n };\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\nimport { AnyArgument } from \"../../RootAstNodesBuilder\";\n\n/**\n * A STEP callback which defines an agent function to call when the associated node is updated.\n */\nexport default class Step extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: AnyArgument[]) {\n super(\"step\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n callAgentFunction = (agent: Agent) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call step function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function.\n callbackFuncInvoker(this.args);\n };\n}\n", "import Action from \"./nodes/leaf/Action\";\nimport Condition from \"./nodes/leaf/Condition\";\nimport Wait from \"./nodes/leaf/Wait\";\nimport Root from \"./nodes/decorator/Root\";\nimport Repeat from \"./nodes/decorator/Repeat\";\nimport Retry from \"./nodes/decorator/Retry\";\nimport Flip from \"./nodes/decorator/Flip\";\nimport Succeed from \"./nodes/decorator/Succeed\";\nimport Fail from \"./nodes/decorator/Fail\";\nimport Lotto from \"./nodes/composite/Lotto\";\nimport Selector from \"./nodes/composite/Selector\";\nimport Sequence from \"./nodes/composite/Sequence\";\nimport Parallel from \"./nodes/composite/Parallel\";\nimport Node from \"./nodes/Node\";\nimport While from \"./attributes/guards/While\";\nimport Until from \"./attributes/guards/Until\";\nimport Entry from \"./attributes/callbacks/Entry\";\nimport Exit from \"./attributes/callbacks/Exit\";\nimport Step from \"./attributes/callbacks/Step\";\nimport Callback from \"./attributes/callbacks/Callback\";\nimport Guard from \"./attributes/guards/Guard\";\nimport Attribute from \"./attributes/Attribute\";\nimport Composite from \"./nodes/composite/Composite\";\nimport Decorator from \"./nodes/decorator/Decorator\";\nimport Leaf from \"./nodes/leaf/Leaf\";\n\nexport type Argument = {\n value: T;\n type: string; // Used for validation.\n};\ntype NullArgument = Argument & {\n type: \"null\";\n};\ntype BooleanArgument = Argument & {\n type: \"boolean\";\n};\ntype NumberArgument = Argument & {\n type: \"number\";\n isInteger: boolean; // Used for validation.\n};\ntype StringPlaceholderArgument = Argument & {\n type: \"string\";\n};\ntype IdentifierArgument = Argument & {\n type: \"identifier\";\n};\nexport type AnyArgument =\n | NullArgument\n | BooleanArgument\n | NumberArgument\n | StringPlaceholderArgument\n | IdentifierArgument;\n\n/**\n * The node attribute factories.\n */\nconst AttributeFactories: {\n [key: string]: (functionName: string, attributeArguments: AnyArgument[]) => Callback | Guard;\n} = {\n WHILE: (condition: string, attributeArguments: AnyArgument[]) => new While(condition, attributeArguments),\n UNTIL: (condition: string, attributeArguments: AnyArgument[]) => new Until(condition, attributeArguments),\n ENTRY: (functionName: string, attributeArguments: AnyArgument[]) => new Entry(functionName, attributeArguments),\n EXIT: (functionName: string, attributeArguments: AnyArgument[]) => new Exit(functionName, attributeArguments),\n STEP: (functionName: string, attributeArguments: AnyArgument[]) => new Step(functionName, attributeArguments)\n};\n\ntype Validatable = {\n children?: AstNode[];\n validate: (depth: number) => void;\n};\n\ntype NodeInstanceCreator = (\n namedRootNodeProvider: (name: string) => RootAstNode,\n visitedBranches: string[]\n) => T;\n\nexport type AstNode = Validatable & {\n type: string;\n createNodeInstance: NodeInstanceCreator;\n};\n\nexport type LeafAstNode = AstNode & {\n type: \"action\" | \"condition\" | \"wait\";\n attributes: Attribute[];\n};\n\nexport type CompositeAstNode = AstNode & {\n type: \"lotto\" | \"parallel\" | \"selector\" | \"sequence\";\n attributes: Attribute[];\n children: AstNode[];\n};\n\nexport type DecoratorAstNode = AstNode & {\n type: \"fail\" | \"flip\" | \"repeat\" | \"retry\" | \"root\" | \"succeed\";\n attributes: Attribute[];\n children: AstNode[];\n};\n\nexport type BranchAstNode = AstNode & {\n type: \"branch\";\n branchName: \"\" | string;\n};\n\nexport type LottoAstNode = CompositeAstNode & {\n type: \"lotto\";\n tickets: number[];\n};\n\nexport type RootAstNode = DecoratorAstNode & {\n type: \"root\";\n name: null | string;\n};\n\nexport type RepeatAstNode = DecoratorAstNode & {\n type: \"repeat\";\n iterations: number | null;\n iterationsMin: number | null;\n iterationsMax: number | null;\n};\n\nexport type RetryAstNode = DecoratorAstNode & {\n type: \"retry\";\n attempts: number | null;\n attemptsMin: number | null;\n attemptsMax: number | null;\n};\n\nexport type ActionAstNode = LeafAstNode & {\n type: \"action\";\n actionName: string;\n actionArguments: AnyArgument[];\n};\n\nexport type ConditionAstNode = LeafAstNode & {\n type: \"condition\";\n conditionName: string;\n conditionArguments: AnyArgument[];\n};\n\nexport type WaitAstNode = LeafAstNode & {\n type: \"wait\";\n duration: number | null;\n durationMin: number | null;\n durationMax: number | null;\n};\n\nexport type AnyAstNode =\n | BranchAstNode\n | CompositeAstNode\n | LottoAstNode\n | DecoratorAstNode\n | RootAstNode\n | RepeatAstNode\n | RetryAstNode\n | LeafAstNode\n | ActionAstNode\n | ConditionAstNode\n | WaitAstNode;\n\n/**\n * The AST node factories.\n */\nconst ASTNodeFactories = {\n ROOT: (): RootAstNode => ({\n type: \"root\",\n attributes: [],\n name: null,\n children: [],\n validate(depth: number) {\n // A root node cannot be the child of another node.\n if (depth > 1) {\n throw new Error(\"a root node cannot be the child of another node\");\n }\n\n // A root node must have a single child node.\n if (this.children.length !== 1) {\n throw new Error(\"a root node must have a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Root(\n this.attributes,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n BRANCH: (): BranchAstNode => ({\n type: \"branch\",\n branchName: \"\",\n validate() {},\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n // Try to find the root node with a matching branch name.\n const targetRootNode = namedRootNodeProvider(this.branchName);\n\n // If we have already visited this branch then we have a circular dependency.\n if (visitedBranches.indexOf(this.branchName) !== -1) {\n throw new Error(`circular dependency found in branch node references for branch '${this.branchName}'`);\n }\n\n // If we have a target root node, then the node instance we want will be the first and only child of the referenced root node.\n if (targetRootNode) {\n return targetRootNode\n .createNodeInstance(namedRootNodeProvider, visitedBranches.concat(this.branchName))\n .getChildren()[0];\n } else {\n throw new Error(`branch references root node '${this.branchName}' which has not been defined`);\n }\n }\n }),\n SELECTOR: (): CompositeAstNode => ({\n type: \"selector\",\n attributes: [],\n children: [],\n validate() {\n // A selector node must have at least a single node.\n if (this.children.length < 1) {\n throw new Error(\"a selector node must have at least a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Selector(\n this.attributes,\n this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice()))\n );\n }\n }),\n SEQUENCE: (): CompositeAstNode => ({\n type: \"sequence\",\n attributes: [],\n children: [],\n validate() {\n // A sequence node must have at least a single node.\n if (this.children.length < 1) {\n throw new Error(\"a sequence node must have at least a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Sequence(\n this.attributes,\n this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice()))\n );\n }\n }),\n PARALLEL: (): CompositeAstNode => ({\n type: \"parallel\",\n attributes: [],\n children: [],\n validate() {\n // A parallel node must have at least a single node.\n if (this.children.length < 1) {\n throw new Error(\"a parallel node must have at least a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Parallel(\n this.attributes,\n this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice()))\n );\n }\n }),\n LOTTO: (): LottoAstNode => ({\n type: \"lotto\",\n attributes: [],\n children: [],\n tickets: [],\n validate() {\n // A lotto node must have at least a single node.\n if (this.children!.length < 1) {\n throw new Error(\"a lotto node must have at least a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Lotto(\n this.attributes,\n this.tickets!,\n this.children!.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice()))\n );\n }\n }),\n REPEAT: (): RepeatAstNode => ({\n type: \"repeat\",\n attributes: [],\n iterations: null,\n iterationsMin: null,\n iterationsMax: null,\n children: [],\n validate() {\n // A repeat node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a repeat node must have a single child\");\n }\n\n if (this.iterations !== null) {\n // A repeat node must have a positive number of iterations if defined.\n if (this.iterations < 0) {\n throw new Error(\"a repeat node must have a positive number of iterations if defined\");\n }\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n // A repeat node must have a positive min and max iteration count if they are defined.\n if (this.iterationsMin < 0 || this.iterationsMax < 0) {\n throw new Error(\n \"a repeat node must have a positive minimum and maximum iteration count if defined\"\n );\n }\n\n // A repeat node must not have an minimum iteration count that exceeds the maximum iteration count.\n if (this.iterationsMin > this.iterationsMax) {\n throw new Error(\n \"a repeat node must not have a minimum iteration count that exceeds the maximum iteration count\"\n );\n }\n } else {\n // If we have no explicit iteration count or a minimum and maximum iteration count set then we are dealing with a repeat node that iterates indefinitely.\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Repeat(\n this.attributes,\n this.iterations,\n this.iterationsMin,\n this.iterationsMax,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n RETRY: (): RetryAstNode => ({\n type: \"retry\",\n attributes: [],\n attempts: null,\n attemptsMin: null,\n attemptsMax: null,\n children: [],\n validate() {\n // A retry node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a retry node must have a single child\");\n }\n\n if (this.attempts !== null) {\n // A retry node must have a positive number of attempts if defined.\n if (this.attempts < 0) {\n throw new Error(\"a retry node must have a positive number of attempts if defined\");\n }\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n // A retry node must have a positive min and max attempts count if they are defined.\n if (this.attemptsMin < 0 || this.attemptsMax < 0) {\n throw new Error(\"a retry node must have a positive minimum and maximum attempt count if defined\");\n }\n\n // A retry node must not have a minimum attempt count that exceeds the maximum attempt count.\n if (this.attemptsMin > this.attemptsMax) {\n throw new Error(\n \"a retry node must not have a minimum attempt count that exceeds the maximum attempt count\"\n );\n }\n } else {\n // If we have no explicit attempt count or a minimum and maximum attempt count set then we are dealing with a retry node that attempts indefinitely.\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Retry(\n this.attributes,\n this.attempts,\n this.attemptsMin,\n this.attemptsMax,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n FLIP: (): DecoratorAstNode => ({\n type: \"flip\",\n attributes: [],\n children: [],\n validate() {\n // A flip node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a flip node must have a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Flip(\n this.attributes,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n SUCCEED: (): DecoratorAstNode => ({\n type: \"succeed\",\n attributes: [],\n children: [],\n validate() {\n // A succeed node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a succeed node must have a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Succeed(\n this.attributes,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n FAIL: (): DecoratorAstNode => ({\n type: \"fail\",\n attributes: [],\n children: [],\n validate() {\n // A fail node must have a single node.\n if (this.children!.length !== 1) {\n throw new Error(\"a fail node must have a single child\");\n }\n },\n createNodeInstance(namedRootNodeProvider, visitedBranches) {\n return new Fail(\n this.attributes,\n this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice())\n );\n }\n }),\n WAIT: (): WaitAstNode => ({\n type: \"wait\",\n attributes: [],\n duration: null,\n durationMin: null,\n durationMax: null,\n validate() {\n if (this.duration !== null) {\n // If an explict duration was defined then it must be a positive number.\n if (this.duration < 0) {\n throw new Error(\"a wait node must have a positive duration\");\n }\n } else if (this.durationMin !== null && this.durationMax !== null) {\n // A wait node must have a positive min and max duration.\n if (this.durationMin < 0 || this.durationMax < 0) {\n throw new Error(\"a wait node must have a positive minimum and maximum duration\");\n }\n\n // A wait node must not have a minimum duration that exceeds the maximum duration.\n if (this.durationMin > this.durationMax) {\n throw new Error(\"a wait node must not have a minimum duration that exceeds the maximum duration\");\n }\n } else {\n // If we have no explicit duration or duration bounds set then we are dealing with a wait node that waits indefinitely.\n }\n },\n createNodeInstance() {\n return new Wait(this.attributes, this.duration, this.durationMin, this.durationMax);\n }\n }),\n ACTION: (): ActionAstNode => ({\n type: \"action\",\n attributes: [],\n actionName: \"\",\n actionArguments: [],\n validate() {},\n createNodeInstance() {\n return new Action(this.attributes, this.actionName!, this.actionArguments!);\n }\n }),\n CONDITION: (): ConditionAstNode => ({\n type: \"condition\",\n attributes: [],\n conditionName: \"\",\n conditionArguments: [],\n validate() {},\n createNodeInstance() {\n return new Condition(this.attributes, this.conditionName!, this.conditionArguments!);\n }\n })\n};\n\ntype OtherAstNodes = AstNode[];\n\n/**\n * Create an array of root AST nodes based on the given definition.\n * @param definition The definition to parse the AST nodes from.\n * @returns The base definition AST nodes.\n */\nexport default function buildRootASTNodes(definition: string): RootAstNode[] {\n // Swap out any node/attribute argument string literals with a placeholder and get a mapping of placeholders to original values as well as the processed definition.\n const { placeholders, processedDefinition } = substituteStringLiterals(definition);\n\n // Convert the processed definition (with substituted string literals) into an array of raw tokens.\n const tokens = parseTokensFromDefinition(processedDefinition);\n\n // There must be at least 3 tokens for the tree definition to be valid. 'ROOT', '{' and '}'.\n if (tokens.length < 3) {\n throw new Error(\"invalid token count\");\n }\n\n // We should have a matching number of '{' and '}' tokens. If not, then there are scopes that have not been properly closed.\n if (tokens.filter((token) => token === \"{\").length !== tokens.filter((token) => token === \"}\").length) {\n throw new Error(\"scope character mismatch\");\n }\n\n // Create a stack of node children arrays, starting with a definition scope.\n const stack: [RootAstNode[], ...OtherAstNodes[]] = [[]];\n const rootScope = stack[0];\n\n // We should keep processing the raw tokens until we run out of them.\n while (tokens.length) {\n // Grab the next token.\n const token = tokens.shift();\n\n const currentScope = stack[stack.length - 1] as OtherAstNodes;\n\n // How we create the next AST token depends on the current raw token value.\n switch (token!.toUpperCase()) {\n case \"ROOT\": {\n // Create a ROOT AST node.\n const node = ASTNodeFactories.ROOT();\n\n // Push the ROOT node into the current scope.\n rootScope.push(node);\n\n // We may have a root node name defined as an argument.\n if (tokens[0] === \"[\") {\n const rootArguments = getArguments(tokens, placeholders);\n\n // We should have only a single argument that is not an empty string for a root node, which is the root name identifier.\n if (rootArguments.length === 1 && rootArguments[0].type === \"identifier\") {\n // The root name will be the first and only node argument.\n node.name = rootArguments[0].value as string;\n } else {\n throw new Error(\"expected single root name argument\");\n }\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new ROOT nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"BRANCH\": {\n // Create a BRANCH AST node.\n const node = ASTNodeFactories.BRANCH();\n\n // Push the BRANCH node into the current scope.\n currentScope.push(node);\n\n // We must have arguments defined, as we require a branch name argument.\n if (tokens[0] !== \"[\") {\n throw new Error(\"expected single branch name argument\");\n }\n\n // The branch name will be defined as a node argument.\n const branchArguments = getArguments(tokens, placeholders);\n\n // We should have only a single identifer argument for a branch node, which is the branch name.\n if (branchArguments.length === 1 && branchArguments[0].type === \"identifier\") {\n // The branch name will be the first and only node argument.\n node.branchName = branchArguments[0].value as string;\n } else {\n throw new Error(\"expected single branch name argument\");\n }\n break;\n }\n\n case \"SELECTOR\": {\n // Create a SELECTOR AST node.\n const node = ASTNodeFactories.SELECTOR();\n\n // Push the SELECTOR node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new SELECTOR nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"SEQUENCE\": {\n // Create a SEQUENCE AST node.\n const node = ASTNodeFactories.SEQUENCE();\n\n // Push the SEQUENCE node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new SEQUENCE nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"PARALLEL\": {\n // Create a PARALLEL AST node.\n const node = ASTNodeFactories.PARALLEL();\n\n // Push the PARALLEL node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new PARALLEL nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"LOTTO\": {\n // Create a LOTTO AST node.\n const node = ASTNodeFactories.LOTTO();\n\n // Push the LOTTO node into the current scope.\n currentScope.push(node);\n\n // If the next token is a '[' character then some ticket counts have been defined as arguments.\n if (tokens[0] === \"[\") {\n // Get the ticket count arguments, each argument must be a number.\n node.tickets = getArguments(\n tokens,\n placeholders,\n (arg) => arg.type === \"number\" && !!arg.isInteger,\n \"lotto node ticket counts must be integer values\"\n ).map((argument) => argument.value as number);\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new LOTTO nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"CONDITION\": {\n // Create a CONDITION AST node.\n const node = ASTNodeFactories.CONDITION();\n\n // Push the CONDITION node into the current scope.\n currentScope.push(node);\n\n // We must have arguments defined, as we require a condition function name argument.\n if (tokens[0] !== \"[\") {\n throw new Error(\"expected condition name identifier argument\");\n }\n\n // Grab the condition node arguments.\n const conditionArguments = getArguments(tokens, placeholders);\n\n // We should have at least a single identifier argument for a condition node, which is the condition function name.\n if (conditionArguments.length && conditionArguments[0].type === \"identifier\") {\n // The condition function name will be the first node argument.\n node.conditionName = conditionArguments.shift()!.value as string;\n } else {\n throw new Error(\"expected condition name identifier argument\");\n }\n\n // Only the first argument should have been an identifier, all following arguments must be string, number, boolean or null.\n conditionArguments\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n \"invalid condition node argument value '\" +\n arg.value +\n \"', must be string, number, boolean or null\"\n );\n });\n\n // Any node arguments that follow the condition name identifier will be treated as condition function arguments.\n node.conditionArguments = conditionArguments;\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n break;\n }\n\n case \"FLIP\": {\n // Create a FLIP AST node.\n const node = ASTNodeFactories.FLIP();\n\n // Push the Flip node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new FLIP nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"SUCCEED\": {\n // Create a SUCCEED AST node.\n const node = ASTNodeFactories.SUCCEED();\n\n // Push the Succeed node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new Succeed nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"FAIL\": {\n // Create a FAIL AST node.\n const node = ASTNodeFactories.FAIL();\n\n // Push the Fail node into the current scope.\n currentScope.push(node);\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new Fail nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"WAIT\": {\n // Create a WAIT AST node.\n const node = ASTNodeFactories.WAIT();\n\n // Push the WAIT node into the current scope.\n currentScope.push(node);\n\n // The arguments of a wait node are optional. We may have:\n // - No node arguments, in which case the wait will be indefinite until it is aborted.\n // - One node argument which will be the explicit duration of the wait.\n // - Two node arguments which define the min and max duration bounds from which a random duration will be picked.\n if (tokens[0] === \"[\") {\n // Get the optional duration and longest duration of the wait.\n const nodeArguments = getArguments(\n tokens,\n placeholders,\n (arg) => arg.type === \"number\" && !!arg.isInteger,\n \"wait node durations must be integer values\"\n ).map((argument) => argument.value);\n\n // We may have:\n // - One node argument which will be the explicit duration of the wait.\n // - Two node arguments which define the min and max duration bounds from which a random duration will be picked.\n // - Too many arguments, which is not valid.\n if (nodeArguments.length === 1) {\n // An explicit duration was defined.\n node.duration = nodeArguments[0] as number;\n } else if (nodeArguments.length === 2) {\n // Min and max duration bounds were defined from which a random duration will be picked.\n node.durationMin = nodeArguments[0] as number;\n node.durationMax = nodeArguments[1] as number;\n } else if (nodeArguments.length > 2) {\n // An incorrect number of durations was defined.\n throw new Error(\"invalid number of wait node duration arguments defined\");\n }\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n break;\n }\n\n case \"REPEAT\": {\n // Create a REPEAT AST node.\n const node = ASTNodeFactories.REPEAT();\n\n // Push the REPEAT node into the current scope.\n currentScope.push(node);\n\n // The arguments of a repeat node are optional. We may have:\n // - No node arguments, in which case the repeat note will iterate indefinitely.\n // - One node argument which will be the explicit number of iterations to make.\n // - Two node arguments which define the min and max iteration bounds from which a random iteration count will be picked.\n if (tokens[0] === \"[\") {\n // An iteration count has been defined. Get the iteration and potential maximum iteration of the wait.\n const nodeArguments = getArguments(\n tokens,\n placeholders,\n (arg) => arg.type === \"number\" && !!arg.isInteger,\n \"repeat node iteration counts must be integer values\"\n ).map((argument) => argument.value);\n\n // We should have got one or two iteration counts.\n if (nodeArguments.length === 1) {\n // A static iteration count was defined.\n node.iterations = nodeArguments[0] as number;\n } else if (nodeArguments.length === 2) {\n // A minimum and maximum iteration count was defined.\n node.iterationsMin = nodeArguments[0] as number;\n node.iterationsMax = nodeArguments[1] as number;\n } else {\n // An incorrect number of iteration counts was defined.\n throw new Error(\"invalid number of repeat node iteration count arguments defined\");\n }\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new REPEAT nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"RETRY\": {\n // Create a RETRY AST node.\n const node = ASTNodeFactories.RETRY();\n\n // Push the RETRY node into the current scope.\n currentScope.push(node);\n\n // The arguments of a retry node are optional. We may have:\n // - No node arguments, in which case the retry note will attempt indefinitely.\n // - One node argument which will be the explicit number of attempts to make.\n // - Two node arguments which define the min and max attempt bounds from which a random attempt count will be picked.\n if (tokens[0] === \"[\") {\n // An attempt count has been defined. Get the attempt count and potential maximum attempt count of the wait.\n const nodeArguments = getArguments(\n tokens,\n placeholders,\n (arg) => arg.type === \"number\" && !!arg.isInteger,\n \"retry node attempt counts must be integer values\"\n ).map((argument) => argument.value);\n\n // We should have got one or two attempt counts.\n if (nodeArguments.length === 1) {\n // A static attempt count was defined.\n node.attempts = nodeArguments[0] as number;\n } else if (nodeArguments.length === 2) {\n // A minimum and maximum attempt count was defined.\n node.attemptsMin = nodeArguments[0] as number;\n node.attemptsMax = nodeArguments[1] as number;\n } else {\n // An incorrect number of attempt counts was defined.\n throw new Error(\"invalid number of retry node attempt count arguments defined\");\n }\n }\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n\n popAndCheck(tokens, \"{\");\n\n // The new scope is that of the new RETRY nodes children.\n stack.push(node.children!);\n break;\n }\n\n case \"ACTION\": {\n // Create a ACTION AST node.\n const node = ASTNodeFactories.ACTION();\n\n // Push the ACTION node into the current scope.\n currentScope.push(node);\n\n // We must have arguments defined, as we require an action name argument.\n if (tokens[0] !== \"[\") {\n throw new Error(\"expected action name identifier argument\");\n }\n\n // The action name will be defined as a node argument.\n const actionArguments = getArguments(tokens, placeholders);\n\n // We should have at least one identifer argument for an action node, which is the action name.\n if (actionArguments.length && actionArguments[0].type === \"identifier\") {\n // The action name will be the first and only node argument.\n node.actionName = actionArguments.shift()!.value as string;\n } else {\n throw new Error(\"expected action name identifier argument\");\n }\n\n // Only the first argument should have been an identifier, all following arguments must be string, number, boolean or null.\n actionArguments\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n \"invalid action node argument value '\" +\n arg.value +\n \"', must be string, number, boolean or null\"\n );\n });\n\n // Any node arguments that follow the action name identifier will be treated as action function arguments.\n node.actionArguments = actionArguments;\n\n // Try to pick any attributes off of the token stack.\n node.attributes = getAttributes(tokens, placeholders);\n break;\n }\n\n case \"}\": {\n // The '}' character closes the current scope.\n stack.pop();\n break;\n }\n\n default: {\n throw new Error(`unexpected token '${token}'`);\n }\n }\n }\n\n // A function to recursively validate each of the nodes in the AST.\n const validateASTNode = (node: Validatable, depth: number): void => {\n // Validate the node.\n node.validate(depth);\n\n // Validate each child of the node.\n (node.children || []).forEach((child) => validateASTNode(child, depth + 1));\n };\n\n // Start node validation from the definition root.\n validateASTNode(\n {\n children: stack[0] as RootAstNode[],\n validate(this: { children: RootAstNode[] }) {\n // We must have at least one node defined as the definition scope, which should be a root node.\n if (this.children.length === 0) {\n throw new Error(\"expected root node to have been defined\");\n }\n\n // Each node at the base of the definition scope MUST be a root node.\n for (const definitionLevelNode of this.children) {\n if (definitionLevelNode.type !== \"root\") {\n throw new Error(\"expected root node at base of definition\");\n }\n }\n\n // Exactly one root node must not have a name defined. This will be the main root, others will have to be referenced via branch nodes.\n if (this.children.filter((definitionLevelNode) => definitionLevelNode.name === null).length !== 1) {\n throw new Error(\"expected single unnamed root node at base of definition to act as main root\");\n }\n\n // No two named root nodes can have matching names.\n const rootNodeNames: string[] = [];\n for (const definitionLevelNode of this.children) {\n if (rootNodeNames.indexOf(definitionLevelNode.name!) !== -1) {\n throw new Error(`multiple root nodes found with duplicate name '${definitionLevelNode.name}'`);\n } else {\n rootNodeNames.push(definitionLevelNode.name!);\n }\n }\n }\n },\n 0\n );\n\n // Return the root AST nodes.\n return stack[0];\n}\n\n/**\n * Pop the next raw token off of the stack and throw an error if it wasn't the expected one.\n * @param tokens The array of remaining tokens.\n * @param expected An optional string or array or items, one of which must match the next popped token.\n * @returns The popped token.\n */\nfunction popAndCheck(tokens: string[], expected: string | string[]) {\n // Get and remove the next token.\n const popped = tokens.shift();\n\n // We were expecting another token.\n if (popped === undefined) {\n throw new Error(\"unexpected end of definition\");\n }\n\n // Do we have an expected token/tokens array?\n if (expected !== undefined) {\n // Check whether the popped token matches at least one of our expected items.\n var tokenMatchesExpectation = ([] as string[])\n .concat(expected)\n .some((item) => popped.toUpperCase() === item.toUpperCase());\n\n // Throw an error if the popped token didn't match any of our expected items.\n if (!tokenMatchesExpectation) {\n const expectationString = ([] as string[])\n .concat(expected)\n .map((item) => \"'\" + item + \"'\")\n .join(\" or \");\n\n throw new Error(`unexpected token found. Expected '${expectationString}' but got '${popped}'`);\n }\n }\n\n // Return the popped token.\n return popped;\n}\n\ntype Placeholders = { [key: string]: string };\n\n/**\n * Pull an argument definition list off of the token stack.\n * @param tokens The array of remaining tokens.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @param argumentValidator The argument validator function.\n * @param validationFailedMessage The exception message to throw if argument validation fails.\n * @returns The argument definition list.\n */\nfunction getArguments(\n tokens: string[],\n stringArgumentPlaceholders: Placeholders,\n argumentValidator?: (arg: AnyArgument) => boolean,\n validationFailedMessage?: string\n) {\n // Any lists of arguments will always be wrapped in '[]' for node arguments or '()' for attribute arguments.\n // We are looking for a '[' or '(' opener that wraps the argument tokens and the relevant closer.\n const closer = popAndCheck(tokens, [\"[\", \"(\"]) === \"[\" ? \"]\" : \")\";\n\n const argumentListTokens: string[] = [];\n const argumentList: AnyArgument[] = [];\n\n // Grab all tokens between the '[' and ']' or '(' and ')'.\n while (tokens.length && tokens[0] !== closer) {\n // The next token is part of our arguments list.\n argumentListTokens.push(tokens.shift()!);\n }\n\n // Validate the order of the argument tokens. Each token must either be a ',' or a single argument that satisfies the validator.\n argumentListTokens.forEach((token, index) => {\n // Get whether this token should be an actual argument.\n const shouldBeArgumentToken = !(index & 1);\n\n // If the current token should be an actual argument then validate it,otherwise it should be a ',' token.\n if (shouldBeArgumentToken) {\n // Get the argument definition.\n const argumentDefinition = getArgumentDefinition(token!, stringArgumentPlaceholders);\n\n // Try to validate the argument.\n if (argumentValidator && !argumentValidator(argumentDefinition)) {\n throw new Error(validationFailedMessage);\n }\n\n // This is a valid argument!\n argumentList.push(argumentDefinition);\n } else {\n // The current token should be a ',' token.\n if (token !== \",\") {\n throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`);\n }\n }\n });\n\n // The arguments list should terminate with a ']' or ')' token, depending on the opener.\n popAndCheck(tokens, closer);\n\n // Return the argument list.\n return argumentList;\n}\n\n/**\n * Gets an argument value definition.\n * @param token The argument token.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @returns An argument value definition.\n */\nfunction getArgumentDefinition(token: string, stringArgumentPlaceholders: Placeholders): AnyArgument {\n // Check whether the token represents a null value.\n if (token === \"null\") {\n return {\n value: null,\n type: \"null\"\n } as NullArgument;\n }\n\n // Check whether the token represents a boolean value.\n if (token === \"true\" || token === \"false\") {\n return {\n value: token === \"true\",\n type: \"boolean\"\n } as BooleanArgument;\n }\n\n // Check whether the token represents a number value.\n // TODO: Relies on broken isNaN - see MDN.\n // if (!Number.isNaN(token)) {\n if (!isNaN(token as any)) {\n return {\n value: parseFloat(token),\n isInteger: parseFloat(token) === parseInt(token, 10),\n type: \"number\"\n } as NumberArgument;\n }\n\n // Check whether the token is a placeholder (e.g. @@0@@) representing a string literal.\n if (token.match(/^@@\\d+@@$/g)) {\n return {\n value: stringArgumentPlaceholders[token].replace('\\\\\"', '\"'),\n type: \"string\"\n } as StringPlaceholderArgument;\n }\n\n // The only remaining option is that the argument value is an identifier.\n return {\n value: token,\n type: \"identifier\"\n } as IdentifierArgument;\n}\n\n/**\n * Pull any attributes off of the token stack.\n * @param tokens The array of remaining tokens.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @returns An array of attributes defined by any directly following tokens.\n */\nfunction getAttributes(tokens: string[], stringArgumentPlaceholders: Placeholders) {\n // Create an array to hold any attributes found.\n const attributes: Attribute[] = [];\n\n // Keep track of names of attribute that we have found on the token stack, as we cannot have duplicates.\n const attributesFound: string[] = [];\n\n // Try to get the attribute factory for the next token.\n let attributeFactory = AttributeFactories[(tokens[0] || \"\").toUpperCase()];\n\n // Pull attribute tokens off of the tokens stack until we have no more.\n while (attributeFactory) {\n // Check to make sure that we have not already created a attribute of this type for this node.\n if (attributesFound.indexOf(tokens[0].toUpperCase()) !== -1) {\n throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`);\n }\n\n // Add the current attribute type to our array of found attributes.\n attributesFound.push(tokens.shift()!.toUpperCase());\n\n // Grab any attribute arguments.\n const attributeArguments = getArguments(tokens, stringArgumentPlaceholders);\n\n // The first attribute argument has to be an identifer, this will reference an agent function.\n if (attributeArguments.length === 0 || attributeArguments[0].type !== \"identifier\") {\n throw new Error(\"expected agent function name identifier argument for attribute\");\n }\n\n // Grab the first attribute which is an identifier that will reference an agent function.\n const attributeFunctionName = attributeArguments.shift()! as IdentifierArgument;\n\n // Any remaining attribute arguments must have a type of string, number, boolean or null.\n attributeArguments\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n \"invalid attribute argument value '\" + arg.value + \"', must be string, number, boolean or null\"\n );\n });\n\n // Create the attribute and add it to the array of attributes found.\n attributes.push(attributeFactory(attributeFunctionName.value, attributeArguments));\n\n // Try to get the next attribute name token, as there could be multiple.\n attributeFactory = AttributeFactories[(tokens[0] || \"\").toUpperCase()];\n }\n\n return attributes;\n}\n\n/**\n * Swaps out any node/attribute argument string literals with placeholders.\n * @param definition The definition.\n * @returns An object containing a mapping of placeholders to original string values as well as the processed definition string.\n */\nfunction substituteStringLiterals(definition: string): {\n placeholders: { [key: string]: string };\n processedDefinition: string;\n} {\n // Create an object to hold the mapping of placeholders to original string values.\n const placeholders: Placeholders = {};\n\n // Replace any string literals wrapped with double quotes in our definition with placeholders to be processed later.\n const processedDefinition = definition.replace(/\\\"(\\\\.|[^\"\\\\])*\\\"/g, (match) => {\n var strippedMatch = match.substring(1, match.length - 1);\n var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch);\n\n // If we have no existing string literal match then create a new placeholder.\n if (!placeholder) {\n placeholder = `@@${Object.keys(placeholders).length}@@`;\n placeholders[placeholder] = strippedMatch;\n }\n\n return placeholder;\n });\n\n return { placeholders, processedDefinition };\n}\n\n/**\n * Parse the tree definition into an array of raw tokens.\n * @param definition The definition.\n * @returns An array of tokens parsed from the definition.\n */\nfunction parseTokensFromDefinition(definition: string): string[] {\n // Add some space around various important characters so that they can be plucked out easier as individual tokens.\n definition = definition.replace(/\\(/g, \" ( \");\n definition = definition.replace(/\\)/g, \" ) \");\n definition = definition.replace(/\\{/g, \" { \");\n definition = definition.replace(/\\}/g, \" } \");\n definition = definition.replace(/\\]/g, \" ] \");\n definition = definition.replace(/\\[/g, \" [ \");\n definition = definition.replace(/\\,/g, \" , \");\n\n // Split the definition into raw token form and return it.\n return definition.replace(/\\s+/g, \" \").trim().split(\" \");\n}\n", "import GuardPath, { GuardPathPart } from \"./attributes/guards/GuardPath\";\nimport buildRootASTNodes, { AnyArgument, RootAstNode } from \"./RootAstNodesBuilder\";\nimport State, { AnyState } from \"./State\";\nimport Lookup from \"./Lookup\";\nimport Node from \"./nodes/Node\";\nimport Root from \"./nodes/decorator/Root\";\nimport Composite from \"./nodes/composite/Composite\";\nimport Decorator from \"./nodes/decorator/Decorator\";\nimport { Agent, GlobalFunction } from \"./Agent\";\nimport { CallbackAttributeDetails } from \"./attributes/callbacks/Callback\";\nimport { GuardAttributeDetails } from \"./attributes/guards/Guard\";\nimport { BehaviourTreeOptions } from \"./BehaviourTreeOptions\";\n\n// Purely for outside inspection of the tree.\nexport type FlattenedTreeNode = {\n id: string;\n type: string;\n caption: string;\n state: AnyState;\n guards: GuardAttributeDetails[];\n callbacks: CallbackAttributeDetails[];\n args: AnyArgument[];\n parentId: string | null;\n};\n\n/**\n * A representation of a behaviour tree.\n */\nexport class BehaviourTree {\n /**\n * The main root tree node.\n */\n public readonly rootNode: Root;\n\n /**\n * Creates a new instance of the BehaviourTree class.\n * @param definition The behaviour tree definition.\n * @param agent The agent instance that this behaviour tree is modelling behaviour for.\n * @param options The behaviour tree options object.\n */\n constructor(definition: string, private agent: Agent, private options: BehaviourTreeOptions = {}) {\n // The tree definition must be defined and a valid string.\n if (typeof definition !== \"string\") {\n throw new Error(\"the tree definition must be a string\");\n }\n\n // The agent must be defined and not null.\n if (typeof agent !== \"object\" || agent === null) {\n throw new Error(\"the agent must be defined and not null\");\n }\n\n // Parse the behaviour tree definition, create the populated tree of behaviour tree nodes, and get the root.\n this.rootNode = BehaviourTree.createRootNode(definition);\n }\n\n /**\n * Gets whether the tree is in the RUNNING state.\n * @returns true if the tree is in the RUNNING state, otherwise false.\n */\n isRunning() {\n return this.rootNode.getState() === State.RUNNING;\n }\n\n /**\n * Gets the current tree state of SUCCEEDED, FAILED, READY or RUNNING.\n * @returns The current tree state.\n */\n getState() {\n return this.rootNode.getState();\n }\n\n /**\n * Step the tree.\n * Carries out a node update that traverses the tree from the root node outwards to any child nodes, skipping those that are already in a resolved state of SUCCEEDED or FAILED.\n * After being updated, leaf nodes will have a state of SUCCEEDED, FAILED or RUNNING. Leaf nodes that are left in the RUNNING state as part of a tree step will be revisited each\n * subsequent step until they move into a resolved state of either SUCCEEDED or FAILED, after which execution will move through the tree to the next node with a state of READY.\n *\n * Calling this method when the tree is already in a resolved state of SUCCEEDED or FAILED will cause it to be reset before tree traversal begins.\n */\n step() {\n // If the root node has already been stepped to completion then we need to reset it.\n if (this.rootNode.getState() === State.SUCCEEDED || this.rootNode.getState() === State.FAILED) {\n this.rootNode.reset();\n }\n\n try {\n this.rootNode.update(this.agent, this.options);\n } catch (exception) {\n throw new Error(`error stepping tree: ${(exception as Error).message}`);\n }\n }\n\n /**\n * Resets the tree from the root node outwards to each nested node, giving each a state of READY.\n */\n reset() {\n this.rootNode.reset();\n }\n\n /**\n * Gets the flattened details of every node in the tree.\n * @returns The flattened details of every node in the tree.\n */\n getFlattenedNodeDetails(): FlattenedTreeNode[] {\n // Create an empty flattened array of tree nodes.\n const flattenedTreeNodes: FlattenedTreeNode[] = [];\n\n /**\n * Helper function to process a node instance and push details into the flattened tree nodes array.\n * @param node The current node.\n * @param parentUid The UID of the node parent, or null if the node is the main root node.\n */\n const processNode = (node: Node, parentUid: string | null) => {\n // Get the guard and callback attribute details for this node.\n const guards = node\n .getAttributes()\n .filter((attribute) => attribute.isGuard())\n .map((attribute) => attribute.getDetails()) as GuardAttributeDetails[];\n const callbacks = node\n .getAttributes()\n .filter((attribute) => !attribute.isGuard())\n .map((attribute) => attribute.getDetails()) as CallbackAttributeDetails[];\n\n // Push the current node into the flattened nodes array.\n flattenedTreeNodes.push({\n id: node.getUid(),\n type: node.getType(),\n caption: node.getName(),\n state: node.getState(),\n guards,\n callbacks,\n args: node.getArguments(),\n parentId: parentUid\n });\n\n // Process each of the nodes children if it is not a leaf node.\n if (!node.isLeafNode()) {\n (node as Composite | Decorator)\n .getChildren()\n .forEach((child) => processNode(child, (node as Composite | Decorator).getUid()));\n }\n };\n\n // Convert the nested node structure into a flattened array of node details.\n processNode(this.rootNode, null);\n\n return flattenedTreeNodes;\n }\n\n /**\n * Registers the action/condition/guard/callback function or subtree with the given name.\n * @param name The name of the function or subtree to register.\n * @param value The function or subtree definition to register.\n */\n static register(name: string, value: GlobalFunction | string) {\n if (typeof value === \"function\") {\n // We are going to register a action/condition/guard/callback function.\n Lookup.setFunc(name, value);\n } else if (typeof value === \"string\") {\n // We are going to register a subtree.\n let rootASTNodes: RootAstNode[];\n\n try {\n // Try to create the behaviour tree AST based on the definition provided, this could fail if the definition is invalid.\n rootASTNodes = buildRootASTNodes(value);\n } catch (exception) {\n // There was an issue in trying to parse and build the tree definition.\n throw new Error(`error registering definition: ${(exception as Error).message}`);\n }\n\n // This function should only ever be called with a definition containing a single unnamed root node.\n if (rootASTNodes.length != 1 || rootASTNodes[0].name !== null) {\n throw new Error(\"error registering definition: expected a single unnamed root node\");\n }\n\n Lookup.setSubtree(name, rootASTNodes[0]);\n } else {\n throw new Error(\"unexpected value, expected string definition or function\");\n }\n }\n\n /**\n * Unregisters the registered action/condition/guard/callback function or subtree with the given name.\n * @param name The name of the registered action/condition/guard/callback function or subtree to unregister.\n */\n static unregister(name: string): void {\n Lookup.remove(name);\n }\n\n /**\n * Unregister all registered action/condition/guard/callback functions and subtrees.\n */\n static unregisterAll(): void {\n Lookup.empty();\n }\n\n /**\n * Parses a behaviour tree definition and creates a tree of behaviour tree nodes.\n * @param {string} definition The behaviour tree definition.\n * @returns The root behaviour tree node.\n */\n private static createRootNode(definition: string): Root {\n try {\n // Try to create the behaviour tree AST based on the definition provided, this could fail if the definition is invalid.\n const rootASTNodes = buildRootASTNodes(definition);\n\n // Create a symbol to use as the main root key in our root node mapping.\n const mainRootNodeKey = Symbol(\"__root__\");\n\n // Create a mapping of root node names to root AST tokens. The main root node will have a key of Symbol(\"__root__\").\n const rootNodeMap: { [key: string | symbol]: RootAstNode } = {};\n for (const rootASTNode of rootASTNodes) {\n rootNodeMap[rootASTNode.name === null ? mainRootNodeKey : rootASTNode.name!] = rootASTNode;\n }\n\n // Convert the AST to our actual tree and get the root node.\n const rootNode: Root = rootNodeMap[mainRootNodeKey].createNodeInstance(\n // Create a provider for named root nodes that are part of our definition or have been registered. Prioritising the former.\n (name: string): RootAstNode => (rootNodeMap[name] ? rootNodeMap[name] : Lookup.getSubtree(name)),\n []\n );\n\n // Set a guard path on every leaf of the tree to evaluate as part of its update.\n BehaviourTree.applyLeafNodeGuardPaths(rootNode);\n\n // Return the root node.\n return rootNode;\n } catch (exception) {\n // There was an issue in trying to parse and build the tree definition.\n throw new Error(`error parsing tree: ${(exception as Error).message}`);\n }\n }\n\n /**\n * Applies a guard path to every leaf of the tree to evaluate as part of each update.\n * @param rootNode The main root tree node.\n */\n private static applyLeafNodeGuardPaths(rootNode: Root) {\n const nodePaths: Node[][] = [];\n\n const findLeafNodes = (path: Node[], node: Node) => {\n // Add the current node to the path.\n path = path.concat(node);\n\n // Check whether the current node is a leaf node.\n if (node.isLeafNode()) {\n nodePaths.push(path);\n } else {\n (node as Composite | Decorator).getChildren().forEach((child) => findLeafNodes(path, child));\n }\n };\n\n // Find all leaf node paths, starting from the root.\n findLeafNodes([], rootNode);\n\n nodePaths.forEach((path) => {\n // Each node in the current path will have to be assigned a guard path, working from the root outwards.\n for (let depth = 0; depth < path.length; depth++) {\n // Get the node in the path at the current depth.\n const currentNode = path[depth];\n\n // The node may already have been assigned a guard path, if so just skip it.\n if (currentNode.hasGuardPath()) {\n continue;\n }\n\n // Create the guard path for the current node.\n const guardPath = new GuardPath(\n path\n .slice(0, depth + 1)\n .map((node) => ({ node, guards: node.getGuardAttributes() }))\n .filter((details) => details.guards.length > 0)\n );\n\n // Assign the guard path to the current node.\n currentNode.setGuardPath(guardPath);\n }\n });\n }\n}\n"], - "mappings": "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", - "names": ["Participant", "Lotto", "createLotto", "State", "createLotto"] + "sources": ["../node_modules/lotto-draw/dist/Participant.js", "../node_modules/lotto-draw/dist/Utilities.js", "../node_modules/lotto-draw/dist/Lotto.js", "../node_modules/lotto-draw/dist/createLotto.js", "../node_modules/lotto-draw/dist/index.js", "../src/index.ts", "../src/State.ts", "../src/BehaviourTreeDefinitionUtilities.ts", "../src/mdsl/MDSLUtilities.ts", "../src/mdsl/MDSLNodeArgumentParser.ts", "../src/mdsl/MDSLNodeAttributeParser.ts", "../src/mdsl/MDSLDefinitionParser.ts", "../src/BehaviourTreeDefinitionValidator.ts", "../src/Lookup.ts", "../src/attributes/guards/GuardUnsatisifedException.ts", "../src/attributes/guards/GuardPath.ts", "../src/nodes/Node.ts", "../src/nodes/composite/Composite.ts", "../src/nodes/composite/Parallel.ts", "../src/nodes/composite/Selector.ts", "../src/nodes/composite/Sequence.ts", "../src/nodes/composite/Lotto.ts", "../src/nodes/decorator/Decorator.ts", "../src/nodes/decorator/Fail.ts", "../src/nodes/decorator/Flip.ts", "../src/nodes/decorator/Repeat.ts", "../src/nodes/decorator/Retry.ts", "../src/nodes/decorator/Root.ts", "../src/nodes/decorator/Succeed.ts", "../src/nodes/leaf/Leaf.ts", "../src/nodes/leaf/Action.ts", "../src/nodes/leaf/Condition.ts", "../src/nodes/leaf/Wait.ts", "../src/attributes/Attribute.ts", "../src/attributes/guards/Guard.ts", "../src/attributes/guards/While.ts", "../src/attributes/guards/Until.ts", "../src/attributes/callbacks/Callback.ts", "../src/attributes/callbacks/Entry.ts", "../src/attributes/callbacks/Step.ts", "../src/attributes/callbacks/Exit.ts", "../src/BehaviourTreeBuilder.ts", "../src/BehaviourTree.ts"], + "sourcesContent": ["\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.Participant = void 0;\r\n/**\r\n * A participant that holds a number of tickets.\r\n */\r\nvar Participant = /** @class */ (function () {\r\n /**\r\n * Creates an instance of the Participant class.\r\n * @param participant The actual participant.\r\n * @param tickets The number of tickets held by the participant.\r\n */\r\n function Participant(participant, tickets) {\r\n if (tickets === void 0) { tickets = 1; }\r\n this._participant = participant;\r\n this._tickets = tickets;\r\n }\r\n Object.defineProperty(Participant.prototype, \"participant\", {\r\n /** Gets the actual participant. */\r\n get: function () {\r\n return this._participant;\r\n },\r\n enumerable: false,\r\n configurable: true\r\n });\r\n Object.defineProperty(Participant.prototype, \"tickets\", {\r\n /** Gets or sets the number of tickets held by the participant. */\r\n get: function () {\r\n return this._tickets;\r\n },\r\n set: function (value) {\r\n this._tickets = value;\r\n },\r\n enumerable: false,\r\n configurable: true\r\n });\r\n return Participant;\r\n}());\r\nexports.Participant = Participant;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.isNaturalNumber = exports.isNullOrUndefined = void 0;\r\n/**\r\n * Gets whether the value provided is null or undefined.\r\n * @param value The value to check.\r\n * @returns Whether the value provided is null or undefined.\r\n */\r\nfunction isNullOrUndefined(value) {\r\n return value === null || value === undefined;\r\n}\r\nexports.isNullOrUndefined = isNullOrUndefined;\r\n/**\r\n * Gets whether the value provided is a natural number.\r\n * @param value The value to check.\r\n * @returns Whether the value provided is a natural number.\r\n */\r\nfunction isNaturalNumber(value) {\r\n return typeof value === \"number\" && value >= 1 && Math.floor(value) === value;\r\n}\r\nexports.isNaturalNumber = isNaturalNumber;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.Lotto = void 0;\r\nvar Participant_1 = require(\"./Participant\");\r\nvar Utilities_1 = require(\"./Utilities\");\r\n/**\r\n * Represents a lotto consisting of a number of pickable ticket-holding participants.\r\n */\r\nvar Lotto = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of Lotto.\r\n * @param customRandom The custom RNG to use in place of Math.random().\r\n */\r\n function Lotto(customRandom) {\r\n /** The array of participants that are holding tickets in the lotto. */\r\n this._participants = [];\r\n this._customRandom = customRandom;\r\n }\r\n /**\r\n * Adds a participant with the specified number of tickets, or adds to the participant ticket count if the participant already holds tickets.\r\n * @param participant The participant to add or to increase the ticket count for if they already hold tickets.\r\n * @param tickets The number of tickets, defaults to 1.\r\n * @returns The Lotto instance.\r\n */\r\n Lotto.prototype.add = function (participant, tickets) {\r\n if (tickets === void 0) { tickets = 1; }\r\n // Check that we have a valid ticket count.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n // Check whether this participant has already been added.\r\n var existingParticipant = this._participants.find(function (part) { return part.participant === participant; });\r\n if (existingParticipant) {\r\n // The participant has already been added to the lotto so just add to their ticket count.\r\n existingParticipant.tickets += tickets;\r\n }\r\n else {\r\n // The participant is not part of the lotto so we should add them.\r\n this._participants.push(new Participant_1.Participant(participant, tickets));\r\n }\r\n return this;\r\n };\r\n /**\r\n * Removes the specified number of tickets for the given participant from the draw, or all tickets if a ticket number is not defined.\r\n * @param participant The participant to remove tickets for.\r\n * @param tickets The number of tickets to remove, or undefined if all tickets are to be removed.\r\n * @returns The Lotto instance.\r\n */\r\n Lotto.prototype.remove = function (participant, tickets) {\r\n // Attempt to get the existing participant.\r\n var existingParticipant = this._participants.find(function (part) { return part.participant === participant; });\r\n // There is nothing to do if the specified participant isn't even part of the lotto.\r\n if (!existingParticipant) {\r\n return this;\r\n }\r\n // Check whether a tickets value was given.\r\n if (tickets !== undefined) {\r\n // Check that we have a valid ticket count.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n existingParticipant.tickets -= tickets;\r\n // If the participant no longer holds any tickets then they should be removed.\r\n if (existingParticipant.tickets < 1) {\r\n this._participants = this._participants.filter(function (part) { return part !== existingParticipant; });\r\n }\r\n }\r\n else {\r\n // We are removing all tickets for the participant so just remove them from the lotto.\r\n this._participants = this._participants.filter(function (part) { return part !== existingParticipant; });\r\n }\r\n return this;\r\n };\r\n /**\r\n * Draw a winning ticket and return the participant that holds the ticket.\r\n * @param options The draw options.\r\n * @returns The participant that holds the winning ticket.\r\n */\r\n Lotto.prototype.draw = function (options) {\r\n if (options === void 0) { options = {}; }\r\n // If we have no participants then just return null.\r\n if (this._participants.length === 0) {\r\n return null;\r\n }\r\n var redrawable = (0, Utilities_1.isNullOrUndefined)(options.redrawable) ? true : options.redrawable;\r\n var pickable = [];\r\n this._participants.forEach(function (_a) {\r\n var participant = _a.participant, tickets = _a.tickets;\r\n for (var ticketCount = 0; ticketCount < tickets; ticketCount++) {\r\n pickable.push(participant);\r\n }\r\n });\r\n var random;\r\n // We need a random floating-point number between 0 (inclusive) and 1 to scale up to pick our winner.\r\n // If a custom random function exists then we should use that or fall back to Math.random().\r\n if (this._customRandom) {\r\n // Call our custom random function to get a random floating-point number.\r\n random = this._customRandom();\r\n // Verify that the result of calling our custom random function is a number between 0 (inclusive) and 1.\r\n if (typeof random !== \"number\" || random < 0 || random >= 1) {\r\n throw new Error(\"the 'random' function provided did not return a number between 0 (inclusive) and 1\");\r\n }\r\n }\r\n else {\r\n // No custom random function was defined so just use good ol' Math.random().\r\n random = Math.random();\r\n }\r\n // Pick a winning participant.\r\n var winner = pickable[Math.floor(random * pickable.length)];\r\n // If the ticket isn't redrawable then we should remove a ticket from the winning participants ticket count.\r\n if (!redrawable) {\r\n this.remove(winner, 1);\r\n }\r\n // Return the winning participant.\r\n return winner;\r\n };\r\n /**\r\n * Draws multiple winning tickets and return an array of the participants that hold the winning tickets.\r\n * @param tickets The number of winning tickets to draw.\r\n * @param options The draw multiple options.\r\n * @returns An array of the participants that hold the winning tickets.\r\n */\r\n Lotto.prototype.drawMultiple = function (tickets, options) {\r\n if (options === void 0) { options = {}; }\r\n var uniqueResults = (0, Utilities_1.isNullOrUndefined)(options.unique) ? false : options.unique;\r\n // Handle cases where the user has asked for zero tickets (no idea why they would do this be we should trust them).\r\n if (tickets === 0) {\r\n return [];\r\n }\r\n // Now that we know out tickets value is not zero we should check that it is a valid natural number.\r\n if (!(0, Utilities_1.isNaturalNumber)(tickets)) {\r\n throw new Error(\"tickets value must be a natural number\");\r\n }\r\n var result = [];\r\n // Keep drawing tickets until we either reach the number of required tickets or we simply run out of tickets to draw.\r\n // We can run out of tickets to draw if 'options.redrawable' is explicity 'false' or we just had no participants when 'drawMultiple' was called.\r\n while (result.length < tickets && this._participants.length > 0) {\r\n result.push(this.draw(options));\r\n }\r\n // If the 'unique' draw option is set then we need to remove duplicates from the result list.\r\n if (uniqueResults) {\r\n // Create an array to store our unique results.\r\n var unique = [];\r\n // Iterate over all of our participants (with potential duplicates) and populate our array of unique values.\r\n for (var _i = 0, result_1 = result; _i < result_1.length; _i++) {\r\n var participant = result_1[_i];\r\n if (unique.indexOf(participant) === -1) {\r\n unique.push(participant);\r\n }\r\n }\r\n result = unique;\r\n }\r\n return result;\r\n };\r\n return Lotto;\r\n}());\r\nexports.Lotto = Lotto;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nexports.createLotto = void 0;\r\nvar Lotto_1 = require(\"./Lotto\");\r\n/**\r\n * A function that creates and returns a Lotto instance.\r\n * @param participantsOrOptions An array of initial participants or options relating to the creation of a Lotto instance.\r\n * @returns A new Lotto instance.\r\n */\r\nfunction createLotto(participantsOrOptions) {\r\n // If no initial participants or lotto options were provided as an argument then we can just return a new lotto instance now.\r\n if (!participantsOrOptions) {\r\n return new Lotto_1.Lotto();\r\n }\r\n // Check whether we were provided with an array of initial participants or a lotto options object.\r\n if (Array.isArray(participantsOrOptions)) {\r\n // We are dealing with a pre-defined array of participants.\r\n var participants = participantsOrOptions;\r\n var lotto_1 = new Lotto_1.Lotto();\r\n // If the lotto participants have been defined upfront then we will need to add them all to our lotto instance now.\r\n participants.forEach(function (_a) {\r\n var participant = _a[0], tokens = _a[1];\r\n return lotto_1.add(participant, tokens);\r\n });\r\n // Return the Lotto instance.\r\n return lotto_1;\r\n }\r\n else {\r\n // We are dealing with some lotto options.\r\n var random = participantsOrOptions.random, participants = participantsOrOptions.participants;\r\n // Create a Lotto instance passing the custom RNG function to use in place of Math.random() (which could be undefined).\r\n var lotto_2 = new Lotto_1.Lotto(random);\r\n // If the lotto participants have been defined upfront as part of the options then we will need to add them all to our lotto instance now.\r\n if (participants) {\r\n participants.forEach(function (_a) {\r\n var participant = _a[0], tokens = _a[1];\r\n return lotto_2.add(participant, tokens);\r\n });\r\n }\r\n // Return the Lotto instance.\r\n return lotto_2;\r\n }\r\n}\r\nexports.createLotto = createLotto;\r\n", "\"use strict\";\r\nObject.defineProperty(exports, \"__esModule\", { value: true });\r\nvar createLotto_1 = require(\"./createLotto\");\r\nexports.default = createLotto_1.createLotto;\r\n", "import State from \"./State\";\nimport { validateDefinition } from \"./BehaviourTreeDefinitionValidator\";\nimport { convertMDSLToJSON } from \"./mdsl/MDSLDefinitionParser\";\nimport { BehaviourTree, FlattenedTreeNode } from \"./BehaviourTree\";\nimport { BehaviourTreeOptions } from \"./BehaviourTreeOptions\";\n\nexport { BehaviourTree, State, convertMDSLToJSON, validateDefinition };\nexport type { FlattenedTreeNode, BehaviourTreeOptions };\n", "/**\n * Enumeration of node state types.\n */\nexport enum State {\n READY = \"mistreevous.ready\",\n RUNNING = \"mistreevous.running\",\n SUCCEEDED = \"mistreevous.succeeded\",\n FAILED = \"mistreevous.failed\"\n}\n\nexport { State as default };\n\nexport type CompleteState = State.SUCCEEDED | State.FAILED;\nexport type AnyState = State.READY | State.RUNNING | CompleteState;\n", "import {\n NodeDefinition,\n RootNodeDefinition,\n DecoratorNodeDefinition,\n CompositeNodeDefinition,\n AnyNodeDefinition,\n BranchNodeDefinition\n} from \"./BehaviourTreeDefinition\";\n\n/**\n * A type guard function that returns true if the specified node satisfies the RootNodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the RootNodeDefinition type.\n */\nexport function isRootNode(node: NodeDefinition): node is RootNodeDefinition {\n return node.type === \"root\";\n}\n\n/**\n * A type guard function that returns true if the specified node satisfies the BranchNodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the BranchNodeDefinition type.\n */\nexport function isBranchNode(node: NodeDefinition): node is BranchNodeDefinition {\n return node.type === \"branch\";\n}\n\n/**\n * A type guard function that returns true if the specified node satisfies the NodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the NodeDefinition type.\n */\nexport function isLeafNode(node: NodeDefinition): node is NodeDefinition {\n return [\"branch\", \"action\", \"condition\", \"wait\"].includes(node.type);\n}\n\n/**\n * A type guard function that returns true if the specified node satisfies the DecoratorNodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the DecoratorNodeDefinition type.\n */\nexport function isDecoratorNode(node: NodeDefinition): node is DecoratorNodeDefinition {\n return [\"root\", \"repeat\", \"retry\", \"flip\", \"succeed\", \"fail\"].includes(node.type);\n}\n\n/**\n * A type guard function that returns true if the specified node satisfies the CompositeNodeDefinition type.\n * @param node The node.\n * @returns A value of true if the specified node satisfies the CompositeNodeDefinition type.\n */\nexport function isCompositeNode(node: NodeDefinition): node is CompositeNodeDefinition {\n return [\"sequence\", \"selector\", \"lotto\", \"parallel\"].includes(node.type);\n}\n\n/**\n * Flatten a node definition into an array of all of its nested node definitions.\n * @param nodeDefinition The node definition to flatten.\n * @returns An array of all of nested node definitions.\n */\nexport function flattenDefinition(nodeDefinition: AnyNodeDefinition): AnyNodeDefinition[] {\n const nodes: AnyNodeDefinition[] = [];\n\n const processNode = (currentNodeDefinition: AnyNodeDefinition) => {\n nodes.push(currentNodeDefinition);\n\n if (isCompositeNode(currentNodeDefinition)) {\n currentNodeDefinition.children.forEach(processNode);\n } else if (isDecoratorNode(currentNodeDefinition)) {\n processNode(currentNodeDefinition.child);\n }\n };\n\n processNode(nodeDefinition);\n\n return nodes;\n}\n\n/**\n * Determines whether the passed value is an integer.\n * @param value The value to check.\n * @returns Whether the passed value is an integer.\n */\nexport function isInteger(value: unknown): boolean {\n return typeof value === \"number\" && Math.floor(value) === value;\n}\n\n/**\n * Determines whether the passed value is null or undefined.\n * @param value The value to check.\n * @returns Whether the passed value is null or undefined.\n */\nexport function isNullOrUndefined(value: unknown): boolean {\n return typeof value === \"undefined\" || value === null;\n}\n", "/**\n * A type defining an object that holds a reference to substitued string literals parsed from the definition.\n */\nexport type StringLiteralPlaceholders = { [key: string]: string };\n\n/**\n * Pop the next raw token from the specified array of tokens and throw an error if it wasn't the expected one.\n * @param tokens The array of tokens.\n * @param expected An optional string or array or items, one of which must match the next popped token.\n * @returns The popped token.\n */\nexport function popAndCheck(tokens: string[], expected?: string | string[]): string {\n // Get and remove the next token.\n const popped = tokens.shift();\n\n // We were expecting another token but there aren't any.\n if (popped === undefined) {\n throw new Error(\"unexpected end of definition\");\n }\n\n // Do we have an expected token/tokens array?\n if (expected != undefined) {\n // Get an array of expected values, if the popped token matches any then we are all good.\n const expectedValues = typeof expected === \"string\" ? [expected] : expected;\n\n // Check whether the popped token matches at least one of our expected items.\n var tokenMatchesExpectation = expectedValues.some((item) => popped.toUpperCase() === item.toUpperCase());\n\n // Throw an error if the popped token didn't match any of our expected items.\n if (!tokenMatchesExpectation) {\n const expectationString = expectedValues.map((item) => \"'\" + item + \"'\").join(\" or \");\n throw new Error(\"unexpected token found. Expected \" + expectationString + \" but got '\" + popped + \"'\");\n }\n }\n\n // Return the popped token.\n return popped;\n}\n\n/**\n * Swaps out any node/attribute argument string literals with placeholders.\n * @param definition The definition.\n * @returns An object containing a mapping of placeholders to original string values as well as the processed definition string.\n */\nexport function substituteStringLiterals(definition: string): {\n placeholders: StringLiteralPlaceholders;\n processedDefinition: string;\n} {\n // Create an object to hold the mapping of placeholders to original string values.\n const placeholders: StringLiteralPlaceholders = {};\n\n // Replace any string literals wrapped with double quotes in our definition with placeholders to be processed later.\n const processedDefinition = definition.replace(/\\\"(\\\\.|[^\"\\\\])*\\\"/g, (match) => {\n var strippedMatch = match.substring(1, match.length - 1);\n var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch);\n\n // If we have no existing string literal match then create a new placeholder.\n if (!placeholder) {\n placeholder = `@@${Object.keys(placeholders).length}@@`;\n placeholders[placeholder] = strippedMatch;\n }\n\n return placeholder;\n });\n\n return { placeholders, processedDefinition };\n}\n\n/**\n * Parse the tree definition into an array of raw tokens.\n * @param definition The definition.\n * @returns An array of tokens parsed from the definition.\n */\nexport function parseTokensFromDefinition(definition: string): string[] {\n // Add some space around various important characters so that they can be plucked out easier as individual tokens.\n definition = definition.replace(/\\(/g, \" ( \");\n definition = definition.replace(/\\)/g, \" ) \");\n definition = definition.replace(/\\{/g, \" { \");\n definition = definition.replace(/\\}/g, \" } \");\n definition = definition.replace(/\\]/g, \" ] \");\n definition = definition.replace(/\\[/g, \" [ \");\n definition = definition.replace(/\\,/g, \" , \");\n\n // Split the definition into raw token form and return it.\n return definition.replace(/\\s+/g, \" \").trim().split(\" \");\n}\n", "import { StringLiteralPlaceholders, popAndCheck } from \"./MDSLUtilities\";\n\n/**\n * A type representing any node function argument.\n */\ntype Argument = {\n /**\n * The argument value.\n */\n value: T;\n /**\n * The argument type, used for validation.\n */\n type: string;\n};\n\ntype NullArgument = Argument & {\n type: \"null\";\n};\n\ntype BooleanArgument = Argument & {\n type: \"boolean\";\n};\n\ntype NumberArgument = Argument & {\n type: \"number\";\n /**\n * A flag defining whether the number argument value is a valid integer. (used for validation)\n */\n isInteger: boolean;\n};\n\ntype StringPlaceholderArgument = Argument & {\n type: \"string\";\n};\n\ntype IdentifierArgument = Argument & {\n type: \"identifier\";\n};\n\n/**\n * A type representing a reference to any node function argument.\n */\ntype AnyArgument = NullArgument | BooleanArgument | NumberArgument | StringPlaceholderArgument | IdentifierArgument;\n\n/**\n * Parse an array of argument definitions from the specified tokens array.\n * @param tokens The array tokens to parse the argument definitions from.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @param argumentValidator The argument validator function.\n * @param validationFailedMessage The exception message to throw if argument validation fails.\n * @returns An array of argument definitions parsed from the specified tokens array.\n */\nexport function parseArgumentTokens(\n tokens: string[],\n stringArgumentPlaceholders: StringLiteralPlaceholders\n): AnyArgument[] {\n const argumentList: AnyArgument[] = [];\n\n // If the next token is not a '[' or '(' then we have no arguments to parse.\n if (![\"[\", \"(\"].includes(tokens[0])) {\n return argumentList;\n }\n\n // Any lists of arguments will always be wrapped in '[]' for node arguments or '()' for attribute arguments.\n // We are looking for a '[' or '(' opener that wraps the argument tokens and the relevant closer.\n const closingToken = popAndCheck(tokens, [\"[\", \"(\"]) === \"[\" ? \"]\" : \")\";\n\n const argumentListTokens: string[] = [];\n\n // Grab all tokens between the '[' and ']' or '(' and ')'.\n while (tokens.length && tokens[0] !== closingToken) {\n // The next token is part of our arguments list.\n argumentListTokens.push(tokens.shift()!);\n }\n\n // Validate the order of the argument tokens. Each token must either be a ',' or a single argument that satisfies the validator.\n argumentListTokens.forEach((token, index) => {\n // Get whether this token should be an actual argument.\n const shouldBeArgumentToken = !(index & 1);\n\n // If the current token should be an actual argument then validate it, otherwise it should be a ',' token.\n if (shouldBeArgumentToken) {\n // Get the argument definition.\n const argumentDefinition = getArgumentDefinition(token, stringArgumentPlaceholders);\n\n // This is a valid argument!\n argumentList.push(argumentDefinition);\n } else {\n // The current token should be a ',' token.\n if (token !== \",\") {\n throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`);\n }\n }\n });\n\n // The arguments list should terminate with a ']' or ')' token, depending on the opener.\n popAndCheck(tokens, closingToken);\n\n // Return the arguments.\n return argumentList;\n}\n\n/**\n * Gets an argument value definition.\n * @param token The argument token.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @returns An argument value definition.\n */\nfunction getArgumentDefinition(token: string, stringArgumentPlaceholders: StringLiteralPlaceholders): AnyArgument {\n // Check whether the token represents a null value.\n if (token === \"null\") {\n return {\n value: null,\n type: \"null\"\n } as NullArgument;\n }\n\n // Check whether the token represents a boolean value.\n if (token === \"true\" || token === \"false\") {\n return {\n value: token === \"true\",\n type: \"boolean\"\n } as BooleanArgument;\n }\n\n // Check whether the token represents a number value.\n // TODO: Relies on broken isNaN - see MDN.\n // if (!Number.isNaN(token)) {\n if (!isNaN(token as any)) {\n return {\n value: parseFloat(token),\n isInteger: parseFloat(token) === parseInt(token, 10),\n type: \"number\"\n } as NumberArgument;\n }\n\n // Check whether the token is a placeholder (e.g. @@0@@) representing a string literal.\n if (token.match(/^@@\\d+@@$/g)) {\n return {\n value: stringArgumentPlaceholders[token].replace('\\\\\"', '\"'),\n type: \"string\"\n } as StringPlaceholderArgument;\n }\n\n // The only remaining option is that the argument value is an identifier.\n return {\n value: token,\n type: \"identifier\"\n } as IdentifierArgument;\n}\n", "import { NodeAttributeDefinition } from \"../BehaviourTreeDefinition\";\nimport { parseArgumentTokens } from \"./MDSLNodeArgumentParser\";\nimport { StringLiteralPlaceholders } from \"./MDSLUtilities\";\n\n/**\n * A type defining the attribute definitions of a node.\n */\ntype NodeAttributes = {\n while?: NodeAttributeDefinition;\n until?: NodeAttributeDefinition;\n entry?: NodeAttributeDefinition;\n exit?: NodeAttributeDefinition;\n step?: NodeAttributeDefinition;\n};\n\n/**\n * Parse any node attribute definitions from the specified tokens array.\n * @param tokens The array of remaining tokens.\n * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values.\n * @returns An object of attribute definitions defined by any directly following tokens.\n */\nexport function parseAttributeTokens(\n tokens: string[],\n stringArgumentPlaceholders: StringLiteralPlaceholders\n): NodeAttributes {\n const nodeAttributeNames: (keyof NodeAttributes)[] = [\"while\", \"until\", \"entry\", \"exit\", \"step\"];\n\n // Create an object to hold any attributes found.\n const attributes: NodeAttributes = {};\n\n // Try to get the name of the attribute for the next token.\n let nextAttributeName = tokens[0]?.toLowerCase() as keyof NodeAttributes;\n\n // Pull attribute tokens as well as their arguments off of the tokens stack until we have no more.\n while (nodeAttributeNames.includes(nextAttributeName)) {\n // Check to make sure that we have not already created an attribute definition of this type.\n if (attributes[nextAttributeName]) {\n throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`);\n }\n\n // Remove the attribute name token from the array of tokens.\n tokens.shift();\n\n // Grab the attribute arguments, assuming the first to be an identifier.\n const [attributeCallIdentifier, ...attributeArguments] = parseArgumentTokens(\n tokens,\n stringArgumentPlaceholders\n );\n\n // The first attribute argument has to be an identifer, this will reference an agent function.\n if (attributeCallIdentifier?.type !== \"identifier\") {\n throw new Error(\"expected agent function or registered function name identifier argument for attribute\");\n }\n\n // Any attribute arguments (other than the expected call identifier) must have a type of string, number, boolean or null.\n attributeArguments\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n `invalid attribute argument value '${arg.value}', must be string, number, boolean or null`\n );\n });\n\n // Create the attribute definition and add it to the object of attribute definitions found.\n attributes[nextAttributeName] = {\n call: attributeCallIdentifier.value,\n args: attributeArguments.map(({ value }) => value)\n };\n\n // Try to get the next attribute name token, as there could be multiple.\n nextAttributeName = tokens[0]?.toLowerCase() as keyof NodeAttributes;\n }\n\n return attributes;\n}\n", "import {\n ActionNodeDefinition,\n AnyChildNodeDefinition,\n AnyNodeDefinition,\n BranchNodeDefinition,\n ConditionNodeDefinition,\n FailNodeDefinition,\n FlipNodeDefinition,\n LottoNodeDefinition,\n ParallelNodeDefinition,\n RepeatNodeDefinition,\n RetryNodeDefinition,\n RootNodeDefinition,\n SelectorNodeDefinition,\n SequenceNodeDefinition,\n SucceedNodeDefinition,\n WaitNodeDefinition\n} from \"../BehaviourTreeDefinition\";\nimport {\n isCompositeNode,\n isDecoratorNode,\n isLeafNode,\n isNullOrUndefined,\n isRootNode\n} from \"../BehaviourTreeDefinitionUtilities\";\nimport { parseArgumentTokens } from \"./MDSLNodeArgumentParser\";\nimport { parseAttributeTokens } from \"./MDSLNodeAttributeParser\";\nimport {\n StringLiteralPlaceholders,\n parseTokensFromDefinition,\n popAndCheck,\n substituteStringLiterals\n} from \"./MDSLUtilities\";\n\n/**\n * Convert the MDSL tree definition string into an equivalent JSON definition.\n * @param definition The tree definition string as MDSL.\n * @returns The root node JSON definitions.\n */\nexport function convertMDSLToJSON(definition: string): RootNodeDefinition[] {\n // Swap out any node/attribute argument string literals with a placeholder and get a mapping of placeholders to original values as well as the processed definition.\n const { placeholders, processedDefinition } = substituteStringLiterals(definition);\n\n // Parse our definition definition string into an array of raw tokens.\n const tokens = parseTokensFromDefinition(processedDefinition);\n\n return convertTokensToJSONDefinition(tokens, placeholders);\n}\n\n/**\n * Converts the specified tree definition tokens into a JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The root node JSON definitions.\n */\nfunction convertTokensToJSONDefinition(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): RootNodeDefinition[] {\n // There must be at least 3 tokens for the tree definition to be valid. 'ROOT', '{' and '}'.\n if (tokens.length < 3) {\n throw new Error(\"invalid token count\");\n }\n\n // We should have a matching number of '{' and '}' tokens. If not, then there are scopes that have not been properly closed.\n if (tokens.filter((token) => token === \"{\").length !== tokens.filter((token) => token === \"}\").length) {\n throw new Error(\"scope character mismatch\");\n }\n\n // Create an array of tree stack arrays where root nodes will always be at the botton and the current composite/decorator node at the top.\n // There should be an element in this array for every root node defined and every element should be an array with a root note as the first element.\n // E.g. A definition with two root nodes defined:\n // [\n // [root, lotto, sequence],\n // [root, selector]\n // ]\n const treeStacks: [Partial, ...Partial[]][] = [];\n\n // Create an array of all root node definitions that we create.\n const rootNodes: Partial[] = [];\n\n // A helper function used to push node definitions onto the tree stack.\n const pushNode = (node: AnyNodeDefinition) => {\n // If the node is a root node then we need to create a new tree stack array with the root node at the root.\n if (isRootNode(node)) {\n // We need to double-check that this root node is not the child of another node.\n // We can do this by checking whether the top tree stack is not empty (contains an existing node)\n if (treeStacks[treeStacks.length - 1]?.length) {\n throw new Error(\"a root node cannot be the child of another node\");\n }\n\n // Add the root node definition to our array of all parsed root node definitions.\n rootNodes.push(node);\n\n // Add the root node definition to the root of a new tree stack.\n treeStacks.push([node]);\n\n return;\n }\n\n // All non-root nodes should be pushed after their root nodes so handle cases\n // where we may not have any tree stacks or our top-most tree stack is empty.\n if (!treeStacks.length || !treeStacks[treeStacks.length - 1].length) {\n throw new Error(\"expected root node at base of definition\");\n }\n\n // Get the current tree stack that we are populating.\n const topTreeStack = treeStacks[treeStacks.length - 1];\n\n // Get the top-most node in the current tree stack, this will be a composite/decorator node\n // for which we will populate its children array if composite or setting its child if a decorator.\n const topTreeStackTopNode = topTreeStack[topTreeStack.length - 1] as AnyNodeDefinition;\n\n // If the top-most node in the current root stack is a composite or decorator\n // node then the current node should be added as a child of the top-most node.\n if (isCompositeNode(topTreeStackTopNode)) {\n topTreeStackTopNode.children = topTreeStackTopNode.children || [];\n topTreeStackTopNode.children.push(node);\n } else if (isDecoratorNode(topTreeStackTopNode)) {\n // If the top node already has a child node set then throw an error as a decorator should only have a single child.\n if (topTreeStackTopNode.child) {\n throw new Error(\"a decorator node must only have a single child node\");\n }\n\n topTreeStackTopNode.child = node;\n }\n\n // If the node we are adding is also a composite or decorator node, then we should push it\n // onto the current tree stack, as subsequent nodes will be added as its child/children.\n if (!isLeafNode(node)) {\n topTreeStack.push(node);\n }\n };\n\n // A helper function used to pop the top-most node definition off of the tree stack and return it.\n const popNode = (): AnyNodeDefinition | null => {\n let poppedNode: AnyNodeDefinition | null = null;\n\n // Get the current tree stack that we are populating.\n const topTreeStack = treeStacks[treeStacks.length - 1];\n\n // Pop the top-most node in the current tree stack if there is one.\n if (topTreeStack.length) {\n poppedNode = topTreeStack.pop() as AnyNodeDefinition;\n }\n\n // We don't want any empty tree stacks in our stack of tree stacks.\n if (!topTreeStack.length) {\n treeStacks.pop();\n }\n\n return poppedNode;\n };\n\n // We should keep processing the raw tokens until we run out of them.\n while (tokens.length) {\n // Grab the next token.\n const token = tokens.shift()!;\n\n // How we create the next node depends on the current raw token value.\n switch (token.toUpperCase()) {\n case \"ROOT\": {\n pushNode(createRootNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"SUCCEED\": {\n pushNode(createSucceedNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"FAIL\": {\n pushNode(createFailNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"FLIP\": {\n pushNode(createFlipNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"REPEAT\": {\n pushNode(createRepeatNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"RETRY\": {\n pushNode(createRetryNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"SEQUENCE\": {\n pushNode(createSequenceNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"SELECTOR\": {\n pushNode(createSelectorNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"PARALLEL\": {\n pushNode(createParallelNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"LOTTO\": {\n pushNode(createLottoNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"ACTION\": {\n pushNode(createActionNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"CONDITION\": {\n pushNode(createConditionNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"WAIT\": {\n pushNode(createWaitNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"BRANCH\": {\n pushNode(createBranchNode(tokens, stringLiteralPlaceholders));\n break;\n }\n\n case \"}\": {\n // The '}' character closes the current scope and means that we have to pop a node off of the current stack.\n const poppedNode = popNode();\n\n // Now that we have a node definition we can carry out any validation that may require the node to be fully populated.\n if (poppedNode) {\n validatePoppedNode(poppedNode);\n }\n\n break;\n }\n\n default: {\n throw new Error(`unexpected token: ${token}`);\n }\n }\n }\n\n return rootNodes as RootNodeDefinition[];\n}\n\n/**\n * Creates a root node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The root node JSON definition.\n */\nfunction createRootNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): RootNodeDefinition {\n // Create the root node definition.\n let node = {\n type: \"root\"\n } as Partial;\n\n // Parse any node arguments, we should only have one if any which will be an identifier argument for the root identifier.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // Check whether any node arguments were defined.\n if (nodeArguments.length) {\n // We should only have one argument, if any, which will be an identifier argument for the root identifier.\n if (nodeArguments.length === 1 && nodeArguments[0].type === \"identifier\") {\n // The root node identifier will be the first and only node argument value.\n node.id = nodeArguments[0].value as string;\n } else {\n throw new Error(\"expected single root name argument\");\n }\n }\n\n // Grab any node attribute definitions and spread them into the node definition.\n node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) };\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the root node definition.\n return node as RootNodeDefinition;\n}\n\n/**\n * Creates a succeed node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The succeed node JSON definition.\n */\nfunction createSucceedNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): SucceedNodeDefinition {\n const node = {\n type: \"succeed\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as SucceedNodeDefinition;\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the succeed node definition.\n return node;\n}\n\n/**\n * Creates a fail node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The fail node JSON definition.\n */\nfunction createFailNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): FailNodeDefinition {\n const node = {\n type: \"fail\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as FailNodeDefinition;\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the fail node definition.\n return node;\n}\n\n/**\n * Creates a flip node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The flip node JSON definition.\n */\nfunction createFlipNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): FlipNodeDefinition {\n const node = {\n type: \"flip\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as FlipNodeDefinition;\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the flip node definition.\n return node;\n}\n\n/**\n * Creates a repeat node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The repeat node JSON definition.\n */\nfunction createRepeatNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): RepeatNodeDefinition {\n let node = { type: \"repeat\" } as RepeatNodeDefinition;\n\n // Get the node arguments.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // The arguments of a repeat node are optional. We may have:\n // - No node arguments, in which case the repeat note will iterate indefinitely.\n // - One node argument which will be the explicit number of iterations to make.\n // - Two node arguments which define the min and max iteration bounds from which a random iteration count will be picked.\n if (nodeArguments.length) {\n // All repeat node arguments MUST be of type number and must be integer.\n nodeArguments\n .filter((arg) => arg.type !== \"number\" || !arg.isInteger)\n .forEach(() => {\n throw new Error(`repeat node iteration counts must be integer values`);\n });\n\n // We should have got one or two iteration counts.\n if (nodeArguments.length === 1) {\n // A static iteration count was defined.\n node.iterations = nodeArguments[0].value as number;\n\n // A repeat node must have a positive number of iterations if defined.\n if (node.iterations < 0) {\n throw new Error(\"a repeat node must have a positive number of iterations if defined\");\n }\n } else if (nodeArguments.length === 2) {\n // A minimum and maximum iteration count was defined.\n node.iterations = [nodeArguments[0].value as number, nodeArguments[1].value as number];\n\n // A repeat node must have a positive min and max iteration count if they are defined.\n if (node.iterations[0] < 0 || node.iterations[1] < 0) {\n throw new Error(\"a repeat node must have a positive minimum and maximum iteration count if defined\");\n }\n\n // A repeat node must not have an minimum iteration count that exceeds the maximum iteration count.\n if (node.iterations[0] > node.iterations[1]) {\n throw new Error(\n \"a repeat node must not have a minimum iteration count that exceeds the maximum iteration count\"\n );\n }\n } else {\n // An incorrect number of iteration counts was defined.\n throw new Error(\"invalid number of repeat node iteration count arguments defined\");\n }\n }\n\n // Grab any node attribute definitions and spread them into the node definition.\n node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) };\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the repeat node definition.\n return node;\n}\n\n/**\n * Creates a retry node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The retry node JSON definition.\n */\nfunction createRetryNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): RetryNodeDefinition {\n let node = { type: \"retry\" } as RetryNodeDefinition;\n\n // Get the node arguments.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // The arguments of a retry node are optional. We may have:\n // - No node arguments, in which case the retry note will attempt indefinitely.\n // - One node argument which will be the explicit number of attempts to make.\n // - Two node arguments which define the min and max attempt bounds from which a random attempt count will be picked.\n if (nodeArguments.length) {\n // All retry node arguments MUST be of type number and must be integer.\n nodeArguments\n .filter((arg) => arg.type !== \"number\" || !arg.isInteger)\n .forEach(() => {\n throw new Error(`retry node attempt counts must be integer values`);\n });\n\n // We should have got one or two attempt counts.\n if (nodeArguments.length === 1) {\n // A static attempt count was defined.\n node.attempts = nodeArguments[0].value as number;\n\n // A retry node must have a positive number of attempts if defined.\n if (node.attempts < 0) {\n throw new Error(\"a retry node must have a positive number of attempts if defined\");\n }\n } else if (nodeArguments.length === 2) {\n // A minimum and maximum attempt count was defined.\n node.attempts = [nodeArguments[0].value as number, nodeArguments[1].value as number];\n\n // A retry node must have a positive min and max attempts count if they are defined.\n if (node.attempts[0] < 0 || node.attempts[1] < 0) {\n throw new Error(\"a retry node must have a positive minimum and maximum attempt count if defined\");\n }\n\n // A retry node must not have a minimum attempt count that exceeds the maximum attempt count.\n if (node.attempts[0] > node.attempts[1]) {\n throw new Error(\n \"a retry node must not have a minimum attempt count that exceeds the maximum attempt count\"\n );\n }\n } else {\n // An incorrect number of attempt counts was defined.\n throw new Error(\"invalid number of retry node attempt count arguments defined\");\n }\n }\n\n // Grab any node attribute definitions and spread them into the node definition.\n node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) };\n\n // This is a decorator node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the retry node definition.\n return node;\n}\n\n/**\n * Creates a sequence node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The sequence node JSON definition.\n */\nfunction createSequenceNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): SequenceNodeDefinition {\n const node = {\n type: \"sequence\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as SequenceNodeDefinition;\n\n // This is a composite node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the sequence node definition.\n return node;\n}\n\n/**\n * Creates a selector node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The selector node JSON definition.\n */\nfunction createSelectorNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): SelectorNodeDefinition {\n const node = {\n type: \"selector\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as SelectorNodeDefinition;\n\n // This is a composite node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the selector node definition.\n return node;\n}\n\n/**\n * Creates a parallel node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The parallel node JSON definition.\n */\nfunction createParallelNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): ParallelNodeDefinition {\n const node = {\n type: \"parallel\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as ParallelNodeDefinition;\n\n // This is a composite node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the parallel node definition.\n return node;\n}\n\n/**\n * Creates a lotto node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The lotto node JSON definition.\n */\nfunction createLottoNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): LottoNodeDefinition {\n // If any node arguments have been defined then they must be our weights.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // All lotto node arguments MUST be of type number and must be positive integers.\n nodeArguments\n .filter((arg) => arg.type !== \"number\" || !arg.isInteger || arg.value < 0)\n .forEach(() => {\n throw new Error(`lotto node weight arguments must be positive integer values`);\n });\n\n const node = {\n type: \"lotto\",\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n } as LottoNodeDefinition;\n\n // Apply the weights if any were defined.\n if (nodeArguments.length) {\n node.weights = nodeArguments.map(({ value }) => value) as number[];\n }\n\n // This is a composite node, so we expect an opening '{'.\n popAndCheck(tokens, \"{\");\n\n // Return the lotto node definition.\n return node;\n}\n\n/**\n * Creates an action node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The action node JSON definition.\n */\nfunction createActionNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): ActionNodeDefinition {\n // Parse any node arguments, we should have at least one which will be an identifier argument for the action name\n // and agent function to invoke for the action, all other arguments are to be passed as arguments to that function.\n const [actionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // Our first argument MUST be defined and be an identifier as we require an action name argument.\n if (actionNameIdentifier?.type !== \"identifier\") {\n throw new Error(\"expected action name identifier argument\");\n }\n\n // Only the first argument should have been an identifier, all agent function arguments must be string, number, boolean or null.\n agentFunctionArgs\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n `invalid action node argument value '${arg.value}', must be string, number, boolean or null`\n );\n });\n\n // Return the action node definition.\n return {\n type: \"action\",\n call: actionNameIdentifier.value,\n args: agentFunctionArgs.map(({ value }) => value),\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n };\n}\n\n/**\n * Creates a condition node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The condition node JSON definition.\n */\nfunction createConditionNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): ConditionNodeDefinition {\n // Parse any node arguments, we should have at least one which will be an identifier argument for the condition name\n // and agent function to invoke for the condition, all other arguments are to be passed as arguments to that function.\n const [conditionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // Our first argument MUST be defined and be an identifier as we require a condition name argument.\n if (conditionNameIdentifier?.type !== \"identifier\") {\n throw new Error(\"expected condition name identifier argument\");\n }\n\n // Only the first argument should have been an identifier, all agent function arguments must be string, number, boolean or null.\n agentFunctionArgs\n .filter((arg) => arg.type === \"identifier\")\n .forEach((arg) => {\n throw new Error(\n `invalid condition node argument value '${arg.value}', must be string, number, boolean or null`\n );\n });\n\n // Return the condition node definition.\n return {\n type: \"condition\",\n call: conditionNameIdentifier.value,\n args: agentFunctionArgs.map(({ value }) => value),\n ...parseAttributeTokens(tokens, stringLiteralPlaceholders)\n };\n}\n\n/**\n * Creates a wait node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The wait node JSON definition.\n */\nfunction createWaitNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): WaitNodeDefinition {\n let node = { type: \"wait\" } as WaitNodeDefinition;\n\n // Get the node arguments.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // The arguments of a wait node are optional. We may have:\n // - No node arguments, in which case the wait will be indefinite until it is aborted.\n // - One node argument which will be the explicit duration of the wait.\n // - Two node arguments which define the min and max duration bounds from which a random duration will be picked.\n if (nodeArguments.length) {\n // All wait node arguments MUST be of type number and must be integer.\n nodeArguments\n .filter((arg) => arg.type !== \"number\" || !arg.isInteger)\n .forEach(() => {\n throw new Error(`wait node durations must be integer values`);\n });\n\n // We may have:\n // - One node argument which will be the explicit duration of the wait.\n // - Two node arguments which define the min and max duration bounds from which a random duration will be picked.\n // - Too many arguments, which is not valid.\n if (nodeArguments.length === 1) {\n // An explicit duration was defined.\n node.duration = nodeArguments[0].value as number;\n\n // If an explict duration was defined then it must be a positive number.\n if (node.duration < 0) {\n throw new Error(\"a wait node must have a positive duration\");\n }\n } else if (nodeArguments.length === 2) {\n // Min and max duration bounds were defined from which a random duration will be picked.\n node.duration = [nodeArguments[0].value as number, nodeArguments[1].value as number];\n\n // A wait node must have a positive min and max duration.\n if (node.duration[0] < 0 || node.duration[1] < 0) {\n throw new Error(\"a wait node must have a positive minimum and maximum duration\");\n }\n\n // A wait node must not have a minimum duration that exceeds the maximum duration.\n if (node.duration[0] > node.duration[1]) {\n throw new Error(\"a wait node must not have a minimum duration that exceeds the maximum duration\");\n }\n } else if (nodeArguments.length > 2) {\n // An incorrect number of duration arguments were defined.\n throw new Error(\"invalid number of wait node duration arguments defined\");\n }\n }\n\n // Return the wait node definition.\n return { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) };\n}\n\n/**\n * Creates a branch node JSON definition.\n * @param tokens The tree definition tokens.\n * @param stringLiteralPlaceholders The substituted string literal placeholders.\n * @returns The branch node JSON definition.\n */\nfunction createBranchNode(\n tokens: string[],\n stringLiteralPlaceholders: StringLiteralPlaceholders\n): BranchNodeDefinition {\n // Parse any node arguments, we should have one which will be an identifier argument for the root ref.\n const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders);\n\n // We should have only a single identifer argument for a branch node, which is the root ref.\n if (nodeArguments.length !== 1 || nodeArguments[0].type !== \"identifier\") {\n throw new Error(\"expected single branch name argument\");\n }\n\n // Return the branch node definition.\n return { type: \"branch\", ref: nodeArguments[0].value };\n}\n\n/**\n * Validate a fully-populated node definition that was popped off of the tree stack.\n * @param definition The popped node to validate.\n */\nfunction validatePoppedNode(definition: AnyNodeDefinition): void {\n // Decorators MUST have a child defined.\n if (isDecoratorNode(definition) && isNullOrUndefined(definition.child)) {\n throw new Error(`a ${definition.type} node must have a single child node defined`);\n }\n\n // Composites MUST have at least one child defined.\n if (isCompositeNode(definition) && !definition.children?.length) {\n throw new Error(`a ${definition.type} node must have at least a single child node defined`);\n }\n\n // We need to make sure that lotto nodes that have weights defined have a number of weights matching the number of child nodes.\n if (definition.type === \"lotto\") {\n // Check whether a 'weights' property has been defined, if it has we expect it to be an array of weights.\n if (typeof definition.weights !== \"undefined\") {\n // Check that the weights property is an array of positive integers with an element for each child node element.\n if (definition.weights.length !== definition.children.length) {\n throw new Error(\n \"expected a number of weight arguments matching the number of child nodes for lotto node\"\n );\n }\n }\n }\n}\n", "import { RootNodeDefinition } from \"./BehaviourTreeDefinition\";\nimport { flattenDefinition, isBranchNode, isInteger } from \"./BehaviourTreeDefinitionUtilities\";\nimport { convertMDSLToJSON } from \"./mdsl/MDSLDefinitionParser\";\n\n/**\n * An object representing the result of validating a tree definition.\n */\nexport type DefinitionValidationResult = {\n /**\n * A flag defining whether validation succeeded.\n */\n succeeded: boolean;\n /**\n * A string containing the error message if validation did not succeed.\n */\n errorMessage?: string;\n /**\n * The definition as json if the validation was successful, or undefined if validation did not succeed.\n */\n json?: RootNodeDefinition[];\n};\n\n/**\n * Validates the specified behaviour tree definition in the form of JSON or MDSL, not taking any globally registered subtrees into consideration.\n * @param definition The behaviour tree definition in the form of JSON or MDSL.\n * @returns An object representing the result of validating the given tree definition.\n */\nexport function validateDefinition(definition: any): DefinitionValidationResult {\n // The definition must be defined.\n if (definition === null || typeof definition === \"undefined\") {\n return createValidationFailureResult(\"definition is null or undefined\");\n }\n\n // We are expecting a definition in one of three different forms:\n // - A string which we will assume is MDSL and we will parse this to JSON before validation.\n // - An array which we will assume is an array of root node definitions with at least one being the primary root node (no 'id' property)\n // - An object which we will assume is the primary root node and should not have an 'id' property.\n if (typeof definition === \"string\") {\n // The definition is a string which we can assume is MDSL, so attempt to validate it.\n return validateMDSLDefinition(definition);\n } else if (typeof definition === \"object\") {\n // The definition will either be an array (of root node definitions) or an object (the single primary root node definition).\n return validateJSONDefinition(definition);\n } else {\n return createValidationFailureResult(`unexpected definition type of '${typeof definition}'`);\n }\n}\n\n/**\n * Validates the specified behaviour tree definition in the form of MDSL.\n * @param definition The behaviour tree definition in the form of MDSL.\n * @returns An object representing the result of validating the given tree definition.\n */\nfunction validateMDSLDefinition(definition: string): DefinitionValidationResult {\n let rootNodeDefinitions;\n\n // The first thing the we need to do is to attempt to convert our MDSL into JSON.\n try {\n // The definition is a string which we can assume is MDSL, so attempt to parse it to a JSON definition in the form of an array of root node definitions.\n rootNodeDefinitions = convertMDSLToJSON(definition);\n } catch (exception) {\n // We failed to parse the JSON from the MDSL, this is likely to be the result of it not being a valid MDSL string.\n return createValidationFailureResult((exception as Error).message);\n }\n\n // Unpack all of the root node definitions into arrays of main ('id' defined) and sub ('id' not defined) root node definitions.\n const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === \"undefined\");\n const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === \"string\" && id.length > 0);\n\n // We should ALWAYS have exactly one root node definition without an 'id' property defined, which is out main root node definition.\n if (mainRootNodeDefinitions.length !== 1) {\n return createValidationFailureResult(\n \"expected single unnamed root node at base of definition to act as main root\"\n );\n }\n\n // We should never have duplicate 'id' properties across our sub root node definitions.\n const subRootNodeIdenitifers: string[] = [];\n for (const { id } of subRootNodeDefinitions) {\n // Have we already come across this 'id' property value?\n if (subRootNodeIdenitifers.includes(id!)) {\n return createValidationFailureResult(`multiple root nodes found with duplicate name '${id}'`);\n }\n\n subRootNodeIdenitifers.push(id!);\n }\n\n try {\n // Validate our branch -> subtree links and check for any circular dependencies, we don't care about checking for broken subtree links here.\n validateBranchSubtreeLinks(rootNodeDefinitions, false);\n } catch (exception) {\n return createValidationFailureResult((exception as Error).message);\n }\n\n // Our definition was valid!\n return {\n succeeded: true,\n json: rootNodeDefinitions\n };\n}\n\n/**\n * Validates the specified behaviour tree definition in the form of JSON.\n * @param definition The behaviour tree definition in the form of JSON.\n * @returns An object representing the result of validating the given tree definition.\n */\nexport function validateJSONDefinition(\n definition: RootNodeDefinition | RootNodeDefinition[]\n): DefinitionValidationResult {\n // The definition will either be an array (of root node definitions) or an object (the single primary root node definition).\n const rootNodeDefinitions = Array.isArray(definition) ? definition : [definition];\n\n // Iterate over our array of root nodes and call validateNode for each, passing an initial depth of 0, wrapped in a try catch to handle validation failures.\n try {\n rootNodeDefinitions.forEach((rootNodeDefinition) => validateNode(rootNodeDefinition, 0));\n } catch (error) {\n // Handle cases where we have caught a thrown Error and return a failure result with the error message.\n if (error instanceof Error) {\n return createValidationFailureResult(error.message);\n }\n\n // No idea what happened here!\n return createValidationFailureResult(`unexpected error: ${error}`);\n }\n\n // Unpack all of the root node definitions into arrays of main ('id' defined) and sub ('id' not defined) root node definitions.\n const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === \"undefined\");\n const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === \"string\" && id.length > 0);\n\n // We should ALWAYS have exactly one root node definition without an 'id' property defined, which is out main root node definition.\n if (mainRootNodeDefinitions.length !== 1) {\n return createValidationFailureResult(\n \"expected single root node without 'id' property defined to act as main root\"\n );\n }\n\n // We should never have duplicate 'id' properties across our sub root node definitions.\n const subRootNodeIdenitifers: string[] = [];\n for (const { id } of subRootNodeDefinitions) {\n // Have we already come across this 'id' property value?\n if (subRootNodeIdenitifers.includes(id!)) {\n return createValidationFailureResult(\n `multiple root nodes found with duplicate 'id' property value of '${id}'`\n );\n }\n\n subRootNodeIdenitifers.push(id!);\n }\n\n try {\n // Validate our branch -> subtree links and check for any circular dependencies, we don't care about checking for broken subtree links here.\n validateBranchSubtreeLinks(rootNodeDefinitions, false);\n } catch (exception) {\n return createValidationFailureResult((exception as Error).message);\n }\n\n // Our definition was valid!\n return {\n succeeded: true,\n json: rootNodeDefinitions\n };\n}\n\n/**\n * Validates the branch -> subtree links across all provided root node definitions.\n * This will not consider branch nodes that reference any globally registered subtrees unless includesGlobalSubtrees\n * is set to true, in which case we will also verify that there are no broken branch -> subtree links.\n * @param rootNodeDefinitions The array of root node definitions.\n * @param includesGlobalSubtrees A flag defining whether the array includes all global subtree root node definitions.\n */\nexport function validateBranchSubtreeLinks(rootNodeDefinitions: RootNodeDefinition[], includesGlobalSubtrees: boolean) {\n // Create a mapping of root node identifiers to other root nodes that they reference via branch nodes.\n // Below is an example of a mapping that includes a circular dependency (root => a => b => c => a)\n // [{ refs: [\"a\", \"b\"] }, { id: \"a\", refs: [\"b\"] }, { id: \"b\", refs: [\"c\"] }, { id: \"c\", refs: [\"a\"] }]\n const rootNodeMappings: { id: string | undefined; refs: string[] }[] = rootNodeDefinitions.map(\n (rootNodeDefinition) => ({\n id: rootNodeDefinition.id,\n refs: flattenDefinition(rootNodeDefinition)\n .filter(isBranchNode)\n .map(({ ref }) => ref)\n })\n );\n\n // A recursive function to walk through the mappings, keeping track of which root nodes we have visited in the form of a path of root node identifiers.\n const followRefs = (mapping: { id: string | undefined; refs: string[] }, path: (string | undefined)[] = []) => {\n // Have we found a circular dependency?\n if (path.includes(mapping.id)) {\n // We found a circular dependency! Get the bad path of root node identifiers.\n const badPath = [...path, mapping.id];\n\n // Create the formatted path value. [undefined, \"a\", \"b\", \"c\", \"a\"] would be formatted as \"a -> b -> c -> a\".\n const badPathFormatted = badPath.filter((element) => !!element).join(\" => \");\n\n // No need to continue, we found a circular dependency.\n throw new Error(`circular dependency found in branch node references: ${badPathFormatted}`);\n }\n\n for (const ref of mapping.refs) {\n // Find the mapping for the root node with an identifer matching the current ref.\n const subMapping = rootNodeMappings.find(({ id }) => id === ref);\n\n // We may not have a mapping for this ref, which is normal when we aren't considering all globally registered subtrees.\n if (subMapping) {\n followRefs(subMapping, [...path, mapping.id]);\n } else if (includesGlobalSubtrees) {\n // We found a reference to a root node that doesn't exist, which is a problem seeing as the root node definitons includes all globally registered subtrees.\n throw new Error(\n mapping.id\n ? `subtree '${mapping.id}' has branch node that references root node '${ref}' which has not been defined`\n : `primary tree has branch node that references root node '${ref}' which has not been defined`\n );\n }\n }\n };\n\n // Start looking for circular dependencies and broken references from the primary root node definition.\n followRefs(rootNodeMappings.find((mapping) => typeof mapping.id === \"undefined\")!);\n}\n\n/**\n * Validate an object that we expect to be a node definition.\n * @param definition An object that we expect to be a node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateNode(definition: any, depth: number): void {\n // Every node must be valid object and have a non-empty 'type' string property.\n if (typeof definition !== \"object\" || typeof definition.type !== \"string\" || definition.type.length === 0) {\n throw new Error(\n `node definition is not an object or 'type' property is not a non-empty string at depth '${depth}'`\n );\n }\n\n // If this node is at the very base of the definition then it MUST be a root node.\n if (depth === 0 && definition.type !== \"root\") {\n throw new Error(`expected root node at base of definition but got node of type '${definition.type}'`);\n }\n\n // How we validate this node definition will depend on its type.\n switch (definition.type) {\n case \"action\":\n validateActionNode(definition, depth);\n break;\n\n case \"condition\":\n validateConditionNode(definition, depth);\n break;\n\n case \"wait\":\n validateWaitNode(definition, depth);\n break;\n\n case \"branch\":\n validateBranchNode(definition, depth);\n break;\n\n case \"root\":\n validateRootNode(definition, depth);\n break;\n\n case \"succeed\":\n validateSucceedNode(definition, depth);\n break;\n\n case \"fail\":\n validateFailNode(definition, depth);\n break;\n\n case \"flip\":\n validateFlipNode(definition, depth);\n break;\n\n case \"repeat\":\n validateRepeatNode(definition, depth);\n break;\n\n case \"retry\":\n validateRetryNode(definition, depth);\n break;\n\n case \"sequence\":\n validateSequenceNode(definition, depth);\n break;\n\n case \"selector\":\n validateSelectorNode(definition, depth);\n break;\n\n case \"parallel\":\n validateParallelNode(definition, depth);\n break;\n\n case \"lotto\":\n validateLottoNode(definition, depth);\n break;\n\n default:\n throw new Error(`unexpected node type of '${definition.type}' at depth '${depth}'`);\n }\n}\n\n/**\n * Validate any attributes for a given node definition.\n * @param definition The node definition.\n * @param depth The depth of the node in the behaviour tree definition.\n */\nfunction validateNodeAttributes(definition: any, depth: number): void {\n // Validate each of the attribute types for this node.\n [\"while\", \"until\", \"entry\", \"exit\", \"step\"].forEach((attributeName) => {\n // Attempt to grab the definition for the current attribute from the node definition.\n const attributeDefinition = definition[attributeName];\n\n // All node attributes are optional, so there is nothing to do if the current attribute is not defined.\n if (typeof attributeDefinition === \"undefined\") {\n return;\n }\n\n // The attribute definition must be an object.\n if (typeof attributeDefinition !== \"object\") {\n throw new Error(\n `expected attribute '${attributeName}' to be an object for '${definition.type}' node at depth '${depth}'`\n );\n }\n\n // The 'call' property must be defined for any attribute definition.\n if (typeof attributeDefinition.call !== \"string\" || attributeDefinition.call.length === 0) {\n throw new Error(\n `expected 'call' property for attribute '${attributeName}' to be a non-empty string for '${definition.type}' node at depth '${depth}'`\n );\n }\n\n // If any node attribute arguments have been defined then they must have been defined in an array.\n if (typeof attributeDefinition.args !== \"undefined\" && !Array.isArray(attributeDefinition.args)) {\n throw new Error(\n `expected 'args' property for attribute '${attributeName}' to be an array for '${definition.type}' node at depth '${depth}'`\n );\n }\n });\n}\n\n/**\n * Validate an object that we expect to be a root node definition.\n * @param definition An object that we expect to be a root node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateRootNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"root\") {\n throw new Error(\"expected node type of 'root' for root node\");\n }\n\n // A root node cannot be the child of another node.\n if (depth > 0) {\n throw new Error(\"a root node cannot be the child of another node\");\n }\n\n // Check that, if the root node 'id' property is defined, it is a non-empty string.\n if (typeof definition.id !== \"undefined\" && (typeof definition.id !== \"string\" || definition.id.length === 0)) {\n throw new Error(\"expected non-empty string for 'id' property if defined for root node\");\n }\n\n // A root node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(\"expected property 'child' to be defined for root node\");\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a succeed node definition.\n * @param definition An object that we expect to be a succeed node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateSucceedNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"succeed\") {\n throw new Error(`expected node type of 'succeed' for succeed node at depth '${depth}'`);\n }\n\n // A succeed node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for succeed node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a fail node definition.\n * @param definition An object that we expect to be a fail node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateFailNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"fail\") {\n throw new Error(`expected node type of 'fail' for fail node at depth '${depth}'`);\n }\n\n // A fail node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for fail node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a flip node definition.\n * @param definition An object that we expect to be a flip node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateFlipNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"flip\") {\n throw new Error(`expected node type of 'flip' for flip node at depth '${depth}'`);\n }\n\n // A flip node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for flip node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a repeat node definition.\n * @param definition An object that we expect to be a repeat node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateRepeatNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"repeat\") {\n throw new Error(`expected node type of 'repeat' for repeat node at depth '${depth}'`);\n }\n\n // A repeat node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for repeat node at depth '${depth}'`);\n }\n\n // Check whether an 'iterations' property has been defined, it may not have been if this node is to repeat indefinitely.\n if (typeof definition.iterations !== \"undefined\") {\n if (Array.isArray(definition.iterations)) {\n // Check whether any elements of the array are not integer values.\n const containsNonInteger = !!definition.iterations.filter((value: unknown) => !isInteger(value)).length;\n\n // If the 'iterations' property is an array then it MUST contain two integer values.\n if (definition.iterations.length !== 2 || containsNonInteger) {\n throw new Error(\n `expected array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n\n // A repeat node must have a positive min and max iterations count if they are defined.\n if (definition.iterations[0] < 0 || definition.iterations[1] < 0) {\n throw new Error(\n `expected positive minimum and maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n\n // A repeat node must not have a minimum iterations count that exceeds the maximum iterations count.\n if (definition.iterations[0] > definition.iterations[1]) {\n throw new Error(\n `expected minimum iterations count that does not exceed the maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n } else if (isInteger(definition.iterations)) {\n // A repeat node must have a positive number of iterations if defined.\n if (definition.iterations < 0) {\n throw new Error(\n `expected positive iterations count for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n } else {\n throw new Error(\n `expected integer value or array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'`\n );\n }\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a retry node definition.\n * @param definition An object that we expect to be a retry node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateRetryNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"retry\") {\n throw new Error(`expected node type of 'retry' for retry node at depth '${depth}'`);\n }\n\n // A retry node is a decorator node, so must have a child node defined.\n if (typeof definition.child === \"undefined\") {\n throw new Error(`expected property 'child' to be defined for retry node at depth '${depth}'`);\n }\n\n // Check whether an 'attempts' property has been defined, it may not have been if this node is to retry indefinitely.\n if (typeof definition.attempts !== \"undefined\") {\n if (Array.isArray(definition.attempts)) {\n // Check whether any elements of the array are not integer values.\n const containsNonInteger = !!definition.attempts.filter((value: unknown) => !isInteger(value)).length;\n\n // If the 'attempts' property is an array then it MUST contain two integer values.\n if (definition.attempts.length !== 2 || containsNonInteger) {\n throw new Error(\n `expected array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n\n // A retry node must have a positive min and max attempts count if they are defined.\n if (definition.attempts[0] < 0 || definition.attempts[1] < 0) {\n throw new Error(\n `expected positive minimum and maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n\n // A retry node must not have a minimum attempts count that exceeds the maximum attempts count.\n if (definition.attempts[0] > definition.attempts[1]) {\n throw new Error(\n `expected minimum attempts count that does not exceed the maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n } else if (isInteger(definition.attempts)) {\n // A retry node must have a positive number of attempts if defined.\n if (definition.attempts < 0) {\n throw new Error(\n `expected positive attempts count for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n } else {\n throw new Error(\n `expected integer value or array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'`\n );\n }\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child node of this decorator node.\n validateNode(definition.child, depth + 1);\n}\n\n/**\n * Validate an object that we expect to be a branch node definition.\n * @param definition An object that we expect to be a branch node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateBranchNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"branch\") {\n throw new Error(`expected node type of 'branch' for branch node at depth '${depth}'`);\n }\n\n // Check that the branch node 'ref' property is defined and is a non-empty string.\n if (typeof definition.ref !== \"string\" || definition.ref.length === 0) {\n throw new Error(`expected non-empty string for 'ref' property for branch node at depth '${depth}'`);\n }\n\n // It is invalid to define guard attributes for a branch node as they should be defined on the referenced root node.\n [\"while\", \"until\"].forEach((attributeName) => {\n if (typeof definition[attributeName] !== \"undefined\") {\n throw new Error(\n `guards should not be defined for branch nodes but guard '${attributeName}' was defined for branch node at depth '${depth}'`\n );\n }\n });\n\n // It is invalid to define callback attributes for a branch node as they should be defined on the referenced root node.\n [\"entry\", \"exit\", \"step\"].forEach((attributeName) => {\n if (typeof definition[attributeName] !== \"undefined\") {\n throw new Error(\n `callbacks should not be defined for branch nodes but callback '${attributeName}' was defined for branch node at depth '${depth}'`\n );\n }\n });\n}\n\n/**\n * Validate an object that we expect to be a action node definition.\n * @param definition An object that we expect to be a action node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateActionNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"action\") {\n throw new Error(`expected node type of 'action' for action node at depth '${depth}'`);\n }\n\n // The 'call' property must be defined for a action node definition.\n if (typeof definition.call !== \"string\" || definition.call.length === 0) {\n throw new Error(`expected non-empty string for 'call' property of action node at depth '${depth}'`);\n }\n\n // If any action function arguments have been defined then they must have been defined in an array.\n if (typeof definition.args !== \"undefined\" && !Array.isArray(definition.args)) {\n throw new Error(`expected array for 'args' property if defined for action node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n}\n\n/**\n * Validate an object that we expect to be a condition node definition.\n * @param definition An object that we expect to be a condition node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateConditionNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"condition\") {\n throw new Error(`expected node type of 'condition' for condition node at depth '${depth}'`);\n }\n\n // The 'call' property must be defined for a condition node definition.\n if (typeof definition.call !== \"string\" || definition.call.length === 0) {\n throw new Error(`expected non-empty string for 'call' property of condition node at depth '${depth}'`);\n }\n\n // If any condition function arguments have been defined then they must have been defined in an array.\n if (typeof definition.args !== \"undefined\" && !Array.isArray(definition.args)) {\n throw new Error(`expected array for 'args' property if defined for condition node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n}\n\n/**\n * Validate an object that we expect to be a wait node definition.\n * @param definition An object that we expect to be a wait node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateWaitNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"wait\") {\n throw new Error(`expected node type of 'wait' for wait node at depth '${depth}'`);\n }\n\n // Check whether a 'duration' property has been defined, it may not have been if this node is to wait indefinitely.\n if (typeof definition.duration !== \"undefined\") {\n if (Array.isArray(definition.duration)) {\n // Check whether any elements of the array are not integer values.\n const containsNonInteger = !!definition.duration.filter((value: unknown) => !isInteger(value)).length;\n\n // If the 'duration' property is an array then it MUST contain two integer values.\n if (definition.duration.length !== 2 || containsNonInteger) {\n throw new Error(\n `expected array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n\n // A wait node must have a positive min and max duration value if they are defined.\n if (definition.duration[0] < 0 || definition.duration[1] < 0) {\n throw new Error(\n `expected positive minimum and maximum duration for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n\n // A wait node must not have a minimum duration value that exceeds the maximum duration value.\n if (definition.duration[0] > definition.duration[1]) {\n throw new Error(\n `expected minimum duration value that does not exceed the maximum duration value for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n } else if (isInteger(definition.duration)) {\n // A wait node must have a positive duration value if defined.\n if (definition.duration < 0) {\n throw new Error(\n `expected positive duration value for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n } else {\n throw new Error(\n `expected integer value or array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'`\n );\n }\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n}\n\n/**\n * Validate an object that we expect to be a sequence node definition.\n * @param definition An object that we expect to be a sequence node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateSequenceNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"sequence\") {\n throw new Error(`expected node type of 'sequence' for sequence node at depth '${depth}'`);\n }\n\n // A sequence node is a composite node, so must have a children nodes array defined.\n if (!Array.isArray(definition.children) || definition.children.length === 0) {\n throw new Error(`expected non-empty 'children' array to be defined for sequence node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child nodes of this composite node.\n definition.children.forEach((child: any) => validateNode(child, depth + 1));\n}\n\n/**\n * Validate an object that we expect to be a selector node definition.\n * @param definition An object that we expect to be a selector node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateSelectorNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"selector\") {\n throw new Error(`expected node type of 'selector' for selector node at depth '${depth}'`);\n }\n\n // A selector node is a composite node, so must have a children nodes array defined.\n if (!Array.isArray(definition.children) || definition.children.length === 0) {\n throw new Error(`expected non-empty 'children' array to be defined for selector node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child nodes of this composite node.\n definition.children.forEach((child: any) => validateNode(child, depth + 1));\n}\n\n/**\n * Validate an object that we expect to be a parallel node definition.\n * @param definition An object that we expect to be a parallel node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateParallelNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"parallel\") {\n throw new Error(`expected node type of 'parallel' for parallel node at depth '${depth}'`);\n }\n\n // A parallel node is a composite node, so must have a children nodes array defined.\n if (!Array.isArray(definition.children) || definition.children.length === 0) {\n throw new Error(`expected non-empty 'children' array to be defined for parallel node at depth '${depth}'`);\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child nodes of this composite node.\n definition.children.forEach((child: any) => validateNode(child, depth + 1));\n}\n\n/**\n * Validate an object that we expect to be a lotto node definition.\n * @param definition An object that we expect to be a lotto node definition.\n * @param depth The depth of the node in the definition tree.\n */\nfunction validateLottoNode(definition: any, depth: number): void {\n // Check that the node type is correct.\n if (definition.type !== \"lotto\") {\n throw new Error(`expected node type of 'lotto' for lotto node at depth '${depth}'`);\n }\n\n // A lotto node is a composite node, so must have a children nodes array defined.\n if (!Array.isArray(definition.children) || definition.children.length === 0) {\n throw new Error(`expected non-empty 'children' array to be defined for lotto node at depth '${depth}'`);\n }\n\n // Check whether a 'weights' property has been defined, if it has we expect it to be an array of weights.\n if (typeof definition.weights !== \"undefined\") {\n // Check that the weights property is an array of positive integers with an element for each child node element.\n if (\n !Array.isArray(definition.weights) ||\n definition.weights.length !== definition.children.length ||\n definition.weights.filter((value: unknown) => !isInteger(value)).length ||\n definition.weights.filter((value: number) => value < 0).length\n ) {\n throw new Error(\n `expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '${depth}'`\n );\n }\n }\n\n // Validate the node attributes.\n validateNodeAttributes(definition, depth);\n\n // Validate the child nodes of this composite node.\n definition.children.forEach((child: any) => validateNode(child, depth + 1));\n}\n\n/**\n * A helper function to create a failure validation result with the given error message.\n * @param errorMessage The validation failure error message.\n * @returns A failure validation result with the given error message.\n */\nfunction createValidationFailureResult(errorMessage: string): DefinitionValidationResult {\n return { succeeded: false, errorMessage };\n}\n", "import { ActionResult, Agent, GlobalFunction } from \"./Agent\";\nimport { RootNodeDefinition } from \"./BehaviourTreeDefinition\";\n\nexport type InvokerFunction = (args: any[]) => ActionResult | boolean;\n\n/**\n * A singleton used to store and lookup registered functions and subtrees.\n */\nexport default class Lookup {\n /**\n * The object holding any registered functions keyed on function name.\n */\n private static registeredFunctions: { [key: string]: GlobalFunction } = {};\n /**\n * The object holding any registered subtree root node definitions keyed on tree name.\n */\n private static registeredSubtrees: { [key: string]: RootNodeDefinition } = {};\n\n /**\n * Gets the function with the specified name.\n * @param name The name of the function.\n * @returns The function with the specified name.\n */\n public static getFunc(name: string): GlobalFunction {\n return this.registeredFunctions[name];\n }\n\n /**\n * Sets the function with the specified name for later lookup.\n * @param name The name of the function.\n * @param func The function.\n */\n public static setFunc(name: string, func: GlobalFunction): void {\n this.registeredFunctions[name] = func;\n }\n\n /**\n * Gets the function invoker for the specified agent and function name.\n * If a function with the specified name exists on the agent object then it will\n * be returned, otherwise we will then check the registered functions for a match.\n * @param agent The agent instance that this behaviour tree is modelling behaviour for.\n * @param name The function name.\n * @returns The function invoker for the specified agent and function name.\n */\n static getFuncInvoker(agent: Agent, name: string): InvokerFunction | null {\n // Check whether the agent contains the specified function.\n const agentFunction = agent[name];\n if (agentFunction && typeof agentFunction === \"function\") {\n return (args: any[]) => agentFunction.apply(agent, args);\n }\n\n // The agent does not contain the specified function but it may have been registered at some point.\n if (this.registeredFunctions[name] && typeof this.registeredFunctions[name] === \"function\") {\n const registeredFunction = this.registeredFunctions[name];\n return (args: any[]) => registeredFunction(agent, ...args);\n }\n\n // We have no function to invoke.\n return null;\n }\n\n /**\n * Gets all registered subtree root node definitions.\n */\n static getSubtrees(): { [key: string]: RootNodeDefinition } {\n return this.registeredSubtrees;\n }\n\n /**\n * Sets the subtree with the specified name for later lookup.\n * @param name The name of the subtree.\n * @param subtree The subtree.\n */\n static setSubtree(name: string, subtree: RootNodeDefinition) {\n this.registeredSubtrees[name] = subtree;\n }\n\n /**\n * Removes the registered function or subtree with the specified name.\n * @param name The name of the registered function or subtree.\n */\n static remove(name: string) {\n delete this.registeredFunctions[name];\n delete this.registeredSubtrees[name];\n }\n\n /**\n * Remove all registered functions and subtrees.\n */\n static empty() {\n this.registeredFunctions = {};\n this.registeredSubtrees = {};\n }\n}\n", "import Node from \"../../nodes/Node\";\n\n/**\n * An exception thrown when evaluating node guard path conditions and a conditions fails.\n */\nexport default class GuardUnsatisifedException extends Error {\n /**\n * @param source The node at which a guard condition failed.\n */\n constructor(private source: Node) {\n super(\"A guard path condition has failed\");\n }\n\n /**\n * Gets whether the specified node is the node at which a guard condition failed.\n * @param node The node to check against the source node.\n * @returns Whether the specified node is the node at which a guard condition failed.\n */\n isSourceNode = (node: Node) => node === this.source;\n}\n", "import { Agent } from \"../../Agent\";\nimport Guard from \"./Guard\";\nimport Node from \"../../nodes/Node\";\nimport GuardUnsatisifedException from \"./GuardUnsatisifedException\";\n\nexport type GuardPathPart = {\n node: Node;\n guards: Guard[];\n};\n\n/**\n * Represents a path of node guards along a root-to-leaf tree path.\n */\nexport default class GuardPath {\n /**\n * @param nodes An array of objects defining a node instance -> guard link, ordered by node depth.\n */\n constructor(private nodes: GuardPathPart[]) {}\n\n /**\n * Evaluate guard conditions for all guards in the tree path, moving outwards from the root.\n * @param agent The agent, required for guard evaluation.\n * @returns An evaluation results object.\n */\n evaluate = (agent: Agent) => {\n // We need to evaluate guard conditions for nodes up the tree, moving outwards from the root.\n for (const details of this.nodes) {\n // There can be multiple guards per node.\n for (const guard of details.guards) {\n // Check whether the guard condition passes, and throw an exception if not.\n if (!guard.isSatisfied(agent)) {\n throw new GuardUnsatisifedException(details.node);\n }\n }\n }\n };\n}\n", "import { BehaviourTreeOptions } from \"../BehaviourTreeOptions\";\nimport State, { AnyState } from \"../State\";\nimport { Agent } from \"../Agent\";\nimport Leaf from \"./leaf/Leaf\";\nimport Attribute from \"../attributes/Attribute\";\nimport Entry from \"../attributes/callbacks/Entry\";\nimport Exit from \"../attributes/callbacks/Exit\";\nimport Step from \"../attributes/callbacks/Step\";\nimport Guard from \"../attributes/guards/Guard\";\nimport GuardPath from \"../attributes/guards/GuardPath\";\nimport GuardUnsatisifedException from \"../attributes/guards/GuardUnsatisifedException\";\n\n/**\n * A base node.\n */\nexport default abstract class Node {\n /**\n * The node uid.\n */\n private readonly uid: string = createNodeUid();\n /**\n * The node state.\n */\n private state: AnyState = State.READY;\n /**\n * The guard path to evaluate as part of a node update.\n */\n private guardPath: GuardPath | undefined;\n\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param args The node argument definitions.\n */\n constructor(private type: string, private attributes: Attribute[], private args: any[]) {}\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected abstract onUpdate(agent: Agent, options: BehaviourTreeOptions): void;\n\n /**\n * Gets the name of the node.\n */\n public abstract getName(): string;\n\n /**\n * Gets whether this node is a leaf node.\n */\n public abstract isLeafNode: () => this is Leaf;\n\n /**\n * Gets/Sets the state of the node.\n */\n getState = (): AnyState => this.state;\n setState = (value: AnyState): void => {\n this.state = value;\n };\n\n /**\n * Gets the unique id of the node.\n */\n getUid = () => this.uid;\n\n /**\n * Gets the type of the node.\n */\n getType = () => this.type;\n\n /**\n * Gets the node attributes.\n */\n getAttributes = () => this.attributes;\n\n /**\n * Gets the node arguments.\n */\n getArguments = () => this.args;\n\n /**\n * Gets the node attribute with the specified type, or null if it does not exist.\n */\n getAttribute(type: \"entry\" | \"ENTRY\"): Entry;\n getAttribute(type: \"exit\" | \"EXIT\"): Exit;\n getAttribute(type: \"step\" | \"STEP\"): Step;\n getAttribute(type: string): Attribute {\n return (\n this.getAttributes().filter((decorator) => decorator.type.toUpperCase() === type.toUpperCase())[0] || null\n );\n }\n\n /**\n * Gets the node attributes.\n */\n getGuardAttributes = (): Guard[] => this.getAttributes().filter((decorator) => decorator.isGuard()) as Guard[];\n\n /**\n * Sets the guard path to evaluate as part of a node update.\n */\n setGuardPath = (value: GuardPath) => (this.guardPath = value);\n\n /**\n * Gets whether a guard path is assigned to this node.\n */\n hasGuardPath = () => !!this.guardPath;\n\n /**\n * Gets whether this node is in the specified state.\n * @param value The value to compare to the node state.\n */\n public is(value: AnyState): boolean {\n return this.state === value;\n }\n\n /**\n * Reset the state of the node.\n */\n public reset(): void {\n this.setState(State.READY);\n }\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n public abort(agent: Agent): void {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n }\n\n /**\n * Update the node.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n * @returns The result of the update.\n */\n public update(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is already in a 'SUCCEEDED' or 'FAILED' state then there is nothing to do.\n if (this.is(State.SUCCEEDED) || this.is(State.FAILED)) {\n return;\n }\n\n try {\n // Evaluate all of the guard path conditions for the current tree path.\n this.guardPath!.evaluate(agent);\n\n // If this node is in the READY state then call the ENTRY for this node if it exists.\n if (this.is(State.READY)) {\n this.getAttribute(\"entry\")?.callAgentFunction(agent);\n }\n\n this.getAttribute(\"step\")?.callAgentFunction(agent);\n\n // Do the actual update.\n this.onUpdate(agent, options);\n\n // If this node is now in a 'SUCCEEDED' or 'FAILED' state then call the EXIT for this node if it exists.\n if (this.is(State.SUCCEEDED) || this.is(State.FAILED)) {\n this.getAttribute(\"exit\")?.callAgentFunction(agent, this.is(State.SUCCEEDED), false);\n }\n } catch (error) {\n // If the error is a GuardUnsatisfiedException then we need to determine if this node is the source.\n if (error instanceof GuardUnsatisifedException && error.isSourceNode(this)) {\n // Abort the current node.\n this.abort(agent);\n\n // Any node that is the source of an abort will be a failed node.\n this.setState(State.FAILED);\n } else {\n throw error;\n }\n }\n }\n}\n\n/**\n * Create a randomly generated node uid.\n * @returns A randomly generated node uid.\n */\nfunction createNodeUid(): string {\n var S4 = function () {\n return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);\n };\n return S4() + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + \"-\" + S4() + S4() + S4();\n}\n", "import Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A composite node that wraps child nodes.\n */\nexport default abstract class Composite extends Node {\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(type: string, attributes: Attribute[], protected children: Node[]) {\n super(type, attributes, []);\n }\n\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => false;\n\n /**\n * Gets the children of this node.\n */\n getChildren = () => this.children;\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the state of any child nodes.\n this.getChildren().forEach((child) => child.reset());\n };\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n abort = (agent: Agent) => {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Abort any child nodes.\n this.getChildren().forEach((child) => child.abort(agent));\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n };\n}\n", "import Composite from \"./Composite\";\nimport State from \"../../State\";\nimport Node from \"../Node\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A PARALLEL node.\n * The child nodes are executed concurrently until one fails or all succeed.\n */\nexport default class Parallel extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], children: Node[]) {\n super(\"parallel\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Keep a count of the number of succeeded child nodes.\n let succeededCount = 0;\n\n let hasChildFailed = false;\n\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then we should move on to the next child.\n if (child.getState() === State.SUCCEEDED) {\n // The child node has succeeded, keep track of this to determine if all children have.\n succeededCount++;\n\n // The child node succeeded, but we have not finished checking every child node yet.\n continue;\n }\n\n // If the current child has a state of 'FAILED' then this node is also a 'FAILED' node.\n if (child.getState() === State.FAILED) {\n hasChildFailed = true;\n\n // There is no need to check the rest of the children.\n break;\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() !== State.RUNNING) {\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n if (hasChildFailed) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // Abort every running child.\n for (const child of this.children) {\n if (child.getState() === State.RUNNING) {\n child.abort(agent);\n }\n }\n } else {\n // If all children have succeeded then this node has also succeeded, otherwise it is still running.\n this.setState(succeededCount === this.children.length ? State.SUCCEEDED : State.RUNNING);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"PARALLEL\";\n}\n", "import Composite from \"./Composite\";\nimport Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A SELECTOR node.\n * The child nodes are executed in sequence until one succeeds or all fail.\n */\nexport default class Selector extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], protected children: Node[]) {\n super(\"selector\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then this node is also a 'SUCCEEDED' node.\n if (child.getState() === State.SUCCEEDED) {\n // This node is a 'SUCCEEDED' node.\n this.setState(State.SUCCEEDED);\n\n // There is no need to check the rest of the selector nodes.\n return;\n }\n\n // If the current child has a state of 'FAILED' then we should move on to the next child.\n if (child.getState() === State.FAILED) {\n // Find out if the current child is the last one in the selector.\n // If it is then this sequence node has also failed.\n if (this.children.indexOf(child) === this.children.length - 1) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // There is no need to check the rest of the selector as we have completed it.\n return;\n } else {\n // The child node failed, try the next one.\n continue;\n }\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() === State.RUNNING) {\n // This node is a 'RUNNING' node.\n this.setState(State.RUNNING);\n\n // There is no need to check the rest of the selector as the current child is still running.\n return;\n }\n\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SELECTOR\";\n}\n", "import Composite from \"./Composite\";\nimport Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A SEQUENCE node.\n * The child nodes are executed in sequence until one fails or all succeed.\n */\nexport default class Sequence extends Composite {\n /**\n * @param attributes The node attributes.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], protected children: Node[]) {\n super(\"sequence\", attributes, children);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Iterate over all of the children of this node.\n for (const child of this.children) {\n // If the child has never been updated or is running then we will need to update it now.\n if (child.getState() === State.READY || child.getState() === State.RUNNING) {\n // Update the child of this node.\n child.update(agent, options);\n }\n\n // If the current child has a state of 'SUCCEEDED' then we should move on to the next child.\n if (child.getState() === State.SUCCEEDED) {\n // Find out if the current child is the last one in the sequence.\n // If it is then this sequence node has also succeeded.\n if (this.children.indexOf(child) === this.children.length - 1) {\n // This node is a 'SUCCEEDED' node.\n this.setState(State.SUCCEEDED);\n\n // There is no need to check the rest of the sequence as we have completed it.\n return;\n } else {\n // The child node succeeded, but we have not finished the sequence yet.\n continue;\n }\n }\n\n // If the current child has a state of 'FAILED' then this node is also a 'FAILED' node.\n if (child.getState() === State.FAILED) {\n // This node is a 'FAILED' node.\n this.setState(State.FAILED);\n\n // There is no need to check the rest of the sequence.\n return;\n }\n\n // The node should be in the 'RUNNING' state.\n if (child.getState() === State.RUNNING) {\n // This node is a 'RUNNING' node.\n this.setState(State.RUNNING);\n\n // There is no need to check the rest of the sequence as the current child is still running.\n return;\n }\n\n // The child node was not in an expected state.\n throw new Error(\"child node was not in an expected state.\");\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SEQUENCE\";\n}\n", "import createLotto from \"lotto-draw\";\n\nimport Node from \"../Node\";\nimport Composite from \"./Composite\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A LOTTO node.\n * A winning child is picked on the initial update of this node, based on ticket weighting.\n * The state of this node will match the state of the winning child.\n */\nexport default class Lotto extends Composite {\n /**\n * @param attributes The node attributes.\n * @param weights The child node weights.\n * @param children The child nodes.\n */\n constructor(attributes: Attribute[], private weights: number[] | undefined, children: Node[]) {\n super(\"lotto\", attributes, children);\n }\n\n /**\n * The child node selected to be the active one.\n */\n private selectedChild: Node | undefined;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to pick a winning child node.\n if (this.is(State.READY)) {\n // Create a lotto draw with which to randomly pick a child node to become the active one.\n const lottoDraw = createLotto({\n // Hook up the optional 'random' behaviour tree function option to the one used by 'lotto-draw'.\n random: options.random,\n // Pass in each child node as a participant in the lotto draw with their respective ticket count.\n participants: this.children.map((child, index) => [child, this.weights?.[index] || 1])\n });\n\n // Randomly pick a child based on ticket weighting, this will become the active child for this composite node.\n this.selectedChild = lottoDraw.draw() || undefined;\n }\n\n // If something went wrong and we don't have an active child then we should throw an error.\n if (!this.selectedChild) {\n throw new Error(\"failed to update lotto node as it has no active child\");\n }\n\n // If the selected child has never been updated or is running then we will need to update it now.\n if (this.selectedChild.getState() === State.READY || this.selectedChild.getState() === State.RUNNING) {\n this.selectedChild.update(agent, options);\n }\n\n // The state of the lotto node is the state of its selected child.\n this.setState(this.selectedChild.getState());\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => (this.weights ? `LOTTO [${this.weights.join(\",\")}]` : \"LOTTO\");\n}\n", "import Node from \"../Node\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A decorator node that wraps a single child node.\n */\nexport default abstract class Decorator extends Node {\n /**\n * @param type The node type.\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(type: string, attributes: Attribute[], protected child: Node) {\n super(type, attributes, []);\n }\n\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => false;\n\n /**\n * Gets the children of this node.\n */\n getChildren = () => [this.child];\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the state of the child node.\n this.child.reset();\n };\n\n /**\n * Abort the running of this node.\n * @param agent The agent.\n */\n abort = (agent: Agent) => {\n // There is nothing to do if this node is not in the running state.\n if (!this.is(State.RUNNING)) {\n return;\n }\n\n // Abort the child node.\n this.child.abort(agent);\n\n // Reset the state of this node.\n this.reset();\n\n this.getAttribute(\"exit\")?.callAgentFunction(agent, false, true);\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Fail node.\n * This node wraps a single child and will always move to the 'FAILED' state when the child moves to a 'SUCCEEDED' or 'FAILED' state.\n */\nexport default class Fail extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"fail\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n case State.FAILED:\n this.setState(State.FAILED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"FAIL\";\n}\n", "import Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport Node from \"../Node\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Flip node.\n * This node wraps a single child and will flip the state of the child state.\n */\nexport default class Flip extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"flip\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n this.setState(State.FAILED);\n break;\n\n case State.FAILED:\n this.setState(State.SUCCEEDED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"FLIP\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A REPEAT node.\n * The node has a single child which can have:\n * -- A number of iterations for which to repeat the child node.\n * -- An infinite repeat loop if neither an iteration count or a condition function is defined.\n * The REPEAT node will stop and have a 'FAILED' state if its child is ever in a 'FAILED' state after an update.\n * The REPEAT node will attempt to move on to the next iteration if its child is ever in a 'SUCCEEDED' state.\n */\nexport default class Repeat extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param iterations The number of iterations to repeat the child node.\n * @param iterationsMin The minimum possible number of iterations to repeat the child node.\n * @param iterationsMax The maximum possible number of iterations to repeat the child node.\n * @param child The child node.\n */\n constructor(\n attributes: Attribute[],\n private iterations: number | null,\n private iterationsMin: number | null,\n private iterationsMax: number | null,\n child: Node\n ) {\n super(\"repeat\", attributes, child);\n }\n\n /**\n * The number of target iterations to make.\n */\n private targetIterationCount: number | null = null;\n\n /**\n * The current iteration count.\n */\n private currentIterationCount: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to reset the child and the target iteration count.\n if (this.is(State.READY)) {\n // Reset the child node.\n this.child.reset();\n\n // Reset the current iteration count.\n this.currentIterationCount = 0;\n\n // Set the target iteration count.\n this.setTargetIterationCount(options);\n }\n\n // Do a check to see if we can iterate. If we can then this node will move into the 'RUNNING' state.\n // If we cannot iterate then we have hit our target iteration count, which means that the node has succeeded.\n if (this.canIterate()) {\n // This node is in the running state and can do its initial iteration.\n this.setState(State.RUNNING);\n\n // We may have already completed an iteration, meaning that the child node will be in the SUCCEEDED state.\n // If this is the case then we will have to reset the child node now.\n if (this.child.getState() === State.SUCCEEDED) {\n this.child.reset();\n }\n\n // Update the child of this node.\n this.child.update(agent, options);\n\n // If the child moved into the FAILED state when we updated it then there is nothing left to do and this node has also failed.\n // If it has moved into the SUCCEEDED state then we have completed the current iteration.\n if (this.child.getState() === State.FAILED) {\n // The child has failed, meaning that this node has failed.\n this.setState(State.FAILED);\n\n return;\n } else if (this.child.getState() === State.SUCCEEDED) {\n // We have completed an iteration.\n this.currentIterationCount += 1;\n }\n } else {\n // This node is in the 'SUCCEEDED' state as we cannot iterate any more.\n this.setState(State.SUCCEEDED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.iterations !== null) {\n return `REPEAT ${this.iterations}x`;\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n return `REPEAT ${this.iterationsMin}x-${this.iterationsMax}x`;\n } else {\n return \"REPEAT\";\n }\n };\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the current iteration count.\n this.currentIterationCount = 0;\n\n // Reset the child node.\n this.child.reset();\n };\n\n /**\n * Gets whether an iteration can be made.\n * @returns Whether an iteration can be made.\n */\n private canIterate = () => {\n if (this.targetIterationCount !== null) {\n // We can iterate as long as we have not reached our target iteration count.\n return this.currentIterationCount < this.targetIterationCount;\n }\n\n // If neither an iteration count or a condition function were defined then we can iterate indefinitely.\n return true;\n };\n\n /**\n * Sets the target iteration count.\n * @param options The behaviour tree options object.\n */\n private setTargetIterationCount = (options: BehaviourTreeOptions) => {\n // Are we dealing with an explicit iteration count or will we be randomly picking a iteration count between the min and max iteration count.\n if (this.iterations !== null) {\n this.targetIterationCount = this.iterations;\n } else if (this.iterationsMin !== null && this.iterationsMax !== null) {\n // We will be picking a random iteration count between a min and max iteration count, if the optional 'random'\n // behaviour tree function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random iteration count between a min and max iteration count.\n this.targetIterationCount = Math.floor(\n random() * (this.iterationsMax - this.iterationsMin + 1) + this.iterationsMin\n );\n } else {\n this.targetIterationCount = null;\n }\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A RETRY node.\n * The node has a single child which can have:\n * -- A number of iterations for which to repeat the child node.\n * -- An infinite repeat loop if neither an iteration count or a condition function is defined.\n * The RETRY node will stop and have a 'SUCCEEDED' state if its child is ever in a 'SUCCEEDED' state after an update.\n * The RETRY node will attempt to move on to the next iteration if its child is ever in a 'FAILED' state.\n */\nexport default class Retry extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param attempts The number of attempts to retry the child node.\n * @param attemptsMin The minimum possible number of attempts to retry the child node.\n * @param attemptsMax The maximum possible number of attempts to retry the child node.\n * @param child The child node.\n */\n constructor(\n attributes: Attribute[],\n private attempts: number | null,\n private attemptsMin: number | null,\n private attemptsMax: number | null,\n child: Node\n ) {\n super(\"retry\", attributes, child);\n }\n\n /**\n * The number of target attempts to make.\n */\n private targetAttemptCount: number | null = null;\n\n /**\n * The current attempt count.\n */\n private currentAttemptCount: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to reset the child and the target attempt count.\n if (this.is(State.READY)) {\n // Reset the child node.\n this.child.reset();\n\n // Reset the current attempt count.\n this.currentAttemptCount = 0;\n\n // Set the target attempt count.\n this.setTargetAttemptCount(options);\n }\n\n // Do a check to see if we can attempt. If we can then this node will move into the 'RUNNING' state.\n // If we cannot attempt then we have hit our target attempt count, which means that the node has succeeded.\n if (this.canAttempt()) {\n // This node is in the running state and can do its initial attempt.\n this.setState(State.RUNNING);\n\n // We may have already completed an attempt, meaning that the child node will be in the FAILED state.\n // If this is the case then we will have to reset the child node now.\n if (this.child.getState() === State.FAILED) {\n this.child.reset();\n }\n\n // Update the child of this node.\n this.child.update(agent, options);\n\n // If the child moved into the SUCCEEDED state when we updated it then there is nothing left to do and this node has also succeeded.\n // If it has moved into the FAILED state then we have completed the current attempt.\n if (this.child.getState() === State.SUCCEEDED) {\n // The child has succeeded, meaning that this node has succeeded.\n this.setState(State.SUCCEEDED);\n\n return;\n } else if (this.child.getState() === State.FAILED) {\n // We have completed an attempt.\n this.currentAttemptCount += 1;\n }\n } else {\n // This node is in the 'FAILED' state as we cannot iterate any more.\n this.setState(State.FAILED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.attempts !== null) {\n return `RETRY ${this.attempts}x`;\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n return `RETRY ${this.attemptsMin}x-${this.attemptsMax}x`;\n } else {\n return \"RETRY\";\n }\n };\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // Reset the current attempt count.\n this.currentAttemptCount = 0;\n\n // Reset the child node.\n this.child.reset();\n };\n\n /**\n * Gets whether an attempt can be made.\n * @returns Whether an attempt can be made.\n */\n canAttempt = () => {\n if (this.targetAttemptCount !== null) {\n // We can attempt as long as we have not reached our target attempt count.\n return this.currentAttemptCount < this.targetAttemptCount;\n }\n\n // If neither an attempt count or a condition function were defined then we can attempt indefinitely.\n return true;\n };\n\n /**\n * Sets the target attempt count.\n * @param options The behaviour tree options object.\n */\n setTargetAttemptCount = (options: BehaviourTreeOptions) => {\n // Are we dealing with an explicit attempt count or will we be randomly picking an attempt count between the min and max attempt count.\n if (this.attempts !== null) {\n this.targetAttemptCount = this.attempts;\n } else if (this.attemptsMin !== null && this.attemptsMax !== null) {\n // We will be picking a random attempt count between a min and max attempt count, if the optional 'random'\n // behaviour tree function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random attempt count between a min and max attempt count.\n this.targetAttemptCount = Math.floor(\n random() * (this.attemptsMax - this.attemptsMin + 1) + this.attemptsMin\n );\n } else {\n this.targetAttemptCount = null;\n }\n };\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Root node.\n * The root node will have a single child.\n */\nexport default class Root extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"root\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n // Update the child of this node.\n this.child.update(agent, options);\n }\n\n // The state of the root node is the state of its child.\n this.setState(this.child.getState());\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"ROOT\";\n}\n", "import Node from \"../Node\";\nimport Decorator from \"./Decorator\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A Succeed node.\n * This node wraps a single child and will always move to the 'SUCCEEDED' state when the child moves to a 'SUCCEEDED' or 'FAILED' state.\n */\nexport default class Succeed extends Decorator {\n /**\n * @param attributes The node attributes.\n * @param child The child node.\n */\n constructor(attributes: Attribute[], child: Node) {\n super(\"succeed\", attributes, child);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the child has never been updated or is running then we will need to update it now.\n if (this.child.getState() === State.READY || this.child.getState() === State.RUNNING) {\n this.child.update(agent, options);\n }\n\n // The state of this node will depend in the state of its child.\n switch (this.child.getState()) {\n case State.RUNNING:\n this.setState(State.RUNNING);\n break;\n\n case State.SUCCEEDED:\n case State.FAILED:\n this.setState(State.SUCCEEDED);\n break;\n\n default:\n this.setState(State.READY);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => \"SUCCEED\";\n}\n", "import Node from \"../Node\";\n\n/**\n * A leaf node.\n */\nexport default abstract class Leaf extends Node {\n /**\n * Gets whether this node is a leaf node.\n */\n isLeafNode = () => true;\n}\n", "import { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\nimport State, { CompleteState } from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Leaf from \"./Leaf\";\nimport Lookup from \"../../Lookup\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * The type representing a resolved/rejected update promise.\n */\ntype UpdatePromiseResult = {\n /**\n * Whether the promise was resolved rather than rejected.\n */\n isResolved: boolean;\n\n /**\n * The promise resolved value or rejection reason.\n */\n value: any;\n};\n\n/**\n * An Action leaf node.\n * This represents an immediate or ongoing state of behaviour.\n */\nexport default class Action extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param actionName The action name.\n * @param actionArguments The array of action argument definitions.\n */\n constructor(attributes: Attribute[], private actionName: string, private actionArguments: any[]) {\n super(\"action\", attributes, actionArguments);\n }\n\n /**\n * Whether there is a pending update promise.\n */\n private isUsingUpdatePromise = false;\n\n /**\n * The finished state result of an update promise.\n */\n private updatePromiseResult: UpdatePromiseResult | null = null;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If the result of this action depends on an update promise then there is nothing to do until it settles.\n if (this.isUsingUpdatePromise) {\n // Are we still waiting for our update promise to settle?\n if (!this.updatePromiseResult) {\n return;\n }\n\n const { isResolved, value } = this.updatePromiseResult;\n\n // Our update promise settled, was it resolved or rejected?\n if (isResolved) {\n // Our promise resolved so check to make sure the result is a valid finished state.\n if (value !== State.SUCCEEDED && value !== State.FAILED) {\n throw new Error(\n \"action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned\"\n );\n }\n\n // Set the state of this node to match the state returned by the promise.\n this.setState(value);\n\n return;\n } else {\n // The promise was rejected, which isn't great.\n throw new Error(`action function '${this.actionName}' promise rejected with '${value}'`);\n }\n }\n\n // Attempt to get the invoker for the action function.\n const actionFuncInvoker = Lookup.getFuncInvoker(agent, this.actionName);\n\n // The action function should be defined.\n if (actionFuncInvoker === null) {\n throw new Error(\n `cannot update action node as the action '${this.actionName}' function is not defined on the agent and has not been registered`\n );\n }\n\n let actionFunctionResult;\n\n try {\n // Call the action function, the result of which may be:\n // - The finished state of this action node.\n // - A promise to return a finished node state.\n // - Undefined if the node should remain in the running state.\n actionFunctionResult = actionFuncInvoker(this.actionArguments) as CompleteState | Promise;\n } catch (error) {\n // An uncaught error was thrown.\n if (error instanceof Error) {\n throw new Error(`action function '${this.actionName}' threw: ${error.stack}`);\n } else {\n throw new Error(`action function '${this.actionName}' threw: ${error}`);\n }\n }\n\n if (actionFunctionResult instanceof Promise) {\n actionFunctionResult.then(\n (result) => {\n // If 'isUpdatePromisePending' is not set then the promise was cleared as it was resolving, probably via an abort of reset.\n if (!this.isUsingUpdatePromise) {\n return;\n }\n\n // Set the resolved update promise result so that it can be handled on the next update of this node.\n this.updatePromiseResult = {\n isResolved: true,\n value: result\n };\n },\n (reason) => {\n // If 'isUpdatePromisePending' is not set then the promise was cleared as it was resolving, probably via an abort or reset.\n if (!this.isUsingUpdatePromise) {\n return;\n }\n\n // Set the rejected update promise result so that it can be handled on the next update of this node.\n this.updatePromiseResult = {\n isResolved: false,\n value: reason\n };\n }\n );\n\n // This node will be in the 'RUNNING' state until the update promise resolves.\n this.setState(State.RUNNING);\n\n // We are now waiting for the promise returned by the use to resolve before we know what state this node is in.\n this.isUsingUpdatePromise = true;\n } else {\n // Validate the returned value.\n this.validateUpdateResult(actionFunctionResult);\n\n // Set the state of this node, this may be undefined, which just means that the node is still in the 'RUNNING' state.\n this.setState(actionFunctionResult || State.RUNNING);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => this.actionName;\n\n /**\n * Reset the state of the node.\n */\n reset = () => {\n // Reset the state of this node.\n this.setState(State.READY);\n\n // There is no longer an update promise that we care about.\n this.isUsingUpdatePromise = false;\n this.updatePromiseResult = null;\n };\n\n /**\n * Validate the result of an update function call.\n * @param result The result of an update function call.\n */\n private validateUpdateResult = (result: CompleteState | State.RUNNING) => {\n switch (result) {\n case State.SUCCEEDED:\n case State.FAILED:\n case State.RUNNING:\n case undefined:\n return;\n default:\n throw new Error(\n `expected action function '${this.actionName}' to return an optional State.SUCCEEDED or State.FAILED value but returned '${result}'`\n );\n }\n };\n}\n", "import { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\nimport State from \"../../State\";\nimport { Agent } from \"../../Agent\";\nimport Leaf from \"./Leaf\";\nimport Lookup from \"../../Lookup\";\nimport Attribute from \"../../attributes/Attribute\";\n\n/**\n * A Condition leaf node.\n * This will succeed or fail immediately based on an agent predicate, without moving to the 'RUNNING' state.\n */\nexport default class Condition extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param conditionName The name of the condition function.\n * @param conditionArguments The array of condition argument definitions.\n */\n constructor(attributes: Attribute[], private conditionName: string, private conditionArguments: any[]) {\n super(\"condition\", attributes, conditionArguments);\n }\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.conditionName);\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot update condition node as the condition '${this.conditionName}' function is not defined on the agent and has not been registered`\n );\n }\n\n let conditionFunctionResult;\n\n try {\n // Call the condition function to determine the state of this node, the result of which should be a boolean.\n conditionFunctionResult = conditionFuncInvoker(this.conditionArguments);\n } catch (error) {\n // An uncaught error was thrown.\n if (error instanceof Error) {\n throw new Error(`condition function '${this.conditionName}' threw: ${error.stack}`);\n } else {\n throw new Error(`condition function '${this.conditionName}' threw: ${error}`);\n }\n }\n\n // The result of calling the condition function must be a boolean value.\n if (typeof conditionFunctionResult !== \"boolean\") {\n throw new Error(\n `expected condition function '${this.conditionName}' to return a boolean but returned '${conditionFunctionResult}'`\n );\n }\n\n // Set the state of this node based on the result of calling the condition function.\n this.setState(!!conditionFunctionResult ? State.SUCCEEDED : State.FAILED);\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => this.conditionName;\n}\n", "import Leaf from \"./Leaf\";\nimport State from \"../../State\";\nimport Attribute from \"../../attributes/Attribute\";\nimport { Agent } from \"../../Agent\";\nimport { BehaviourTreeOptions } from \"../../BehaviourTreeOptions\";\n\n/**\n * A WAIT node.\n * The state of this node will change to SUCCEEDED after a duration of time\n */\nexport default class Wait extends Leaf {\n /**\n * @param attributes The node attributes.\n * @param duration The duration that this node will wait to succeed in milliseconds.\n * @param durationMin The minimum possible duration in milliseconds that this node will wait to succeed.\n * @param durationMax The maximum possible duration in milliseconds that this node will wait to succeed.\n */\n constructor(\n attributes: Attribute[],\n private duration: number | null,\n private durationMin: number | null,\n private durationMax: number | null\n ) {\n super(\"wait\", attributes, []);\n }\n\n /**\n * The time in milliseconds at which this node was first updated.\n */\n private initialUpdateTime: number = 0;\n\n /**\n * The total duration in milliseconds that this node will be waiting for.\n */\n private totalDuration: number | null = null;\n\n /**\n * The duration in milliseconds that this node has been waiting for.\n */\n private waitedDuration: number = 0;\n\n /**\n * Called when the node is being updated.\n * @param agent The agent.\n * @param options The behaviour tree options object.\n */\n protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void {\n // If this node is in the READY state then we need to set the initial update time.\n if (this.is(State.READY)) {\n // Set the initial update time.\n this.initialUpdateTime = new Date().getTime();\n\n // Set the initial waited duration.\n this.waitedDuration = 0;\n\n // Are we dealing with an explicit duration or will we be randomly picking a duration between the min and max duration.\n if (this.duration !== null) {\n this.totalDuration = this.duration;\n } else if (this.durationMin !== null && this.durationMax !== null) {\n // We will be picking a random duration between a min and max duration, if the optional 'random' behaviour tree\n // function option is defined then we will be using that, otherwise we will fall back to using Math.random.\n const random = typeof options.random === \"function\" ? options.random : Math.random;\n\n // Pick a random duration between a min and max duration.\n this.totalDuration = Math.floor(\n random() * (this.durationMax - this.durationMin + 1) + this.durationMin\n );\n } else {\n this.totalDuration = null;\n }\n\n // The node is now running until we finish waiting.\n this.setState(State.RUNNING);\n }\n\n // If we have no total duration then this wait node will wait indefinitely until it is aborted.\n if (this.totalDuration === null) {\n return;\n }\n\n // If we have a 'getDeltaTime' function defined as part of our options then we will use it to figure out how long we have waited for.\n if (typeof options.getDeltaTime === \"function\") {\n // Get the delta time.\n const deltaTime = options.getDeltaTime();\n\n // Our delta time must be a valid number and cannot be NaN.\n if (typeof deltaTime !== \"number\" || isNaN(deltaTime)) {\n throw new Error(\"The delta time must be a valid number and not NaN.\");\n }\n\n // Update the amount of time that this node has been waiting for based on the delta time.\n this.waitedDuration += deltaTime * 1000;\n } else {\n // We are not using a delta time, so we will just work out hom much time has passed since the first update.\n this.waitedDuration = new Date().getTime() - this.initialUpdateTime;\n }\n\n // Have we waited long enough?\n if (this.waitedDuration >= this.totalDuration) {\n // We have finished waiting!\n this.setState(State.SUCCEEDED);\n }\n }\n\n /**\n * Gets the name of the node.\n */\n getName = () => {\n if (this.duration !== null) {\n return `WAIT ${this.duration}ms`;\n } else if (this.durationMin !== null && this.durationMax !== null) {\n return `WAIT ${this.durationMin}ms-${this.durationMax}ms`;\n } else {\n return \"WAIT\";\n }\n };\n}\n", "import Guard from \"./guards/Guard\";\n\nexport type AttributeDetails = {\n /** The attribute type. */\n type: string;\n\n /** The attribute arguments. */\n args: any[];\n};\n\n/**\n * A base node attribute.\n */\nexport default abstract class Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of attribute arguments.\n */\n constructor(public type: string, public args: any[]) {}\n\n /**\n * Gets the attribute details.\n */\n abstract getDetails(): TAttributeDetails;\n\n /**\n * Gets whether this attribute is a guard.\n */\n abstract isGuard: () => this is Guard;\n}\n", "import { Agent } from \"../../Agent\";\nimport Attribute, { AttributeDetails } from \"../Attribute\";\n\nexport type GuardAttributeDetails = {\n /** The name of the condition function that determines whether the guard is satisfied. */\n condition: string;\n} & AttributeDetails;\n\n/**\n * A base node guard attribute.\n */\nexport default abstract class Guard extends Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of decorator argument definitions.\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n */\n constructor(type: string, args: any[], private condition: string) {\n super(type, args);\n }\n\n /**\n * Gets the name of the condition function that determines whether the guard is satisfied.\n */\n getCondition = () => this.condition;\n\n /**\n * Gets whether this attribute is a guard.\n */\n isGuard = () => true;\n\n /**\n * Gets the attribute details.\n */\n getDetails(): GuardAttributeDetails {\n return {\n type: this.type,\n args: this.args,\n condition: this.getCondition()\n };\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n abstract isSatisfied(agent: Agent): boolean;\n}\n", "import Guard from \"./Guard\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * A WHILE guard which is satisfied as long as the given condition remains true.\n */\nexport default class While extends Guard {\n /**\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n * @param args The array of decorator argument definitions.\n */\n constructor(condition: string, args: any[]) {\n super(\"while\", args, condition);\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n isSatisfied = (agent: Agent) => {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.getCondition());\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered`\n );\n }\n\n let conditionFunctionResult;\n\n try {\n // Call the guard condition function to determine the state of this node, the result of which should be a boolean.\n conditionFunctionResult = conditionFuncInvoker(this.args);\n } catch (error) {\n // An uncaught error was thrown.\n if (error instanceof Error) {\n throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`);\n } else {\n throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`);\n }\n }\n\n // The result of calling the guard condition function must be a boolean value.\n if (typeof conditionFunctionResult !== \"boolean\") {\n throw new Error(\n `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'`\n );\n }\n\n // Return whether this guard is satisfied.\n return conditionFunctionResult;\n };\n}\n", "import Guard from \"./Guard\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * An UNTIL guard which is satisfied as long as the given condition remains false.\n */\nexport default class Until extends Guard {\n /**\n * @param condition The name of the condition function that determines whether the guard is satisfied.\n * @param args The array of decorator argument definitions.\n */\n constructor(condition: string, args: any[]) {\n super(\"until\", args, condition);\n }\n\n /**\n * Gets whether the guard is satisfied.\n * @param agent The agent.\n * @returns Whether the guard is satisfied.\n */\n isSatisfied = (agent: Agent) => {\n // Attempt to get the invoker for the condition function.\n const conditionFuncInvoker = Lookup.getFuncInvoker(agent, this.getCondition());\n\n // The condition function should be defined.\n if (conditionFuncInvoker === null) {\n throw new Error(\n `cannot evaluate node guard as the condition '${this.getCondition()}' function is not defined on the agent and has not been registered`\n );\n }\n\n let conditionFunctionResult;\n\n try {\n // Call the guard condition function to determine the state of this node, the result of which should be a boolean.\n conditionFunctionResult = conditionFuncInvoker(this.args);\n } catch (error) {\n // An uncaught error was thrown.\n if (error instanceof Error) {\n throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`);\n } else {\n throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`);\n }\n }\n\n // The result of calling the guard condition function must be a boolean value.\n if (typeof conditionFunctionResult !== \"boolean\") {\n throw new Error(\n `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'`\n );\n }\n\n // Return whether this guard is satisfied.\n return !conditionFunctionResult;\n };\n}\n", "import { Agent } from \"../../Agent\";\nimport Attribute, { AttributeDetails } from \"../Attribute\";\n\nexport type CallbackAttributeDetails = {\n /** The name of the agent function that is called. */\n functionName: string;\n} & AttributeDetails;\n\n/**\n * A base node callback attribute.\n */\nexport default abstract class Callback extends Attribute {\n /**\n * @param type The node attribute type.\n * @param args The array of decorator argument definitions.\n * @param functionName The name of the agent function to call.\n */\n constructor(type: string, args: any[], private functionName: string) {\n super(type, args);\n }\n\n /**\n * Gets the name of the agent function to call.\n */\n getFunctionName = () => this.functionName;\n\n /**\n * Gets whether this attribute is a guard.\n */\n isGuard = () => false;\n\n /**\n * Gets the attribute details.\n */\n getDetails(): CallbackAttributeDetails {\n return {\n type: this.type,\n args: this.args,\n functionName: this.getFunctionName()\n };\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n abstract callAgentFunction: (agent: Agent, isSuccess: boolean, isAborted: boolean) => void;\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * An ENTRY callback which defines an agent function to call when the associated node is updated and moves out of running state.\n */\nexport default class Entry extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: any[]) {\n super(\"entry\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n callAgentFunction = (agent: Agent) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call entry function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function.\n callbackFuncInvoker(this.args);\n };\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * A STEP callback which defines an agent function to call when the associated node is updated.\n */\nexport default class Step extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: any[]) {\n super(\"step\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n */\n callAgentFunction = (agent: Agent) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call step function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function.\n callbackFuncInvoker(this.args);\n };\n}\n", "import Callback from \"./Callback\";\nimport Lookup from \"../../Lookup\";\nimport { Agent } from \"../../Agent\";\n\n/**\n * An EXIT callback which defines an agent function to call when the associated node is updated and moves to a finished state or is aborted.\n */\nexport default class Exit extends Callback {\n /**\n * @param functionName The name of the agent function to call.\n * @param args The array of callback argument definitions.\n */\n constructor(functionName: string, args: any[]) {\n super(\"exit\", args, functionName);\n }\n\n /**\n * Attempt to call the agent function that this callback refers to.\n * @param agent The agent.\n * @param isSuccess Whether the decorated node was left with a success state.\n * @param isAborted Whether the decorated node was aborted.\n */\n callAgentFunction = (agent: Agent, isSuccess: boolean, isAborted: boolean) => {\n // Attempt to get the invoker for the callback function.\n const callbackFuncInvoker = Lookup.getFuncInvoker(agent, this.getFunctionName());\n\n // The callback function should be defined.\n if (callbackFuncInvoker === null) {\n throw new Error(\n `cannot call exit function '${this.getFunctionName()}' as is not defined on the agent and has not been registered`\n );\n }\n\n // Call the callback function\n callbackFuncInvoker([{ succeeded: isSuccess, aborted: isAborted }, ...this.args]);\n };\n}\n", "import { AnyNodeDefinition, RootNodeDefinition } from \"./BehaviourTreeDefinition\";\nimport GuardPath, { GuardPathPart } from \"./attributes/guards/GuardPath\";\nimport { validateBranchSubtreeLinks } from \"./BehaviourTreeDefinitionValidator\";\nimport { isInteger } from \"./BehaviourTreeDefinitionUtilities\";\nimport Node from \"./nodes/Node\";\nimport Composite from \"./nodes/composite/Composite\";\nimport Decorator from \"./nodes/decorator/Decorator\";\nimport Parallel from \"./nodes/composite/Parallel\";\nimport Selector from \"./nodes/composite/Selector\";\nimport Sequence from \"./nodes/composite/Sequence\";\nimport Lotto from \"./nodes/composite/Lotto\";\nimport Fail from \"./nodes/decorator/Fail\";\nimport Flip from \"./nodes/decorator/Flip\";\nimport Repeat from \"./nodes/decorator/Repeat\";\nimport Retry from \"./nodes/decorator/Retry\";\nimport Root from \"./nodes/decorator/Root\";\nimport Succeed from \"./nodes/decorator/Succeed\";\nimport Action from \"./nodes/leaf/Action\";\nimport Condition from \"./nodes/leaf/Condition\";\nimport Wait from \"./nodes/leaf/Wait\";\nimport Lookup from \"./Lookup\";\nimport Attribute from \"./attributes/Attribute\";\nimport While from \"./attributes/guards/While\";\nimport Until from \"./attributes/guards/Until\";\nimport Entry from \"./attributes/callbacks/Entry\";\nimport Step from \"./attributes/callbacks/Step\";\nimport Exit from \"./attributes/callbacks/Exit\";\n\n/**\n * A type representing any node instance in a behaviour tree.\n */\ntype AnyNode =\n | Root\n | Action\n | Condition\n | Wait\n | Sequence\n | Selector\n | Lotto\n | Parallel\n | Repeat\n | Retry\n | Flip\n | Succeed\n | Fail;\n\n/**\n * A type defining a mapping of root node identifiers to root node definitions.\n */\ntype RootNodeDefinitionMap = { [key: string | symbol]: RootNodeDefinition };\n\n/**\n * A symbol to use as the main root key in any root node mappings.\n */\nconst MAIN_ROOT_NODE_KEY = Symbol(\"__root__\");\n\n/**\n * Build and populate the root nodes based on the provided definition, assuming that the definition has been validated.\n * @param definition The root node definitions.\n * @returns The built and populated root node definitions.\n */\nexport default function buildRootNode(definition: RootNodeDefinition[]): Root {\n // Create a mapping of root node identifers to root node definitions, including globally registered subtree root node definitions.\n const rootNodeDefinitionMap = createRootNodeDefinitionMap(definition);\n\n // Now that we have all of our root node definitions (those part of the tree definition and those globally registered)\n // we should validate the branch-subtree links. This will also double-check that we dont have any circular dependencies\n // in our branch-subtree references and that we have no broken branch-subtree links.\n validateBranchSubtreeLinks(\n [rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], ...Object.values(rootNodeDefinitionMap)],\n true\n );\n\n // Create our populated tree of node instances, starting with our main root node.\n const rootNode = nodeFactory(rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], rootNodeDefinitionMap) as Root;\n\n // Set a guard path on every leaf of the tree to evaluate as part of each update.\n applyLeafNodeGuardPaths(rootNode);\n\n // We only need to return the main root node.\n return rootNode;\n}\n\n/**\n * A factory function which creates a node instance based on the specified definition.\n * @param definition The node definition.\n * @param rootNodeDefinitionMap The mapping of root node identifers to root node definitions, including globally registered subtree root node definitions.\n * @returns A node instance based on the specified definition.\n */\nfunction nodeFactory(definition: AnyNodeDefinition, rootNodeDefinitionMap: RootNodeDefinitionMap): AnyNode {\n // Get the attributes for the node.\n const attributes = nodeAttributesFactory(definition);\n\n // Create the node instance based on the definition type.\n switch (definition.type) {\n case \"root\":\n return new Root(attributes, nodeFactory(definition.child, rootNodeDefinitionMap));\n\n case \"repeat\":\n let iterations: number | null = null;\n let iterationsMin: number | null = null;\n let iterationsMax: number | null = null;\n\n if (Array.isArray(definition.iterations)) {\n iterationsMin = definition.iterations[0];\n iterationsMax = definition.iterations[1];\n } else if (isInteger(definition.iterations)) {\n iterations = definition.iterations!;\n }\n\n return new Repeat(\n attributes,\n iterations,\n iterationsMin,\n iterationsMax,\n nodeFactory(definition.child, rootNodeDefinitionMap)\n );\n\n case \"retry\":\n let attempts: number | null = null;\n let attemptsMin: number | null = null;\n let attemptsMax: number | null = null;\n\n if (Array.isArray(definition.attempts)) {\n attemptsMin = definition.attempts[0];\n attemptsMax = definition.attempts[1];\n } else if (isInteger(definition.attempts)) {\n attempts = definition.attempts!;\n }\n\n return new Retry(\n attributes,\n attempts,\n attemptsMin,\n attemptsMax,\n nodeFactory(definition.child, rootNodeDefinitionMap)\n );\n\n case \"flip\":\n return new Flip(attributes, nodeFactory(definition.child, rootNodeDefinitionMap));\n\n case \"succeed\":\n return new Succeed(attributes, nodeFactory(definition.child, rootNodeDefinitionMap));\n\n case \"fail\":\n return new Fail(attributes, nodeFactory(definition.child, rootNodeDefinitionMap));\n\n case \"sequence\":\n return new Sequence(\n attributes,\n definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap))\n );\n\n case \"selector\":\n return new Selector(\n attributes,\n definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap))\n );\n\n case \"parallel\":\n return new Parallel(\n attributes,\n definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap))\n );\n\n case \"lotto\":\n return new Lotto(\n attributes,\n definition.weights,\n definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap))\n );\n\n case \"branch\":\n return nodeFactory(rootNodeDefinitionMap[definition.ref].child, rootNodeDefinitionMap);\n\n case \"action\":\n return new Action(attributes, definition.call, definition.args || []);\n\n case \"condition\":\n return new Condition(attributes, definition.call, definition.args || []);\n\n case \"wait\":\n let duration: number | null = null;\n let durationMin: number | null = null;\n let durationMax: number | null = null;\n\n if (Array.isArray(definition.duration)) {\n durationMin = definition.duration[0];\n durationMax = definition.duration[1];\n } else if (isInteger(definition.duration)) {\n duration = definition.duration!;\n }\n\n return new Wait(attributes, duration, durationMin, durationMax);\n }\n}\n\n/**\n * Creates an array of node attribute instances based on the specified node definition.\n * @param definition The node definition.\n * @returns An array of node attribute instances based on the specified node definition.\n */\nfunction nodeAttributesFactory(definition: AnyNodeDefinition): Attribute[] {\n const attributes: Attribute[] = [];\n\n if (definition.while) {\n attributes.push(new While(definition.while.call, definition.while.args ?? []));\n }\n\n if (definition.until) {\n attributes.push(new Until(definition.until.call, definition.until.args ?? []));\n }\n\n if (definition.entry) {\n attributes.push(new Entry(definition.entry.call, definition.entry.args ?? []));\n }\n\n if (definition.step) {\n attributes.push(new Step(definition.step.call, definition.step.args ?? []));\n }\n\n if (definition.exit) {\n attributes.push(new Exit(definition.exit.call, definition.exit.args ?? []));\n }\n\n return attributes;\n}\n\n/**\n * Creates a mapping of root node identifers to root node definitions, mixing in globally registered subtree root node definitions.\n * @param definition The root node definitions.\n * @returns A mapping of root node identifers to root node definitions, including globally registered subtree root node definitions.\n */\nfunction createRootNodeDefinitionMap(definition: RootNodeDefinition[]): RootNodeDefinitionMap {\n // Create a mapping of root node identifers to root node definitions.\n const rootNodeMap: RootNodeDefinitionMap = {};\n\n // Add in any registered subtree root node definitions.\n for (const [name, rootNodeDefinition] of Object.entries(Lookup.getSubtrees())) {\n // The name used when registering the subtree will be used as the root node identifier.\n rootNodeMap[name] = { ...rootNodeDefinition, id: name };\n }\n\n // Populate the map with the root node definitions that were included with the tree definition.\n // We do this after adding any registered subtrees as we want these to take presedence.\n for (const rootNodeDefinition of definition) {\n rootNodeMap[rootNodeDefinition.id ?? MAIN_ROOT_NODE_KEY] = rootNodeDefinition;\n }\n\n return rootNodeMap;\n}\n\n/**\n * Applies a guard path to every leaf of the tree to evaluate as part of each update.\n * @param root The main root tree node.\n */\nfunction applyLeafNodeGuardPaths(root: Root) {\n const nodePaths: Node[][] = [];\n\n const findLeafNodes = (path: Node[], node: Node) => {\n // Add the current node to the path.\n path = path.concat(node);\n\n // Check whether the current node is a leaf node.\n if (node.isLeafNode()) {\n nodePaths.push(path);\n } else {\n (node as Composite | Decorator).getChildren().forEach((child) => findLeafNodes(path, child));\n }\n };\n\n // Find all leaf node paths, starting from the root.\n findLeafNodes([], root);\n\n nodePaths.forEach((path) => {\n // Each node in the current path will have to be assigned a guard path, working from the root outwards.\n for (let depth = 0; depth < path.length; depth++) {\n // Get the node in the path at the current depth.\n const currentNode = path[depth];\n\n // The node may already have been assigned a guard path, if so just skip it.\n if (currentNode.hasGuardPath()) {\n continue;\n }\n\n // Create the guard path for the current node.\n const guardPath = new GuardPath(\n path\n .slice(0, depth + 1)\n .map((node) => ({ node, guards: node.getGuardAttributes() }))\n .filter((details) => details.guards.length > 0)\n );\n\n // Assign the guard path to the current node.\n currentNode.setGuardPath(guardPath);\n }\n });\n}\n", "import State, { AnyState } from \"./State\";\nimport Lookup from \"./Lookup\";\nimport Node from \"./nodes/Node\";\nimport Root from \"./nodes/decorator/Root\";\nimport Composite from \"./nodes/composite/Composite\";\nimport Decorator from \"./nodes/decorator/Decorator\";\nimport { Agent, GlobalFunction } from \"./Agent\";\nimport { CallbackAttributeDetails } from \"./attributes/callbacks/Callback\";\nimport { GuardAttributeDetails } from \"./attributes/guards/Guard\";\nimport { BehaviourTreeOptions } from \"./BehaviourTreeOptions\";\nimport { convertMDSLToJSON } from \"./mdsl/MDSLDefinitionParser\";\nimport { RootNodeDefinition } from \"./BehaviourTreeDefinition\";\nimport { validateDefinition, validateJSONDefinition } from \"./BehaviourTreeDefinitionValidator\";\nimport buildRootNode from \"./BehaviourTreeBuilder\";\nimport { isNullOrUndefined } from \"./BehaviourTreeDefinitionUtilities\";\n\n// Purely for outside inspection of the tree.\nexport type FlattenedTreeNode = {\n id: string;\n type: string;\n caption: string;\n state: AnyState;\n guards: GuardAttributeDetails[];\n callbacks: CallbackAttributeDetails[];\n args: any[];\n parentId: string | null;\n};\n\n/**\n * A representation of a behaviour tree.\n */\nexport class BehaviourTree {\n /**\n * The main root tree node.\n */\n private readonly _rootNode: Root;\n\n /**\n * Creates a new instance of the BehaviourTree class.\n * @param definition The behaviour tree definition as either an MDSL string, root node definition object or array of root node definition objects.\n * @param agent The agent instance that this behaviour tree is modelling behaviour for.\n * @param options The behaviour tree options object.\n */\n constructor(\n definition: string | RootNodeDefinition | RootNodeDefinition[],\n private agent: Agent,\n private options: BehaviourTreeOptions = {}\n ) {\n // The tree definition must be defined.\n if (isNullOrUndefined(definition)) {\n throw new Error(\"tree definition not defined\");\n }\n\n // The agent must be defined and not null.\n if (typeof agent !== \"object\" || agent === null) {\n throw new Error(\"the agent must be an object and not null\");\n }\n\n // We should validate the definition before we try to build the tree nodes.\n const { succeeded, errorMessage, json } = validateDefinition(definition);\n\n // Did our validation fail without error?\n if (!succeeded) {\n throw new Error(`invalid definition: ${errorMessage}`);\n }\n\n // Double check that we did actually get our json definition as part of our definition validtion.\n if (!json) {\n throw new Error(\n \"expected json definition to be returned as part of successful definition validation response\"\n );\n }\n\n try {\n // Create the populated tree of behaviour tree nodes and get the root node.\n this._rootNode = buildRootNode(json);\n } catch (exception) {\n // There was an issue in trying build and populate the behaviour tree.\n throw new Error(`error building tree: ${(exception as Error).message}`);\n }\n }\n\n /**\n * Gets whether the tree is in the RUNNING state.\n * @returns true if the tree is in the RUNNING state, otherwise false.\n */\n isRunning() {\n return this._rootNode.getState() === State.RUNNING;\n }\n\n /**\n * Gets the current tree state of SUCCEEDED, FAILED, READY or RUNNING.\n * @returns The current tree state.\n */\n getState() {\n return this._rootNode.getState();\n }\n\n /**\n * Step the tree.\n * Carries out a node update that traverses the tree from the root node outwards to any child nodes, skipping those that are already in a resolved state of SUCCEEDED or FAILED.\n * After being updated, leaf nodes will have a state of SUCCEEDED, FAILED or RUNNING. Leaf nodes that are left in the RUNNING state as part of a tree step will be revisited each\n * subsequent step until they move into a resolved state of either SUCCEEDED or FAILED, after which execution will move through the tree to the next node with a state of READY.\n *\n * Calling this method when the tree is already in a resolved state of SUCCEEDED or FAILED will cause it to be reset before tree traversal begins.\n */\n step() {\n // If the root node has already been stepped to completion then we need to reset it.\n if (this._rootNode.getState() === State.SUCCEEDED || this._rootNode.getState() === State.FAILED) {\n this._rootNode.reset();\n }\n\n try {\n this._rootNode.update(this.agent, this.options);\n } catch (exception) {\n throw new Error(`error stepping tree: ${(exception as Error).message}`);\n }\n }\n\n /**\n * Resets the tree from the root node outwards to each nested node, giving each a state of READY.\n */\n reset() {\n this._rootNode.reset();\n }\n\n /**\n * Gets the flattened details of every node in the tree.\n * @returns The flattened details of every node in the tree.\n */\n getFlattenedNodeDetails(): FlattenedTreeNode[] {\n // Create an empty flattened array of tree nodes.\n const flattenedTreeNodes: FlattenedTreeNode[] = [];\n\n /**\n * Helper function to process a node instance and push details into the flattened tree nodes array.\n * @param node The current node.\n * @param parentUid The UID of the node parent, or null if the node is the main root node.\n */\n const processNode = (node: Node, parentUid: string | null) => {\n // Get the guard and callback attribute details for this node.\n const guards = node\n .getAttributes()\n .filter((attribute) => attribute.isGuard())\n .map((attribute) => attribute.getDetails()) as GuardAttributeDetails[];\n const callbacks = node\n .getAttributes()\n .filter((attribute) => !attribute.isGuard())\n .map((attribute) => attribute.getDetails()) as CallbackAttributeDetails[];\n\n // Push the current node into the flattened nodes array.\n flattenedTreeNodes.push({\n id: node.getUid(),\n type: node.getType(),\n caption: node.getName(),\n state: node.getState(),\n guards,\n callbacks,\n args: node.getArguments(),\n parentId: parentUid\n });\n\n // Process each of the nodes children if it is not a leaf node.\n if (!node.isLeafNode()) {\n (node as Composite | Decorator)\n .getChildren()\n .forEach((child) => processNode(child, (node as Composite | Decorator).getUid()));\n }\n };\n\n // Convert the nested node structure into a flattened array of node details.\n processNode(this._rootNode, null);\n\n return flattenedTreeNodes;\n }\n\n /**\n * Registers the action/condition/guard/callback function or subtree with the given name.\n * @param name The name of the function or subtree to register.\n * @param value The function or subtree definition to register.\n */\n static register(name: string, value: GlobalFunction | string | RootNodeDefinition) {\n // Are we going to register a action/condition/guard/callback function?\n if (typeof value === \"function\") {\n Lookup.setFunc(name, value);\n return;\n }\n\n // We are not registering an action/condition/guard/callback function, so we must be registering a subtree.\n if (typeof value === \"string\") {\n let rootNodeDefinitions: RootNodeDefinition[];\n\n // We will assume that any string passed in will be a mdsl definition.\n try {\n rootNodeDefinitions = convertMDSLToJSON(value);\n } catch (exception) {\n throw new Error(`error registering definition, invalid MDSL: ${(exception as Error).message}`);\n }\n\n // This function should only ever be called with a definition containing a single unnamed root node.\n if (rootNodeDefinitions.length != 1 || typeof rootNodeDefinitions[0].id !== \"undefined\") {\n throw new Error(\"error registering definition: expected a single unnamed root node\");\n }\n\n try {\n // We should validate the subtree as we don't want invalid subtrees available via the lookup.\n const { succeeded, errorMessage } = validateJSONDefinition(rootNodeDefinitions[0]);\n\n // Did our validation fail without error?\n if (!succeeded) {\n throw new Error(errorMessage);\n }\n } catch (exception) {\n throw new Error(`error registering definition: ${(exception as Error).message}`);\n }\n\n // Everything seems hunky-dory, register the subtree.\n Lookup.setSubtree(name, rootNodeDefinitions[0]);\n } else if (typeof value === \"object\" && !Array.isArray(value)) {\n // We will assume that any object passed in is a root node definition.\n\n try {\n // We should validate the subtree as we don't want invalid subtrees available via the lookup.\n const { succeeded, errorMessage } = validateJSONDefinition(value);\n\n // Did our validation fail without error?\n if (!succeeded) {\n throw new Error(errorMessage);\n }\n } catch (exception) {\n throw new Error(`error registering definition: ${(exception as Error).message}`);\n }\n\n // Everything seems hunky-dory, register the subtree.\n Lookup.setSubtree(name, value);\n } else {\n throw new Error(\"unexpected value, expected string mdsl definition, root node json definition or function\");\n }\n }\n\n /**\n * Unregisters the registered action/condition/guard/callback function or subtree with the given name.\n * @param name The name of the registered action/condition/guard/callback function or subtree to unregister.\n */\n static unregister(name: string): void {\n Lookup.remove(name);\n }\n\n /**\n * Unregister all registered action/condition/guard/callback functions and subtrees.\n */\n static unregisterAll(): void {\n Lookup.empty();\n }\n}\n"], + "mappings": "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", + "names": ["Participant", "isNullOrUndefined", "Lotto", "createLotto", "State", "createLotto"] } diff --git a/dist/mdsl/MDSLDefinitionParser.d.ts b/dist/mdsl/MDSLDefinitionParser.d.ts new file mode 100644 index 0000000..53b2754 --- /dev/null +++ b/dist/mdsl/MDSLDefinitionParser.d.ts @@ -0,0 +1,7 @@ +import { RootNodeDefinition } from "../BehaviourTreeDefinition"; +/** + * Convert the MDSL tree definition string into an equivalent JSON definition. + * @param definition The tree definition string as MDSL. + * @returns The root node JSON definitions. + */ +export declare function convertMDSLToJSON(definition: string): RootNodeDefinition[]; diff --git a/dist/mdsl/MDSLNodeArgumentParser.d.ts b/dist/mdsl/MDSLNodeArgumentParser.d.ts new file mode 100644 index 0000000..affc2e5 --- /dev/null +++ b/dist/mdsl/MDSLNodeArgumentParser.d.ts @@ -0,0 +1,47 @@ +import { StringLiteralPlaceholders } from "./MDSLUtilities"; +/** + * A type representing any node function argument. + */ +type Argument = { + /** + * The argument value. + */ + value: T; + /** + * The argument type, used for validation. + */ + type: string; +}; +type NullArgument = Argument & { + type: "null"; +}; +type BooleanArgument = Argument & { + type: "boolean"; +}; +type NumberArgument = Argument & { + type: "number"; + /** + * A flag defining whether the number argument value is a valid integer. (used for validation) + */ + isInteger: boolean; +}; +type StringPlaceholderArgument = Argument & { + type: "string"; +}; +type IdentifierArgument = Argument & { + type: "identifier"; +}; +/** + * A type representing a reference to any node function argument. + */ +type AnyArgument = NullArgument | BooleanArgument | NumberArgument | StringPlaceholderArgument | IdentifierArgument; +/** + * Parse an array of argument definitions from the specified tokens array. + * @param tokens The array tokens to parse the argument definitions from. + * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values. + * @param argumentValidator The argument validator function. + * @param validationFailedMessage The exception message to throw if argument validation fails. + * @returns An array of argument definitions parsed from the specified tokens array. + */ +export declare function parseArgumentTokens(tokens: string[], stringArgumentPlaceholders: StringLiteralPlaceholders): AnyArgument[]; +export {}; diff --git a/dist/mdsl/MDSLNodeAttributeParser.d.ts b/dist/mdsl/MDSLNodeAttributeParser.d.ts new file mode 100644 index 0000000..f9530e3 --- /dev/null +++ b/dist/mdsl/MDSLNodeAttributeParser.d.ts @@ -0,0 +1,20 @@ +import { NodeAttributeDefinition } from "../BehaviourTreeDefinition"; +import { StringLiteralPlaceholders } from "./MDSLUtilities"; +/** + * A type defining the attribute definitions of a node. + */ +type NodeAttributes = { + while?: NodeAttributeDefinition; + until?: NodeAttributeDefinition; + entry?: NodeAttributeDefinition; + exit?: NodeAttributeDefinition; + step?: NodeAttributeDefinition; +}; +/** + * Parse any node attribute definitions from the specified tokens array. + * @param tokens The array of remaining tokens. + * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values. + * @returns An object of attribute definitions defined by any directly following tokens. + */ +export declare function parseAttributeTokens(tokens: string[], stringArgumentPlaceholders: StringLiteralPlaceholders): NodeAttributes; +export {}; diff --git a/dist/mdsl/MDSLUtilities.d.ts b/dist/mdsl/MDSLUtilities.d.ts new file mode 100644 index 0000000..29312a5 --- /dev/null +++ b/dist/mdsl/MDSLUtilities.d.ts @@ -0,0 +1,28 @@ +/** + * A type defining an object that holds a reference to substitued string literals parsed from the definition. + */ +export type StringLiteralPlaceholders = { + [key: string]: string; +}; +/** + * Pop the next raw token from the specified array of tokens and throw an error if it wasn't the expected one. + * @param tokens The array of tokens. + * @param expected An optional string or array or items, one of which must match the next popped token. + * @returns The popped token. + */ +export declare function popAndCheck(tokens: string[], expected?: string | string[]): string; +/** + * Swaps out any node/attribute argument string literals with placeholders. + * @param definition The definition. + * @returns An object containing a mapping of placeholders to original string values as well as the processed definition string. + */ +export declare function substituteStringLiterals(definition: string): { + placeholders: StringLiteralPlaceholders; + processedDefinition: string; +}; +/** + * Parse the tree definition into an array of raw tokens. + * @param definition The definition. + * @returns An array of tokens parsed from the definition. + */ +export declare function parseTokensFromDefinition(definition: string): string[]; diff --git a/dist/nodes/Node.d.ts b/dist/nodes/Node.d.ts index 864bffc..765b572 100644 --- a/dist/nodes/Node.d.ts +++ b/dist/nodes/Node.d.ts @@ -1,14 +1,13 @@ +import { BehaviourTreeOptions } from "../BehaviourTreeOptions"; +import { AnyState } from "../State"; import { Agent } from "../Agent"; +import Leaf from "./leaf/Leaf"; import Attribute from "../attributes/Attribute"; import Entry from "../attributes/callbacks/Entry"; import Exit from "../attributes/callbacks/Exit"; import Step from "../attributes/callbacks/Step"; import Guard from "../attributes/guards/Guard"; import GuardPath from "../attributes/guards/GuardPath"; -import { BehaviourTreeOptions } from "../BehaviourTreeOptions"; -import { AnyArgument } from "../RootAstNodesBuilder"; -import { AnyState } from "../State"; -import Leaf from "./leaf/Leaf"; /** * A base node. */ @@ -33,7 +32,7 @@ export default abstract class Node { * @param attributes The node attributes. * @param args The node argument definitions. */ - constructor(type: string, attributes: Attribute[], args: AnyArgument[]); + constructor(type: string, attributes: Attribute[], args: any[]); /** * Called when the node is being updated. * @param agent The agent. @@ -68,7 +67,7 @@ export default abstract class Node { /** * Gets the node arguments. */ - getArguments: () => AnyArgument[]; + getArguments: () => any[]; /** * Gets the node attribute with the specified type, or null if it does not exist. */ diff --git a/dist/nodes/composite/Lotto.d.ts b/dist/nodes/composite/Lotto.d.ts index 2a2a001..a374d86 100644 --- a/dist/nodes/composite/Lotto.d.ts +++ b/dist/nodes/composite/Lotto.d.ts @@ -9,13 +9,13 @@ import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; * The state of this node will match the state of the winning child. */ export default class Lotto extends Composite { - private tickets; + private weights; /** * @param attributes The node attributes. - * @param tickets The child node tickets. + * @param weights The child node weights. * @param children The child nodes. */ - constructor(attributes: Attribute[], tickets: number[], children: Node[]); + constructor(attributes: Attribute[], weights: number[] | undefined, children: Node[]); /** * The child node selected to be the active one. */ diff --git a/dist/nodes/leaf/Action.d.ts b/dist/nodes/leaf/Action.d.ts index 3b889be..37f047c 100644 --- a/dist/nodes/leaf/Action.d.ts +++ b/dist/nodes/leaf/Action.d.ts @@ -1,8 +1,7 @@ -import Leaf from "./Leaf"; +import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; import { Agent } from "../../Agent"; +import Leaf from "./Leaf"; import Attribute from "../../attributes/Attribute"; -import { AnyArgument } from "../../RootAstNodesBuilder"; -import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; /** * An Action leaf node. * This represents an immediate or ongoing state of behaviour. @@ -15,7 +14,7 @@ export default class Action extends Leaf { * @param actionName The action name. * @param actionArguments The array of action argument definitions. */ - constructor(attributes: Attribute[], actionName: string, actionArguments: AnyArgument[]); + constructor(attributes: Attribute[], actionName: string, actionArguments: any[]); /** * Whether there is a pending update promise. */ @@ -23,7 +22,7 @@ export default class Action extends Leaf { /** * The finished state result of an update promise. */ - private updatePromiseStateResult; + private updatePromiseResult; /** * Called when the node is being updated. * @param agent The agent. diff --git a/dist/nodes/leaf/Condition.d.ts b/dist/nodes/leaf/Condition.d.ts index e2f08bd..edf7d56 100644 --- a/dist/nodes/leaf/Condition.d.ts +++ b/dist/nodes/leaf/Condition.d.ts @@ -1,8 +1,7 @@ -import Leaf from "./Leaf"; +import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; import { Agent } from "../../Agent"; +import Leaf from "./Leaf"; import Attribute from "../../attributes/Attribute"; -import { AnyArgument } from "../../RootAstNodesBuilder"; -import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; /** * A Condition leaf node. * This will succeed or fail immediately based on an agent predicate, without moving to the 'RUNNING' state. @@ -15,7 +14,7 @@ export default class Condition extends Leaf { * @param conditionName The name of the condition function. * @param conditionArguments The array of condition argument definitions. */ - constructor(attributes: Attribute[], conditionName: string, conditionArguments: AnyArgument[]); + constructor(attributes: Attribute[], conditionName: string, conditionArguments: any[]); /** * Called when the node is being updated. * @param agent The agent. diff --git a/package-lock.json b/package-lock.json index ba18dd8..bb85d2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,30 @@ { "name": "mistreevous", - "version": "3.2.0", + "version": "4.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mistreevous", - "version": "3.2.0", + "version": "4.0.0", "license": "MIT", "dependencies": { "lotto-draw": "^1.0.2" }, "devDependencies": { - "chai": "^4.3.7", + "@types/chai": "^4.3.11", + "@types/mocha": "^10.0.6", + "@types/sinon": "^17.0.3", + "chai": "^4.4.1", "esbuild": "^0.15.18", "expect": "^25.5.0", "mocha": "^10.2.0", "npm-run-all": "^4.1.5", "prettier": "2.7.1", "should": "^1.3.0", - "sinon": "^14.0.1" + "sinon": "^14.0.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" } }, "node_modules/@babel/code-frame": { @@ -63,6 +68,18 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.15.18", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", @@ -159,6 +176,31 @@ "node": ">=8" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@sinonjs/commons": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", @@ -203,6 +245,36 @@ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -228,6 +300,37 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/mocha": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", + "dev": true, + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -249,6 +352,27 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -307,6 +431,12 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -391,18 +521,18 @@ } }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.0.8" }, "engines": { "node": ">=4" @@ -423,10 +553,13 @@ } }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, "engines": { "node": "*" } @@ -493,6 +626,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -1184,9 +1323,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*" @@ -2000,6 +2139,12 @@ "get-func-name": "^2.0.0" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -2618,6 +2763,7 @@ "version": "14.0.2", "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.2.tgz", "integrity": "sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==", + "deprecated": "16.1.1", "dev": true, "dependencies": { "@sinonjs/commons": "^2.0.0", @@ -2834,6 +2980,58 @@ "node": ">=8.0" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -2843,6 +3041,19 @@ "node": ">=4" } }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -2858,6 +3069,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "peer": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -2991,6 +3215,15 @@ "node": ">=10" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index edf7641..811ec88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mistreevous", - "version": "3.2.0", + "version": "4.0.0", "description": "A tool to build behaviour trees in JavaScript", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -8,24 +8,29 @@ "dist/*" ], "devDependencies": { - "chai": "^4.3.7", + "@types/chai": "^4.3.11", + "@types/mocha": "^10.0.6", + "@types/sinon": "^17.0.3", + "chai": "^4.4.1", "esbuild": "^0.15.18", "expect": "^25.5.0", "mocha": "^10.2.0", "npm-run-all": "^4.1.5", "prettier": "2.7.1", "should": "^1.3.0", - "sinon": "^14.0.1" + "sinon": "^14.0.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" }, "scripts": { - "build": "npm-run-all build:node build:web build:typecheck", + "build": "npm-run-all build:format build:node build:web build:typecheck", "watch": "npm run build:node -- --watch", - "test": "npm-run-all build:format build test:unit-test", - "build:format": "prettier --write \"src/**/*.ts\" \"test/**/*.js\"", - "build:node": "esbuild ./src/index.js --bundle --sourcemap --outdir=dist --platform=node", - "build:web": "esbuild ./src/index.js --bundle --sourcemap --platform=browser --global-name=mistreevous --outfile=dist/bundle.js", + "test": "npm-run-all build test:unit-test", + "build:format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "build:node": "esbuild ./src/index.ts --bundle --sourcemap --outdir=dist --platform=node", + "build:web": "esbuild ./src/index.ts --bundle --sourcemap --platform=browser --global-name=mistreevous --outfile=dist/bundle.js", "build:typecheck": "tsc --emitDeclarationOnly", - "test:unit-test": "mocha \"test/**/*.js\"" + "test:unit-test": "mocha --require ts-node/register test/**/*.spec.ts" }, "repository": { "type": "git", diff --git a/src/Agent.ts b/src/Agent.ts index 8fbdc92..0d21edc 100644 --- a/src/Agent.ts +++ b/src/Agent.ts @@ -1,7 +1,10 @@ -import { CompleteState } from "./State"; +import State, { CompleteState } from "./State"; +/** + * A type representing an agent that a behavior tree instance would operate on. + */ export type Agent = { - [actionName: string]: AgentFunction; + [propertyName: string]: AgentFunction | unknown; }; /* @@ -31,7 +34,7 @@ export type Agent = { */ export type ExitFunctionArg = { succeeded: boolean; aborted: boolean }; -export type FunctionArg = number | string | boolean | null | ExitFunctionArg; -export type ActionResult = CompleteState | Promise | boolean; -export type AgentFunction = (this: Agent, ...args: FunctionArg[]) => ActionResult; -export type GlobalFunction = (agent: Agent, ...args: FunctionArg[]) => ActionResult; +export type FunctionArg = any | ExitFunctionArg; +export type ActionResult = CompleteState | Promise | State.RUNNING | void; +export type AgentFunction = (this: Agent, ...args: FunctionArg[]) => ActionResult | boolean; +export type GlobalFunction = (agent: Agent, ...args: FunctionArg[]) => ActionResult | boolean; diff --git a/src/BehaviourTree.ts b/src/BehaviourTree.ts index 7903613..9953a0c 100644 --- a/src/BehaviourTree.ts +++ b/src/BehaviourTree.ts @@ -1,5 +1,3 @@ -import GuardPath, { GuardPathPart } from "./attributes/guards/GuardPath"; -import buildRootASTNodes, { AnyArgument, RootAstNode } from "./RootAstNodesBuilder"; import State, { AnyState } from "./State"; import Lookup from "./Lookup"; import Node from "./nodes/Node"; @@ -10,6 +8,11 @@ import { Agent, GlobalFunction } from "./Agent"; import { CallbackAttributeDetails } from "./attributes/callbacks/Callback"; import { GuardAttributeDetails } from "./attributes/guards/Guard"; import { BehaviourTreeOptions } from "./BehaviourTreeOptions"; +import { convertMDSLToJSON } from "./mdsl/MDSLDefinitionParser"; +import { RootNodeDefinition } from "./BehaviourTreeDefinition"; +import { validateDefinition, validateJSONDefinition } from "./BehaviourTreeDefinitionValidator"; +import buildRootNode from "./BehaviourTreeBuilder"; +import { isNullOrUndefined } from "./BehaviourTreeDefinitionUtilities"; // Purely for outside inspection of the tree. export type FlattenedTreeNode = { @@ -19,7 +22,7 @@ export type FlattenedTreeNode = { state: AnyState; guards: GuardAttributeDetails[]; callbacks: CallbackAttributeDetails[]; - args: AnyArgument[]; + args: any[]; parentId: string | null; }; @@ -30,27 +33,51 @@ export class BehaviourTree { /** * The main root tree node. */ - public readonly rootNode: Root; + private readonly _rootNode: Root; /** * Creates a new instance of the BehaviourTree class. - * @param definition The behaviour tree definition. + * @param definition The behaviour tree definition as either an MDSL string, root node definition object or array of root node definition objects. * @param agent The agent instance that this behaviour tree is modelling behaviour for. * @param options The behaviour tree options object. */ - constructor(definition: string, private agent: Agent, private options: BehaviourTreeOptions = {}) { - // The tree definition must be defined and a valid string. - if (typeof definition !== "string") { - throw new Error("the tree definition must be a string"); + constructor( + definition: string | RootNodeDefinition | RootNodeDefinition[], + private agent: Agent, + private options: BehaviourTreeOptions = {} + ) { + // The tree definition must be defined. + if (isNullOrUndefined(definition)) { + throw new Error("tree definition not defined"); } // The agent must be defined and not null. if (typeof agent !== "object" || agent === null) { - throw new Error("the agent must be defined and not null"); + throw new Error("the agent must be an object and not null"); } - // Parse the behaviour tree definition, create the populated tree of behaviour tree nodes, and get the root. - this.rootNode = BehaviourTree.createRootNode(definition); + // We should validate the definition before we try to build the tree nodes. + const { succeeded, errorMessage, json } = validateDefinition(definition); + + // Did our validation fail without error? + if (!succeeded) { + throw new Error(`invalid definition: ${errorMessage}`); + } + + // Double check that we did actually get our json definition as part of our definition validtion. + if (!json) { + throw new Error( + "expected json definition to be returned as part of successful definition validation response" + ); + } + + try { + // Create the populated tree of behaviour tree nodes and get the root node. + this._rootNode = buildRootNode(json); + } catch (exception) { + // There was an issue in trying build and populate the behaviour tree. + throw new Error(`error building tree: ${(exception as Error).message}`); + } } /** @@ -58,7 +85,7 @@ export class BehaviourTree { * @returns true if the tree is in the RUNNING state, otherwise false. */ isRunning() { - return this.rootNode.getState() === State.RUNNING; + return this._rootNode.getState() === State.RUNNING; } /** @@ -66,7 +93,7 @@ export class BehaviourTree { * @returns The current tree state. */ getState() { - return this.rootNode.getState(); + return this._rootNode.getState(); } /** @@ -79,12 +106,12 @@ export class BehaviourTree { */ step() { // If the root node has already been stepped to completion then we need to reset it. - if (this.rootNode.getState() === State.SUCCEEDED || this.rootNode.getState() === State.FAILED) { - this.rootNode.reset(); + if (this._rootNode.getState() === State.SUCCEEDED || this._rootNode.getState() === State.FAILED) { + this._rootNode.reset(); } try { - this.rootNode.update(this.agent, this.options); + this._rootNode.update(this.agent, this.options); } catch (exception) { throw new Error(`error stepping tree: ${(exception as Error).message}`); } @@ -94,7 +121,7 @@ export class BehaviourTree { * Resets the tree from the root node outwards to each nested node, giving each a state of READY. */ reset() { - this.rootNode.reset(); + this._rootNode.reset(); } /** @@ -142,7 +169,7 @@ export class BehaviourTree { }; // Convert the nested node structure into a flattened array of node details. - processNode(this.rootNode, null); + processNode(this._rootNode, null); return flattenedTreeNodes; } @@ -152,30 +179,62 @@ export class BehaviourTree { * @param name The name of the function or subtree to register. * @param value The function or subtree definition to register. */ - static register(name: string, value: GlobalFunction | string) { + static register(name: string, value: GlobalFunction | string | RootNodeDefinition) { + // Are we going to register a action/condition/guard/callback function? if (typeof value === "function") { - // We are going to register a action/condition/guard/callback function. Lookup.setFunc(name, value); - } else if (typeof value === "string") { - // We are going to register a subtree. - let rootASTNodes: RootAstNode[]; + return; + } + + // We are not registering an action/condition/guard/callback function, so we must be registering a subtree. + if (typeof value === "string") { + let rootNodeDefinitions: RootNodeDefinition[]; + // We will assume that any string passed in will be a mdsl definition. try { - // Try to create the behaviour tree AST based on the definition provided, this could fail if the definition is invalid. - rootASTNodes = buildRootASTNodes(value); + rootNodeDefinitions = convertMDSLToJSON(value); } catch (exception) { - // There was an issue in trying to parse and build the tree definition. - throw new Error(`error registering definition: ${(exception as Error).message}`); + throw new Error(`error registering definition, invalid MDSL: ${(exception as Error).message}`); } // This function should only ever be called with a definition containing a single unnamed root node. - if (rootASTNodes.length != 1 || rootASTNodes[0].name !== null) { + if (rootNodeDefinitions.length != 1 || typeof rootNodeDefinitions[0].id !== "undefined") { throw new Error("error registering definition: expected a single unnamed root node"); } - Lookup.setSubtree(name, rootASTNodes[0]); + try { + // We should validate the subtree as we don't want invalid subtrees available via the lookup. + const { succeeded, errorMessage } = validateJSONDefinition(rootNodeDefinitions[0]); + + // Did our validation fail without error? + if (!succeeded) { + throw new Error(errorMessage); + } + } catch (exception) { + throw new Error(`error registering definition: ${(exception as Error).message}`); + } + + // Everything seems hunky-dory, register the subtree. + Lookup.setSubtree(name, rootNodeDefinitions[0]); + } else if (typeof value === "object" && !Array.isArray(value)) { + // We will assume that any object passed in is a root node definition. + + try { + // We should validate the subtree as we don't want invalid subtrees available via the lookup. + const { succeeded, errorMessage } = validateJSONDefinition(value); + + // Did our validation fail without error? + if (!succeeded) { + throw new Error(errorMessage); + } + } catch (exception) { + throw new Error(`error registering definition: ${(exception as Error).message}`); + } + + // Everything seems hunky-dory, register the subtree. + Lookup.setSubtree(name, value); } else { - throw new Error("unexpected value, expected string definition or function"); + throw new Error("unexpected value, expected string mdsl definition, root node json definition or function"); } } @@ -193,88 +252,4 @@ export class BehaviourTree { static unregisterAll(): void { Lookup.empty(); } - - /** - * Parses a behaviour tree definition and creates a tree of behaviour tree nodes. - * @param {string} definition The behaviour tree definition. - * @returns The root behaviour tree node. - */ - private static createRootNode(definition: string): Root { - try { - // Try to create the behaviour tree AST based on the definition provided, this could fail if the definition is invalid. - const rootASTNodes = buildRootASTNodes(definition); - - // Create a symbol to use as the main root key in our root node mapping. - const mainRootNodeKey = Symbol("__root__"); - - // Create a mapping of root node names to root AST tokens. The main root node will have a key of Symbol("__root__"). - const rootNodeMap: { [key: string | symbol]: RootAstNode } = {}; - for (const rootASTNode of rootASTNodes) { - rootNodeMap[rootASTNode.name === null ? mainRootNodeKey : rootASTNode.name!] = rootASTNode; - } - - // Convert the AST to our actual tree and get the root node. - const rootNode: Root = rootNodeMap[mainRootNodeKey].createNodeInstance( - // Create a provider for named root nodes that are part of our definition or have been registered. Prioritising the former. - (name: string): RootAstNode => (rootNodeMap[name] ? rootNodeMap[name] : Lookup.getSubtree(name)), - [] - ); - - // Set a guard path on every leaf of the tree to evaluate as part of its update. - BehaviourTree.applyLeafNodeGuardPaths(rootNode); - - // Return the root node. - return rootNode; - } catch (exception) { - // There was an issue in trying to parse and build the tree definition. - throw new Error(`error parsing tree: ${(exception as Error).message}`); - } - } - - /** - * Applies a guard path to every leaf of the tree to evaluate as part of each update. - * @param rootNode The main root tree node. - */ - private static applyLeafNodeGuardPaths(rootNode: Root) { - const nodePaths: Node[][] = []; - - const findLeafNodes = (path: Node[], node: Node) => { - // Add the current node to the path. - path = path.concat(node); - - // Check whether the current node is a leaf node. - if (node.isLeafNode()) { - nodePaths.push(path); - } else { - (node as Composite | Decorator).getChildren().forEach((child) => findLeafNodes(path, child)); - } - }; - - // Find all leaf node paths, starting from the root. - findLeafNodes([], rootNode); - - nodePaths.forEach((path) => { - // Each node in the current path will have to be assigned a guard path, working from the root outwards. - for (let depth = 0; depth < path.length; depth++) { - // Get the node in the path at the current depth. - const currentNode = path[depth]; - - // The node may already have been assigned a guard path, if so just skip it. - if (currentNode.hasGuardPath()) { - continue; - } - - // Create the guard path for the current node. - const guardPath = new GuardPath( - path - .slice(0, depth + 1) - .map((node) => ({ node, guards: node.getGuardAttributes() })) - .filter((details) => details.guards.length > 0) - ); - - // Assign the guard path to the current node. - currentNode.setGuardPath(guardPath); - } - }); - } } diff --git a/src/BehaviourTreeBuilder.ts b/src/BehaviourTreeBuilder.ts new file mode 100644 index 0000000..89d0c31 --- /dev/null +++ b/src/BehaviourTreeBuilder.ts @@ -0,0 +1,298 @@ +import { AnyNodeDefinition, RootNodeDefinition } from "./BehaviourTreeDefinition"; +import GuardPath, { GuardPathPart } from "./attributes/guards/GuardPath"; +import { validateBranchSubtreeLinks } from "./BehaviourTreeDefinitionValidator"; +import { isInteger } from "./BehaviourTreeDefinitionUtilities"; +import Node from "./nodes/Node"; +import Composite from "./nodes/composite/Composite"; +import Decorator from "./nodes/decorator/Decorator"; +import Parallel from "./nodes/composite/Parallel"; +import Selector from "./nodes/composite/Selector"; +import Sequence from "./nodes/composite/Sequence"; +import Lotto from "./nodes/composite/Lotto"; +import Fail from "./nodes/decorator/Fail"; +import Flip from "./nodes/decorator/Flip"; +import Repeat from "./nodes/decorator/Repeat"; +import Retry from "./nodes/decorator/Retry"; +import Root from "./nodes/decorator/Root"; +import Succeed from "./nodes/decorator/Succeed"; +import Action from "./nodes/leaf/Action"; +import Condition from "./nodes/leaf/Condition"; +import Wait from "./nodes/leaf/Wait"; +import Lookup from "./Lookup"; +import Attribute from "./attributes/Attribute"; +import While from "./attributes/guards/While"; +import Until from "./attributes/guards/Until"; +import Entry from "./attributes/callbacks/Entry"; +import Step from "./attributes/callbacks/Step"; +import Exit from "./attributes/callbacks/Exit"; + +/** + * A type representing any node instance in a behaviour tree. + */ +type AnyNode = + | Root + | Action + | Condition + | Wait + | Sequence + | Selector + | Lotto + | Parallel + | Repeat + | Retry + | Flip + | Succeed + | Fail; + +/** + * A type defining a mapping of root node identifiers to root node definitions. + */ +type RootNodeDefinitionMap = { [key: string | symbol]: RootNodeDefinition }; + +/** + * A symbol to use as the main root key in any root node mappings. + */ +const MAIN_ROOT_NODE_KEY = Symbol("__root__"); + +/** + * Build and populate the root nodes based on the provided definition, assuming that the definition has been validated. + * @param definition The root node definitions. + * @returns The built and populated root node definitions. + */ +export default function buildRootNode(definition: RootNodeDefinition[]): Root { + // Create a mapping of root node identifers to root node definitions, including globally registered subtree root node definitions. + const rootNodeDefinitionMap = createRootNodeDefinitionMap(definition); + + // Now that we have all of our root node definitions (those part of the tree definition and those globally registered) + // we should validate the branch-subtree links. This will also double-check that we dont have any circular dependencies + // in our branch-subtree references and that we have no broken branch-subtree links. + validateBranchSubtreeLinks( + [rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], ...Object.values(rootNodeDefinitionMap)], + true + ); + + // Create our populated tree of node instances, starting with our main root node. + const rootNode = nodeFactory(rootNodeDefinitionMap[MAIN_ROOT_NODE_KEY], rootNodeDefinitionMap) as Root; + + // Set a guard path on every leaf of the tree to evaluate as part of each update. + applyLeafNodeGuardPaths(rootNode); + + // We only need to return the main root node. + return rootNode; +} + +/** + * A factory function which creates a node instance based on the specified definition. + * @param definition The node definition. + * @param rootNodeDefinitionMap The mapping of root node identifers to root node definitions, including globally registered subtree root node definitions. + * @returns A node instance based on the specified definition. + */ +function nodeFactory(definition: AnyNodeDefinition, rootNodeDefinitionMap: RootNodeDefinitionMap): AnyNode { + // Get the attributes for the node. + const attributes = nodeAttributesFactory(definition); + + // Create the node instance based on the definition type. + switch (definition.type) { + case "root": + return new Root(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + + case "repeat": + let iterations: number | null = null; + let iterationsMin: number | null = null; + let iterationsMax: number | null = null; + + if (Array.isArray(definition.iterations)) { + iterationsMin = definition.iterations[0]; + iterationsMax = definition.iterations[1]; + } else if (isInteger(definition.iterations)) { + iterations = definition.iterations!; + } + + return new Repeat( + attributes, + iterations, + iterationsMin, + iterationsMax, + nodeFactory(definition.child, rootNodeDefinitionMap) + ); + + case "retry": + let attempts: number | null = null; + let attemptsMin: number | null = null; + let attemptsMax: number | null = null; + + if (Array.isArray(definition.attempts)) { + attemptsMin = definition.attempts[0]; + attemptsMax = definition.attempts[1]; + } else if (isInteger(definition.attempts)) { + attempts = definition.attempts!; + } + + return new Retry( + attributes, + attempts, + attemptsMin, + attemptsMax, + nodeFactory(definition.child, rootNodeDefinitionMap) + ); + + case "flip": + return new Flip(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + + case "succeed": + return new Succeed(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + + case "fail": + return new Fail(attributes, nodeFactory(definition.child, rootNodeDefinitionMap)); + + case "sequence": + return new Sequence( + attributes, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) + ); + + case "selector": + return new Selector( + attributes, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) + ); + + case "parallel": + return new Parallel( + attributes, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) + ); + + case "lotto": + return new Lotto( + attributes, + definition.weights, + definition.children.map((child) => nodeFactory(child, rootNodeDefinitionMap)) + ); + + case "branch": + return nodeFactory(rootNodeDefinitionMap[definition.ref].child, rootNodeDefinitionMap); + + case "action": + return new Action(attributes, definition.call, definition.args || []); + + case "condition": + return new Condition(attributes, definition.call, definition.args || []); + + case "wait": + let duration: number | null = null; + let durationMin: number | null = null; + let durationMax: number | null = null; + + if (Array.isArray(definition.duration)) { + durationMin = definition.duration[0]; + durationMax = definition.duration[1]; + } else if (isInteger(definition.duration)) { + duration = definition.duration!; + } + + return new Wait(attributes, duration, durationMin, durationMax); + } +} + +/** + * Creates an array of node attribute instances based on the specified node definition. + * @param definition The node definition. + * @returns An array of node attribute instances based on the specified node definition. + */ +function nodeAttributesFactory(definition: AnyNodeDefinition): Attribute[] { + const attributes: Attribute[] = []; + + if (definition.while) { + attributes.push(new While(definition.while.call, definition.while.args ?? [])); + } + + if (definition.until) { + attributes.push(new Until(definition.until.call, definition.until.args ?? [])); + } + + if (definition.entry) { + attributes.push(new Entry(definition.entry.call, definition.entry.args ?? [])); + } + + if (definition.step) { + attributes.push(new Step(definition.step.call, definition.step.args ?? [])); + } + + if (definition.exit) { + attributes.push(new Exit(definition.exit.call, definition.exit.args ?? [])); + } + + return attributes; +} + +/** + * Creates a mapping of root node identifers to root node definitions, mixing in globally registered subtree root node definitions. + * @param definition The root node definitions. + * @returns A mapping of root node identifers to root node definitions, including globally registered subtree root node definitions. + */ +function createRootNodeDefinitionMap(definition: RootNodeDefinition[]): RootNodeDefinitionMap { + // Create a mapping of root node identifers to root node definitions. + const rootNodeMap: RootNodeDefinitionMap = {}; + + // Add in any registered subtree root node definitions. + for (const [name, rootNodeDefinition] of Object.entries(Lookup.getSubtrees())) { + // The name used when registering the subtree will be used as the root node identifier. + rootNodeMap[name] = { ...rootNodeDefinition, id: name }; + } + + // Populate the map with the root node definitions that were included with the tree definition. + // We do this after adding any registered subtrees as we want these to take presedence. + for (const rootNodeDefinition of definition) { + rootNodeMap[rootNodeDefinition.id ?? MAIN_ROOT_NODE_KEY] = rootNodeDefinition; + } + + return rootNodeMap; +} + +/** + * Applies a guard path to every leaf of the tree to evaluate as part of each update. + * @param root The main root tree node. + */ +function applyLeafNodeGuardPaths(root: Root) { + const nodePaths: Node[][] = []; + + const findLeafNodes = (path: Node[], node: Node) => { + // Add the current node to the path. + path = path.concat(node); + + // Check whether the current node is a leaf node. + if (node.isLeafNode()) { + nodePaths.push(path); + } else { + (node as Composite | Decorator).getChildren().forEach((child) => findLeafNodes(path, child)); + } + }; + + // Find all leaf node paths, starting from the root. + findLeafNodes([], root); + + nodePaths.forEach((path) => { + // Each node in the current path will have to be assigned a guard path, working from the root outwards. + for (let depth = 0; depth < path.length; depth++) { + // Get the node in the path at the current depth. + const currentNode = path[depth]; + + // The node may already have been assigned a guard path, if so just skip it. + if (currentNode.hasGuardPath()) { + continue; + } + + // Create the guard path for the current node. + const guardPath = new GuardPath( + path + .slice(0, depth + 1) + .map((node) => ({ node, guards: node.getGuardAttributes() })) + .filter((details) => details.guards.length > 0) + ); + + // Assign the guard path to the current node. + currentNode.setGuardPath(guardPath); + } + }); +} diff --git a/src/BehaviourTreeDefinition.ts b/src/BehaviourTreeDefinition.ts new file mode 100644 index 0000000..70776ff --- /dev/null +++ b/src/BehaviourTreeDefinition.ts @@ -0,0 +1,258 @@ +/** + * An attribute for a node. + */ +export interface NodeAttributeDefinition { + /** + * The name of the agent function to invoke. + */ + call: string; + /** + * An array of arguments to pass when invoking the agent function. + */ + args?: any[]; +} + +/** + * A type defining a general node definition. + */ +export interface NodeDefinition { + /** + * The node type. + */ + type: string; + /** + * The 'while' node attribute definition. + */ + while?: NodeAttributeDefinition; + /** + * The 'until' node attribute definition. + */ + until?: NodeAttributeDefinition; + /** + * The 'entry' node attribute definition. + */ + entry?: NodeAttributeDefinition; + /** + * The 'exit' node attribute definition. + */ + exit?: NodeAttributeDefinition; + /** + * The 'step' node attribute definition. + */ + step?: NodeAttributeDefinition; +} + +/** + * A composite node that can contain any number of child nodes. + */ +export interface CompositeNodeDefinition extends NodeDefinition { + /** + * The child nodes of this composite node. + */ + children: AnyChildNodeDefinition[]; +} + +/** + * A decorator node, a composite with only a single child node. + */ +export interface DecoratorNodeDefinition extends NodeDefinition { + /** + * The child node of this decorator node. + */ + child: AnyChildNodeDefinition; +} + +/** + * A branch node. + */ +export interface BranchNodeDefinition extends NodeDefinition { + /** + * The node type. + */ + type: "branch"; + /** + * The reference matching a root node identifier. + */ + ref: string; +} + +/** + * An action node. + */ +export interface ActionNodeDefinition extends NodeDefinition { + /** + * The node type. + */ + type: "action"; + /** + * The name of the agent function or registered function to invoke. + */ + call: string; + /** + * An array of arguments to pass when invoking the action function. + */ + args?: any[]; +} + +/** + * A condition node. + */ +export interface ConditionNodeDefinition extends NodeDefinition { + /** + * The node type. + */ + type: "condition"; + /** + * The name of the agent function or registered function to invoke. + */ + call: string; + /** + * An array of arguments to pass when invoking the condition function. + */ + args?: any[]; +} + +/** + * A wait node. + */ +export interface WaitNodeDefinition extends NodeDefinition { + /** + * The node type. + */ + type: "wait"; + duration?: number | [number, number]; +} + +/** + * A sequence node. + */ +export interface SequenceNodeDefinition extends CompositeNodeDefinition { + /** + * The node type. + */ + type: "sequence"; +} + +/** + * A selector node. + */ +export interface SelectorNodeDefinition extends CompositeNodeDefinition { + /** + * The node type. + */ + type: "selector"; +} + +/** + * A lotto node. + */ +export interface LottoNodeDefinition extends CompositeNodeDefinition { + /** + * The node type. + */ + type: "lotto"; + /** + * The selection weights for child nodes that correspond to the child node position. + */ + weights?: number[]; +} + +/** + * A parallel node. + */ +export interface ParallelNodeDefinition extends CompositeNodeDefinition { + /** + * The node type. + */ + type: "parallel"; +} + +/** + * A root node. + */ +export interface RootNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "root"; + /** + * The unique root node identifier. + */ + id?: string; +} + +/** + * A repeat node. + */ +export interface RepeatNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "repeat"; + iterations?: number | [number, number]; +} + +/** + * A retry node. + */ +export interface RetryNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "retry"; + attempts?: number | [number, number]; +} + +/** + * A flip node. + */ +export interface FlipNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "flip"; +} + +/** + * A succeed node. + */ +export interface SucceedNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "succeed"; +} + +/** + * A fail node. + */ +export interface FailNodeDefinition extends DecoratorNodeDefinition { + /** + * The node type. + */ + type: "fail"; +} + +/** + * A type defining any node definition. + */ +export type AnyNodeDefinition = + | BranchNodeDefinition + | ActionNodeDefinition + | ConditionNodeDefinition + | WaitNodeDefinition + | SequenceNodeDefinition + | SelectorNodeDefinition + | LottoNodeDefinition + | ParallelNodeDefinition + | RootNodeDefinition + | RepeatNodeDefinition + | RetryNodeDefinition + | FlipNodeDefinition + | SucceedNodeDefinition + | FailNodeDefinition; + +/** + * A type defining any node type that can be a child of composite parent node. + */ +export type AnyChildNodeDefinition = Exclude; diff --git a/src/BehaviourTreeDefinitionUtilities.ts b/src/BehaviourTreeDefinitionUtilities.ts new file mode 100644 index 0000000..780e9c5 --- /dev/null +++ b/src/BehaviourTreeDefinitionUtilities.ts @@ -0,0 +1,94 @@ +import { + NodeDefinition, + RootNodeDefinition, + DecoratorNodeDefinition, + CompositeNodeDefinition, + AnyNodeDefinition, + BranchNodeDefinition +} from "./BehaviourTreeDefinition"; + +/** + * A type guard function that returns true if the specified node satisfies the RootNodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the RootNodeDefinition type. + */ +export function isRootNode(node: NodeDefinition): node is RootNodeDefinition { + return node.type === "root"; +} + +/** + * A type guard function that returns true if the specified node satisfies the BranchNodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the BranchNodeDefinition type. + */ +export function isBranchNode(node: NodeDefinition): node is BranchNodeDefinition { + return node.type === "branch"; +} + +/** + * A type guard function that returns true if the specified node satisfies the NodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the NodeDefinition type. + */ +export function isLeafNode(node: NodeDefinition): node is NodeDefinition { + return ["branch", "action", "condition", "wait"].includes(node.type); +} + +/** + * A type guard function that returns true if the specified node satisfies the DecoratorNodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the DecoratorNodeDefinition type. + */ +export function isDecoratorNode(node: NodeDefinition): node is DecoratorNodeDefinition { + return ["root", "repeat", "retry", "flip", "succeed", "fail"].includes(node.type); +} + +/** + * A type guard function that returns true if the specified node satisfies the CompositeNodeDefinition type. + * @param node The node. + * @returns A value of true if the specified node satisfies the CompositeNodeDefinition type. + */ +export function isCompositeNode(node: NodeDefinition): node is CompositeNodeDefinition { + return ["sequence", "selector", "lotto", "parallel"].includes(node.type); +} + +/** + * Flatten a node definition into an array of all of its nested node definitions. + * @param nodeDefinition The node definition to flatten. + * @returns An array of all of nested node definitions. + */ +export function flattenDefinition(nodeDefinition: AnyNodeDefinition): AnyNodeDefinition[] { + const nodes: AnyNodeDefinition[] = []; + + const processNode = (currentNodeDefinition: AnyNodeDefinition) => { + nodes.push(currentNodeDefinition); + + if (isCompositeNode(currentNodeDefinition)) { + currentNodeDefinition.children.forEach(processNode); + } else if (isDecoratorNode(currentNodeDefinition)) { + processNode(currentNodeDefinition.child); + } + }; + + processNode(nodeDefinition); + + return nodes; +} + +/** + * Determines whether the passed value is an integer. + * @param value The value to check. + * @returns Whether the passed value is an integer. + */ +export function isInteger(value: unknown): boolean { + return typeof value === "number" && Math.floor(value) === value; +} + +/** + * Determines whether the passed value is null or undefined. + * @param value The value to check. + * @returns Whether the passed value is null or undefined. + */ +export function isNullOrUndefined(value: unknown): boolean { + return typeof value === "undefined" || value === null; +} diff --git a/src/BehaviourTreeDefinitionValidator.ts b/src/BehaviourTreeDefinitionValidator.ts new file mode 100644 index 0000000..693196f --- /dev/null +++ b/src/BehaviourTreeDefinitionValidator.ts @@ -0,0 +1,822 @@ +import { RootNodeDefinition } from "./BehaviourTreeDefinition"; +import { flattenDefinition, isBranchNode, isInteger } from "./BehaviourTreeDefinitionUtilities"; +import { convertMDSLToJSON } from "./mdsl/MDSLDefinitionParser"; + +/** + * An object representing the result of validating a tree definition. + */ +export type DefinitionValidationResult = { + /** + * A flag defining whether validation succeeded. + */ + succeeded: boolean; + /** + * A string containing the error message if validation did not succeed. + */ + errorMessage?: string; + /** + * The definition as json if the validation was successful, or undefined if validation did not succeed. + */ + json?: RootNodeDefinition[]; +}; + +/** + * Validates the specified behaviour tree definition in the form of JSON or MDSL, not taking any globally registered subtrees into consideration. + * @param definition The behaviour tree definition in the form of JSON or MDSL. + * @returns An object representing the result of validating the given tree definition. + */ +export function validateDefinition(definition: any): DefinitionValidationResult { + // The definition must be defined. + if (definition === null || typeof definition === "undefined") { + return createValidationFailureResult("definition is null or undefined"); + } + + // We are expecting a definition in one of three different forms: + // - A string which we will assume is MDSL and we will parse this to JSON before validation. + // - An array which we will assume is an array of root node definitions with at least one being the primary root node (no 'id' property) + // - An object which we will assume is the primary root node and should not have an 'id' property. + if (typeof definition === "string") { + // The definition is a string which we can assume is MDSL, so attempt to validate it. + return validateMDSLDefinition(definition); + } else if (typeof definition === "object") { + // The definition will either be an array (of root node definitions) or an object (the single primary root node definition). + return validateJSONDefinition(definition); + } else { + return createValidationFailureResult(`unexpected definition type of '${typeof definition}'`); + } +} + +/** + * Validates the specified behaviour tree definition in the form of MDSL. + * @param definition The behaviour tree definition in the form of MDSL. + * @returns An object representing the result of validating the given tree definition. + */ +function validateMDSLDefinition(definition: string): DefinitionValidationResult { + let rootNodeDefinitions; + + // The first thing the we need to do is to attempt to convert our MDSL into JSON. + try { + // The definition is a string which we can assume is MDSL, so attempt to parse it to a JSON definition in the form of an array of root node definitions. + rootNodeDefinitions = convertMDSLToJSON(definition); + } catch (exception) { + // We failed to parse the JSON from the MDSL, this is likely to be the result of it not being a valid MDSL string. + return createValidationFailureResult((exception as Error).message); + } + + // Unpack all of the root node definitions into arrays of main ('id' defined) and sub ('id' not defined) root node definitions. + const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "undefined"); + const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "string" && id.length > 0); + + // We should ALWAYS have exactly one root node definition without an 'id' property defined, which is out main root node definition. + if (mainRootNodeDefinitions.length !== 1) { + return createValidationFailureResult( + "expected single unnamed root node at base of definition to act as main root" + ); + } + + // We should never have duplicate 'id' properties across our sub root node definitions. + const subRootNodeIdenitifers: string[] = []; + for (const { id } of subRootNodeDefinitions) { + // Have we already come across this 'id' property value? + if (subRootNodeIdenitifers.includes(id!)) { + return createValidationFailureResult(`multiple root nodes found with duplicate name '${id}'`); + } + + subRootNodeIdenitifers.push(id!); + } + + try { + // Validate our branch -> subtree links and check for any circular dependencies, we don't care about checking for broken subtree links here. + validateBranchSubtreeLinks(rootNodeDefinitions, false); + } catch (exception) { + return createValidationFailureResult((exception as Error).message); + } + + // Our definition was valid! + return { + succeeded: true, + json: rootNodeDefinitions + }; +} + +/** + * Validates the specified behaviour tree definition in the form of JSON. + * @param definition The behaviour tree definition in the form of JSON. + * @returns An object representing the result of validating the given tree definition. + */ +export function validateJSONDefinition( + definition: RootNodeDefinition | RootNodeDefinition[] +): DefinitionValidationResult { + // The definition will either be an array (of root node definitions) or an object (the single primary root node definition). + const rootNodeDefinitions = Array.isArray(definition) ? definition : [definition]; + + // Iterate over our array of root nodes and call validateNode for each, passing an initial depth of 0, wrapped in a try catch to handle validation failures. + try { + rootNodeDefinitions.forEach((rootNodeDefinition) => validateNode(rootNodeDefinition, 0)); + } catch (error) { + // Handle cases where we have caught a thrown Error and return a failure result with the error message. + if (error instanceof Error) { + return createValidationFailureResult(error.message); + } + + // No idea what happened here! + return createValidationFailureResult(`unexpected error: ${error}`); + } + + // Unpack all of the root node definitions into arrays of main ('id' defined) and sub ('id' not defined) root node definitions. + const mainRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "undefined"); + const subRootNodeDefinitions = rootNodeDefinitions.filter(({ id }) => typeof id === "string" && id.length > 0); + + // We should ALWAYS have exactly one root node definition without an 'id' property defined, which is out main root node definition. + if (mainRootNodeDefinitions.length !== 1) { + return createValidationFailureResult( + "expected single root node without 'id' property defined to act as main root" + ); + } + + // We should never have duplicate 'id' properties across our sub root node definitions. + const subRootNodeIdenitifers: string[] = []; + for (const { id } of subRootNodeDefinitions) { + // Have we already come across this 'id' property value? + if (subRootNodeIdenitifers.includes(id!)) { + return createValidationFailureResult( + `multiple root nodes found with duplicate 'id' property value of '${id}'` + ); + } + + subRootNodeIdenitifers.push(id!); + } + + try { + // Validate our branch -> subtree links and check for any circular dependencies, we don't care about checking for broken subtree links here. + validateBranchSubtreeLinks(rootNodeDefinitions, false); + } catch (exception) { + return createValidationFailureResult((exception as Error).message); + } + + // Our definition was valid! + return { + succeeded: true, + json: rootNodeDefinitions + }; +} + +/** + * Validates the branch -> subtree links across all provided root node definitions. + * This will not consider branch nodes that reference any globally registered subtrees unless includesGlobalSubtrees + * is set to true, in which case we will also verify that there are no broken branch -> subtree links. + * @param rootNodeDefinitions The array of root node definitions. + * @param includesGlobalSubtrees A flag defining whether the array includes all global subtree root node definitions. + */ +export function validateBranchSubtreeLinks(rootNodeDefinitions: RootNodeDefinition[], includesGlobalSubtrees: boolean) { + // Create a mapping of root node identifiers to other root nodes that they reference via branch nodes. + // Below is an example of a mapping that includes a circular dependency (root => a => b => c => a) + // [{ refs: ["a", "b"] }, { id: "a", refs: ["b"] }, { id: "b", refs: ["c"] }, { id: "c", refs: ["a"] }] + const rootNodeMappings: { id: string | undefined; refs: string[] }[] = rootNodeDefinitions.map( + (rootNodeDefinition) => ({ + id: rootNodeDefinition.id, + refs: flattenDefinition(rootNodeDefinition) + .filter(isBranchNode) + .map(({ ref }) => ref) + }) + ); + + // A recursive function to walk through the mappings, keeping track of which root nodes we have visited in the form of a path of root node identifiers. + const followRefs = (mapping: { id: string | undefined; refs: string[] }, path: (string | undefined)[] = []) => { + // Have we found a circular dependency? + if (path.includes(mapping.id)) { + // We found a circular dependency! Get the bad path of root node identifiers. + const badPath = [...path, mapping.id]; + + // Create the formatted path value. [undefined, "a", "b", "c", "a"] would be formatted as "a -> b -> c -> a". + const badPathFormatted = badPath.filter((element) => !!element).join(" => "); + + // No need to continue, we found a circular dependency. + throw new Error(`circular dependency found in branch node references: ${badPathFormatted}`); + } + + for (const ref of mapping.refs) { + // Find the mapping for the root node with an identifer matching the current ref. + const subMapping = rootNodeMappings.find(({ id }) => id === ref); + + // We may not have a mapping for this ref, which is normal when we aren't considering all globally registered subtrees. + if (subMapping) { + followRefs(subMapping, [...path, mapping.id]); + } else if (includesGlobalSubtrees) { + // We found a reference to a root node that doesn't exist, which is a problem seeing as the root node definitons includes all globally registered subtrees. + throw new Error( + mapping.id + ? `subtree '${mapping.id}' has branch node that references root node '${ref}' which has not been defined` + : `primary tree has branch node that references root node '${ref}' which has not been defined` + ); + } + } + }; + + // Start looking for circular dependencies and broken references from the primary root node definition. + followRefs(rootNodeMappings.find((mapping) => typeof mapping.id === "undefined")!); +} + +/** + * Validate an object that we expect to be a node definition. + * @param definition An object that we expect to be a node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateNode(definition: any, depth: number): void { + // Every node must be valid object and have a non-empty 'type' string property. + if (typeof definition !== "object" || typeof definition.type !== "string" || definition.type.length === 0) { + throw new Error( + `node definition is not an object or 'type' property is not a non-empty string at depth '${depth}'` + ); + } + + // If this node is at the very base of the definition then it MUST be a root node. + if (depth === 0 && definition.type !== "root") { + throw new Error(`expected root node at base of definition but got node of type '${definition.type}'`); + } + + // How we validate this node definition will depend on its type. + switch (definition.type) { + case "action": + validateActionNode(definition, depth); + break; + + case "condition": + validateConditionNode(definition, depth); + break; + + case "wait": + validateWaitNode(definition, depth); + break; + + case "branch": + validateBranchNode(definition, depth); + break; + + case "root": + validateRootNode(definition, depth); + break; + + case "succeed": + validateSucceedNode(definition, depth); + break; + + case "fail": + validateFailNode(definition, depth); + break; + + case "flip": + validateFlipNode(definition, depth); + break; + + case "repeat": + validateRepeatNode(definition, depth); + break; + + case "retry": + validateRetryNode(definition, depth); + break; + + case "sequence": + validateSequenceNode(definition, depth); + break; + + case "selector": + validateSelectorNode(definition, depth); + break; + + case "parallel": + validateParallelNode(definition, depth); + break; + + case "lotto": + validateLottoNode(definition, depth); + break; + + default: + throw new Error(`unexpected node type of '${definition.type}' at depth '${depth}'`); + } +} + +/** + * Validate any attributes for a given node definition. + * @param definition The node definition. + * @param depth The depth of the node in the behaviour tree definition. + */ +function validateNodeAttributes(definition: any, depth: number): void { + // Validate each of the attribute types for this node. + ["while", "until", "entry", "exit", "step"].forEach((attributeName) => { + // Attempt to grab the definition for the current attribute from the node definition. + const attributeDefinition = definition[attributeName]; + + // All node attributes are optional, so there is nothing to do if the current attribute is not defined. + if (typeof attributeDefinition === "undefined") { + return; + } + + // The attribute definition must be an object. + if (typeof attributeDefinition !== "object") { + throw new Error( + `expected attribute '${attributeName}' to be an object for '${definition.type}' node at depth '${depth}'` + ); + } + + // The 'call' property must be defined for any attribute definition. + if (typeof attributeDefinition.call !== "string" || attributeDefinition.call.length === 0) { + throw new Error( + `expected 'call' property for attribute '${attributeName}' to be a non-empty string for '${definition.type}' node at depth '${depth}'` + ); + } + + // If any node attribute arguments have been defined then they must have been defined in an array. + if (typeof attributeDefinition.args !== "undefined" && !Array.isArray(attributeDefinition.args)) { + throw new Error( + `expected 'args' property for attribute '${attributeName}' to be an array for '${definition.type}' node at depth '${depth}'` + ); + } + }); +} + +/** + * Validate an object that we expect to be a root node definition. + * @param definition An object that we expect to be a root node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateRootNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "root") { + throw new Error("expected node type of 'root' for root node"); + } + + // A root node cannot be the child of another node. + if (depth > 0) { + throw new Error("a root node cannot be the child of another node"); + } + + // Check that, if the root node 'id' property is defined, it is a non-empty string. + if (typeof definition.id !== "undefined" && (typeof definition.id !== "string" || definition.id.length === 0)) { + throw new Error("expected non-empty string for 'id' property if defined for root node"); + } + + // A root node is a decorator node, so must have a child node defined. + if (typeof definition.child === "undefined") { + throw new Error("expected property 'child' to be defined for root node"); + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child node of this decorator node. + validateNode(definition.child, depth + 1); +} + +/** + * Validate an object that we expect to be a succeed node definition. + * @param definition An object that we expect to be a succeed node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateSucceedNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "succeed") { + throw new Error(`expected node type of 'succeed' for succeed node at depth '${depth}'`); + } + + // A succeed node is a decorator node, so must have a child node defined. + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for succeed node at depth '${depth}'`); + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child node of this decorator node. + validateNode(definition.child, depth + 1); +} + +/** + * Validate an object that we expect to be a fail node definition. + * @param definition An object that we expect to be a fail node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateFailNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "fail") { + throw new Error(`expected node type of 'fail' for fail node at depth '${depth}'`); + } + + // A fail node is a decorator node, so must have a child node defined. + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for fail node at depth '${depth}'`); + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child node of this decorator node. + validateNode(definition.child, depth + 1); +} + +/** + * Validate an object that we expect to be a flip node definition. + * @param definition An object that we expect to be a flip node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateFlipNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "flip") { + throw new Error(`expected node type of 'flip' for flip node at depth '${depth}'`); + } + + // A flip node is a decorator node, so must have a child node defined. + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for flip node at depth '${depth}'`); + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child node of this decorator node. + validateNode(definition.child, depth + 1); +} + +/** + * Validate an object that we expect to be a repeat node definition. + * @param definition An object that we expect to be a repeat node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateRepeatNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "repeat") { + throw new Error(`expected node type of 'repeat' for repeat node at depth '${depth}'`); + } + + // A repeat node is a decorator node, so must have a child node defined. + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for repeat node at depth '${depth}'`); + } + + // Check whether an 'iterations' property has been defined, it may not have been if this node is to repeat indefinitely. + if (typeof definition.iterations !== "undefined") { + if (Array.isArray(definition.iterations)) { + // Check whether any elements of the array are not integer values. + const containsNonInteger = !!definition.iterations.filter((value: unknown) => !isInteger(value)).length; + + // If the 'iterations' property is an array then it MUST contain two integer values. + if (definition.iterations.length !== 2 || containsNonInteger) { + throw new Error( + `expected array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + + // A repeat node must have a positive min and max iterations count if they are defined. + if (definition.iterations[0] < 0 || definition.iterations[1] < 0) { + throw new Error( + `expected positive minimum and maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + + // A repeat node must not have a minimum iterations count that exceeds the maximum iterations count. + if (definition.iterations[0] > definition.iterations[1]) { + throw new Error( + `expected minimum iterations count that does not exceed the maximum iterations count for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + } else if (isInteger(definition.iterations)) { + // A repeat node must have a positive number of iterations if defined. + if (definition.iterations < 0) { + throw new Error( + `expected positive iterations count for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + } else { + throw new Error( + `expected integer value or array containing two integer values for 'iterations' property if defined for repeat node at depth '${depth}'` + ); + } + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child node of this decorator node. + validateNode(definition.child, depth + 1); +} + +/** + * Validate an object that we expect to be a retry node definition. + * @param definition An object that we expect to be a retry node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateRetryNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "retry") { + throw new Error(`expected node type of 'retry' for retry node at depth '${depth}'`); + } + + // A retry node is a decorator node, so must have a child node defined. + if (typeof definition.child === "undefined") { + throw new Error(`expected property 'child' to be defined for retry node at depth '${depth}'`); + } + + // Check whether an 'attempts' property has been defined, it may not have been if this node is to retry indefinitely. + if (typeof definition.attempts !== "undefined") { + if (Array.isArray(definition.attempts)) { + // Check whether any elements of the array are not integer values. + const containsNonInteger = !!definition.attempts.filter((value: unknown) => !isInteger(value)).length; + + // If the 'attempts' property is an array then it MUST contain two integer values. + if (definition.attempts.length !== 2 || containsNonInteger) { + throw new Error( + `expected array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + + // A retry node must have a positive min and max attempts count if they are defined. + if (definition.attempts[0] < 0 || definition.attempts[1] < 0) { + throw new Error( + `expected positive minimum and maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + + // A retry node must not have a minimum attempts count that exceeds the maximum attempts count. + if (definition.attempts[0] > definition.attempts[1]) { + throw new Error( + `expected minimum attempts count that does not exceed the maximum attempts count for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + } else if (isInteger(definition.attempts)) { + // A retry node must have a positive number of attempts if defined. + if (definition.attempts < 0) { + throw new Error( + `expected positive attempts count for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + } else { + throw new Error( + `expected integer value or array containing two integer values for 'attempts' property if defined for retry node at depth '${depth}'` + ); + } + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child node of this decorator node. + validateNode(definition.child, depth + 1); +} + +/** + * Validate an object that we expect to be a branch node definition. + * @param definition An object that we expect to be a branch node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateBranchNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "branch") { + throw new Error(`expected node type of 'branch' for branch node at depth '${depth}'`); + } + + // Check that the branch node 'ref' property is defined and is a non-empty string. + if (typeof definition.ref !== "string" || definition.ref.length === 0) { + throw new Error(`expected non-empty string for 'ref' property for branch node at depth '${depth}'`); + } + + // It is invalid to define guard attributes for a branch node as they should be defined on the referenced root node. + ["while", "until"].forEach((attributeName) => { + if (typeof definition[attributeName] !== "undefined") { + throw new Error( + `guards should not be defined for branch nodes but guard '${attributeName}' was defined for branch node at depth '${depth}'` + ); + } + }); + + // It is invalid to define callback attributes for a branch node as they should be defined on the referenced root node. + ["entry", "exit", "step"].forEach((attributeName) => { + if (typeof definition[attributeName] !== "undefined") { + throw new Error( + `callbacks should not be defined for branch nodes but callback '${attributeName}' was defined for branch node at depth '${depth}'` + ); + } + }); +} + +/** + * Validate an object that we expect to be a action node definition. + * @param definition An object that we expect to be a action node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateActionNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "action") { + throw new Error(`expected node type of 'action' for action node at depth '${depth}'`); + } + + // The 'call' property must be defined for a action node definition. + if (typeof definition.call !== "string" || definition.call.length === 0) { + throw new Error(`expected non-empty string for 'call' property of action node at depth '${depth}'`); + } + + // If any action function arguments have been defined then they must have been defined in an array. + if (typeof definition.args !== "undefined" && !Array.isArray(definition.args)) { + throw new Error(`expected array for 'args' property if defined for action node at depth '${depth}'`); + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); +} + +/** + * Validate an object that we expect to be a condition node definition. + * @param definition An object that we expect to be a condition node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateConditionNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "condition") { + throw new Error(`expected node type of 'condition' for condition node at depth '${depth}'`); + } + + // The 'call' property must be defined for a condition node definition. + if (typeof definition.call !== "string" || definition.call.length === 0) { + throw new Error(`expected non-empty string for 'call' property of condition node at depth '${depth}'`); + } + + // If any condition function arguments have been defined then they must have been defined in an array. + if (typeof definition.args !== "undefined" && !Array.isArray(definition.args)) { + throw new Error(`expected array for 'args' property if defined for condition node at depth '${depth}'`); + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); +} + +/** + * Validate an object that we expect to be a wait node definition. + * @param definition An object that we expect to be a wait node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateWaitNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "wait") { + throw new Error(`expected node type of 'wait' for wait node at depth '${depth}'`); + } + + // Check whether a 'duration' property has been defined, it may not have been if this node is to wait indefinitely. + if (typeof definition.duration !== "undefined") { + if (Array.isArray(definition.duration)) { + // Check whether any elements of the array are not integer values. + const containsNonInteger = !!definition.duration.filter((value: unknown) => !isInteger(value)).length; + + // If the 'duration' property is an array then it MUST contain two integer values. + if (definition.duration.length !== 2 || containsNonInteger) { + throw new Error( + `expected array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + + // A wait node must have a positive min and max duration value if they are defined. + if (definition.duration[0] < 0 || definition.duration[1] < 0) { + throw new Error( + `expected positive minimum and maximum duration for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + + // A wait node must not have a minimum duration value that exceeds the maximum duration value. + if (definition.duration[0] > definition.duration[1]) { + throw new Error( + `expected minimum duration value that does not exceed the maximum duration value for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + } else if (isInteger(definition.duration)) { + // A wait node must have a positive duration value if defined. + if (definition.duration < 0) { + throw new Error( + `expected positive duration value for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + } else { + throw new Error( + `expected integer value or array containing two integer values for 'duration' property if defined for wait node at depth '${depth}'` + ); + } + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); +} + +/** + * Validate an object that we expect to be a sequence node definition. + * @param definition An object that we expect to be a sequence node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateSequenceNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "sequence") { + throw new Error(`expected node type of 'sequence' for sequence node at depth '${depth}'`); + } + + // A sequence node is a composite node, so must have a children nodes array defined. + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for sequence node at depth '${depth}'`); + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child nodes of this composite node. + definition.children.forEach((child: any) => validateNode(child, depth + 1)); +} + +/** + * Validate an object that we expect to be a selector node definition. + * @param definition An object that we expect to be a selector node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateSelectorNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "selector") { + throw new Error(`expected node type of 'selector' for selector node at depth '${depth}'`); + } + + // A selector node is a composite node, so must have a children nodes array defined. + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for selector node at depth '${depth}'`); + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child nodes of this composite node. + definition.children.forEach((child: any) => validateNode(child, depth + 1)); +} + +/** + * Validate an object that we expect to be a parallel node definition. + * @param definition An object that we expect to be a parallel node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateParallelNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "parallel") { + throw new Error(`expected node type of 'parallel' for parallel node at depth '${depth}'`); + } + + // A parallel node is a composite node, so must have a children nodes array defined. + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for parallel node at depth '${depth}'`); + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child nodes of this composite node. + definition.children.forEach((child: any) => validateNode(child, depth + 1)); +} + +/** + * Validate an object that we expect to be a lotto node definition. + * @param definition An object that we expect to be a lotto node definition. + * @param depth The depth of the node in the definition tree. + */ +function validateLottoNode(definition: any, depth: number): void { + // Check that the node type is correct. + if (definition.type !== "lotto") { + throw new Error(`expected node type of 'lotto' for lotto node at depth '${depth}'`); + } + + // A lotto node is a composite node, so must have a children nodes array defined. + if (!Array.isArray(definition.children) || definition.children.length === 0) { + throw new Error(`expected non-empty 'children' array to be defined for lotto node at depth '${depth}'`); + } + + // Check whether a 'weights' property has been defined, if it has we expect it to be an array of weights. + if (typeof definition.weights !== "undefined") { + // Check that the weights property is an array of positive integers with an element for each child node element. + if ( + !Array.isArray(definition.weights) || + definition.weights.length !== definition.children.length || + definition.weights.filter((value: unknown) => !isInteger(value)).length || + definition.weights.filter((value: number) => value < 0).length + ) { + throw new Error( + `expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '${depth}'` + ); + } + } + + // Validate the node attributes. + validateNodeAttributes(definition, depth); + + // Validate the child nodes of this composite node. + definition.children.forEach((child: any) => validateNode(child, depth + 1)); +} + +/** + * A helper function to create a failure validation result with the given error message. + * @param errorMessage The validation failure error message. + * @returns A failure validation result with the given error message. + */ +function createValidationFailureResult(errorMessage: string): DefinitionValidationResult { + return { succeeded: false, errorMessage }; +} diff --git a/src/Lookup.ts b/src/Lookup.ts index b67b568..e15a9c1 100644 --- a/src/Lookup.ts +++ b/src/Lookup.ts @@ -1,12 +1,7 @@ -import { ActionResult, Agent, ExitFunctionArg, FunctionArg, GlobalFunction } from "./Agent"; -import { AnyArgument, RootAstNode } from "./RootAstNodesBuilder"; +import { ActionResult, Agent, GlobalFunction } from "./Agent"; +import { RootNodeDefinition } from "./BehaviourTreeDefinition"; -// Exit callbacks receive their own special type of argument. -// There's probably stricter ways to represent this but it feels overly complex right now. -type ExitResultArg = { value: ExitFunctionArg }; -export type AnyExitArgument = AnyArgument | ExitResultArg; - -export type InvokerFunction = (args: AnyExitArgument[]) => ActionResult; +export type InvokerFunction = (args: any[]) => ActionResult | boolean; /** * A singleton used to store and lookup registered functions and subtrees. @@ -15,11 +10,11 @@ export default class Lookup { /** * The object holding any registered functions keyed on function name. */ - private static functionTable: { [key: string]: GlobalFunction } = {}; + private static registeredFunctions: { [key: string]: GlobalFunction } = {}; /** - * The object holding any registered sub-trees keyed on tree name. + * The object holding any registered subtree root node definitions keyed on tree name. */ - private static subtreeTable: { [key: string]: RootAstNode } = {}; + private static registeredSubtrees: { [key: string]: RootNodeDefinition } = {}; /** * Gets the function with the specified name. @@ -27,7 +22,7 @@ export default class Lookup { * @returns The function with the specified name. */ public static getFunc(name: string): GlobalFunction { - return this.functionTable[name]; + return this.registeredFunctions[name]; } /** @@ -36,7 +31,7 @@ export default class Lookup { * @param func The function. */ public static setFunc(name: string, func: GlobalFunction): void { - this.functionTable[name] = func; + this.registeredFunctions[name] = func; } /** @@ -49,18 +44,15 @@ export default class Lookup { */ static getFuncInvoker(agent: Agent, name: string): InvokerFunction | null { // Check whether the agent contains the specified function. - const foundOnAgent = agent[name]; - if (foundOnAgent && typeof foundOnAgent === "function") { - return (args: AnyExitArgument[]): boolean | ActionResult => - foundOnAgent.apply( - agent, - args.map((arg) => arg.value) - ); + const agentFunction = agent[name]; + if (agentFunction && typeof agentFunction === "function") { + return (args: any[]) => agentFunction.apply(agent, args); } // The agent does not contain the specified function but it may have been registered at some point. - if (this.functionTable[name] && typeof this.functionTable[name] === "function") { - return (args: AnyExitArgument[]) => this.functionTable[name](agent, ...args.map((arg) => arg.value)); + if (this.registeredFunctions[name] && typeof this.registeredFunctions[name] === "function") { + const registeredFunction = this.registeredFunctions[name]; + return (args: any[]) => registeredFunction(agent, ...args); } // We have no function to invoke. @@ -68,12 +60,10 @@ export default class Lookup { } /** - * Gets the subtree with the specified name. - * @param name The name of the subtree. - * @returns The subtree with the specified name. + * Gets all registered subtree root node definitions. */ - static getSubtree(name: string): RootAstNode { - return this.subtreeTable[name]; + static getSubtrees(): { [key: string]: RootNodeDefinition } { + return this.registeredSubtrees; } /** @@ -81,8 +71,8 @@ export default class Lookup { * @param name The name of the subtree. * @param subtree The subtree. */ - static setSubtree(name: string, subtree: RootAstNode) { - this.subtreeTable[name] = subtree; + static setSubtree(name: string, subtree: RootNodeDefinition) { + this.registeredSubtrees[name] = subtree; } /** @@ -90,15 +80,15 @@ export default class Lookup { * @param name The name of the registered function or subtree. */ static remove(name: string) { - delete this.functionTable[name]; - delete this.subtreeTable[name]; + delete this.registeredFunctions[name]; + delete this.registeredSubtrees[name]; } /** * Remove all registered functions and subtrees. */ static empty() { - this.functionTable = {}; - this.subtreeTable = {}; + this.registeredFunctions = {}; + this.registeredSubtrees = {}; } } diff --git a/src/RootAstNodesBuilder.ts b/src/RootAstNodesBuilder.ts deleted file mode 100644 index 24e74dc..0000000 --- a/src/RootAstNodesBuilder.ts +++ /dev/null @@ -1,1219 +0,0 @@ -import Action from "./nodes/leaf/Action"; -import Condition from "./nodes/leaf/Condition"; -import Wait from "./nodes/leaf/Wait"; -import Root from "./nodes/decorator/Root"; -import Repeat from "./nodes/decorator/Repeat"; -import Retry from "./nodes/decorator/Retry"; -import Flip from "./nodes/decorator/Flip"; -import Succeed from "./nodes/decorator/Succeed"; -import Fail from "./nodes/decorator/Fail"; -import Lotto from "./nodes/composite/Lotto"; -import Selector from "./nodes/composite/Selector"; -import Sequence from "./nodes/composite/Sequence"; -import Parallel from "./nodes/composite/Parallel"; -import Node from "./nodes/Node"; -import While from "./attributes/guards/While"; -import Until from "./attributes/guards/Until"; -import Entry from "./attributes/callbacks/Entry"; -import Exit from "./attributes/callbacks/Exit"; -import Step from "./attributes/callbacks/Step"; -import Callback from "./attributes/callbacks/Callback"; -import Guard from "./attributes/guards/Guard"; -import Attribute from "./attributes/Attribute"; -import Composite from "./nodes/composite/Composite"; -import Decorator from "./nodes/decorator/Decorator"; -import Leaf from "./nodes/leaf/Leaf"; - -export type Argument = { - value: T; - type: string; // Used for validation. -}; -type NullArgument = Argument & { - type: "null"; -}; -type BooleanArgument = Argument & { - type: "boolean"; -}; -type NumberArgument = Argument & { - type: "number"; - isInteger: boolean; // Used for validation. -}; -type StringPlaceholderArgument = Argument & { - type: "string"; -}; -type IdentifierArgument = Argument & { - type: "identifier"; -}; -export type AnyArgument = - | NullArgument - | BooleanArgument - | NumberArgument - | StringPlaceholderArgument - | IdentifierArgument; - -/** - * The node attribute factories. - */ -const AttributeFactories: { - [key: string]: (functionName: string, attributeArguments: AnyArgument[]) => Callback | Guard; -} = { - WHILE: (condition: string, attributeArguments: AnyArgument[]) => new While(condition, attributeArguments), - UNTIL: (condition: string, attributeArguments: AnyArgument[]) => new Until(condition, attributeArguments), - ENTRY: (functionName: string, attributeArguments: AnyArgument[]) => new Entry(functionName, attributeArguments), - EXIT: (functionName: string, attributeArguments: AnyArgument[]) => new Exit(functionName, attributeArguments), - STEP: (functionName: string, attributeArguments: AnyArgument[]) => new Step(functionName, attributeArguments) -}; - -type Validatable = { - children?: AstNode[]; - validate: (depth: number) => void; -}; - -type NodeInstanceCreator = ( - namedRootNodeProvider: (name: string) => RootAstNode, - visitedBranches: string[] -) => T; - -export type AstNode = Validatable & { - type: string; - createNodeInstance: NodeInstanceCreator; -}; - -export type LeafAstNode = AstNode & { - type: "action" | "condition" | "wait"; - attributes: Attribute[]; -}; - -export type CompositeAstNode = AstNode & { - type: "lotto" | "parallel" | "selector" | "sequence"; - attributes: Attribute[]; - children: AstNode[]; -}; - -export type DecoratorAstNode = AstNode & { - type: "fail" | "flip" | "repeat" | "retry" | "root" | "succeed"; - attributes: Attribute[]; - children: AstNode[]; -}; - -export type BranchAstNode = AstNode & { - type: "branch"; - branchName: "" | string; -}; - -export type LottoAstNode = CompositeAstNode & { - type: "lotto"; - tickets: number[]; -}; - -export type RootAstNode = DecoratorAstNode & { - type: "root"; - name: null | string; -}; - -export type RepeatAstNode = DecoratorAstNode & { - type: "repeat"; - iterations: number | null; - iterationsMin: number | null; - iterationsMax: number | null; -}; - -export type RetryAstNode = DecoratorAstNode & { - type: "retry"; - attempts: number | null; - attemptsMin: number | null; - attemptsMax: number | null; -}; - -export type ActionAstNode = LeafAstNode & { - type: "action"; - actionName: string; - actionArguments: AnyArgument[]; -}; - -export type ConditionAstNode = LeafAstNode & { - type: "condition"; - conditionName: string; - conditionArguments: AnyArgument[]; -}; - -export type WaitAstNode = LeafAstNode & { - type: "wait"; - duration: number | null; - durationMin: number | null; - durationMax: number | null; -}; - -export type AnyAstNode = - | BranchAstNode - | CompositeAstNode - | LottoAstNode - | DecoratorAstNode - | RootAstNode - | RepeatAstNode - | RetryAstNode - | LeafAstNode - | ActionAstNode - | ConditionAstNode - | WaitAstNode; - -/** - * The AST node factories. - */ -const ASTNodeFactories = { - ROOT: (): RootAstNode => ({ - type: "root", - attributes: [], - name: null, - children: [], - validate(depth: number) { - // A root node cannot be the child of another node. - if (depth > 1) { - throw new Error("a root node cannot be the child of another node"); - } - - // A root node must have a single child node. - if (this.children.length !== 1) { - throw new Error("a root node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Root( - this.attributes, - this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) - ); - } - }), - BRANCH: (): BranchAstNode => ({ - type: "branch", - branchName: "", - validate() {}, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - // Try to find the root node with a matching branch name. - const targetRootNode = namedRootNodeProvider(this.branchName); - - // If we have already visited this branch then we have a circular dependency. - if (visitedBranches.indexOf(this.branchName) !== -1) { - throw new Error(`circular dependency found in branch node references for branch '${this.branchName}'`); - } - - // If we have a target root node, then the node instance we want will be the first and only child of the referenced root node. - if (targetRootNode) { - return targetRootNode - .createNodeInstance(namedRootNodeProvider, visitedBranches.concat(this.branchName)) - .getChildren()[0]; - } else { - throw new Error(`branch references root node '${this.branchName}' which has not been defined`); - } - } - }), - SELECTOR: (): CompositeAstNode => ({ - type: "selector", - attributes: [], - children: [], - validate() { - // A selector node must have at least a single node. - if (this.children.length < 1) { - throw new Error("a selector node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Selector( - this.attributes, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - SEQUENCE: (): CompositeAstNode => ({ - type: "sequence", - attributes: [], - children: [], - validate() { - // A sequence node must have at least a single node. - if (this.children.length < 1) { - throw new Error("a sequence node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Sequence( - this.attributes, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - PARALLEL: (): CompositeAstNode => ({ - type: "parallel", - attributes: [], - children: [], - validate() { - // A parallel node must have at least a single node. - if (this.children.length < 1) { - throw new Error("a parallel node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Parallel( - this.attributes, - this.children.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - LOTTO: (): LottoAstNode => ({ - type: "lotto", - attributes: [], - children: [], - tickets: [], - validate() { - // A lotto node must have at least a single node. - if (this.children!.length < 1) { - throw new Error("a lotto node must have at least a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Lotto( - this.attributes, - this.tickets!, - this.children!.map((child) => child.createNodeInstance(namedRootNodeProvider, visitedBranches.slice())) - ); - } - }), - REPEAT: (): RepeatAstNode => ({ - type: "repeat", - attributes: [], - iterations: null, - iterationsMin: null, - iterationsMax: null, - children: [], - validate() { - // A repeat node must have a single node. - if (this.children!.length !== 1) { - throw new Error("a repeat node must have a single child"); - } - - if (this.iterations !== null) { - // A repeat node must have a positive number of iterations if defined. - if (this.iterations < 0) { - throw new Error("a repeat node must have a positive number of iterations if defined"); - } - } else if (this.iterationsMin !== null && this.iterationsMax !== null) { - // A repeat node must have a positive min and max iteration count if they are defined. - if (this.iterationsMin < 0 || this.iterationsMax < 0) { - throw new Error( - "a repeat node must have a positive minimum and maximum iteration count if defined" - ); - } - - // A repeat node must not have an minimum iteration count that exceeds the maximum iteration count. - if (this.iterationsMin > this.iterationsMax) { - throw new Error( - "a repeat node must not have a minimum iteration count that exceeds the maximum iteration count" - ); - } - } else { - // If we have no explicit iteration count or a minimum and maximum iteration count set then we are dealing with a repeat node that iterates indefinitely. - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Repeat( - this.attributes, - this.iterations, - this.iterationsMin, - this.iterationsMax, - this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) - ); - } - }), - RETRY: (): RetryAstNode => ({ - type: "retry", - attributes: [], - attempts: null, - attemptsMin: null, - attemptsMax: null, - children: [], - validate() { - // A retry node must have a single node. - if (this.children!.length !== 1) { - throw new Error("a retry node must have a single child"); - } - - if (this.attempts !== null) { - // A retry node must have a positive number of attempts if defined. - if (this.attempts < 0) { - throw new Error("a retry node must have a positive number of attempts if defined"); - } - } else if (this.attemptsMin !== null && this.attemptsMax !== null) { - // A retry node must have a positive min and max attempts count if they are defined. - if (this.attemptsMin < 0 || this.attemptsMax < 0) { - throw new Error("a retry node must have a positive minimum and maximum attempt count if defined"); - } - - // A retry node must not have a minimum attempt count that exceeds the maximum attempt count. - if (this.attemptsMin > this.attemptsMax) { - throw new Error( - "a retry node must not have a minimum attempt count that exceeds the maximum attempt count" - ); - } - } else { - // If we have no explicit attempt count or a minimum and maximum attempt count set then we are dealing with a retry node that attempts indefinitely. - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Retry( - this.attributes, - this.attempts, - this.attemptsMin, - this.attemptsMax, - this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) - ); - } - }), - FLIP: (): DecoratorAstNode => ({ - type: "flip", - attributes: [], - children: [], - validate() { - // A flip node must have a single node. - if (this.children!.length !== 1) { - throw new Error("a flip node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Flip( - this.attributes, - this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) - ); - } - }), - SUCCEED: (): DecoratorAstNode => ({ - type: "succeed", - attributes: [], - children: [], - validate() { - // A succeed node must have a single node. - if (this.children!.length !== 1) { - throw new Error("a succeed node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Succeed( - this.attributes, - this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) - ); - } - }), - FAIL: (): DecoratorAstNode => ({ - type: "fail", - attributes: [], - children: [], - validate() { - // A fail node must have a single node. - if (this.children!.length !== 1) { - throw new Error("a fail node must have a single child"); - } - }, - createNodeInstance(namedRootNodeProvider, visitedBranches) { - return new Fail( - this.attributes, - this.children![0].createNodeInstance(namedRootNodeProvider, visitedBranches.slice()) - ); - } - }), - WAIT: (): WaitAstNode => ({ - type: "wait", - attributes: [], - duration: null, - durationMin: null, - durationMax: null, - validate() { - if (this.duration !== null) { - // If an explict duration was defined then it must be a positive number. - if (this.duration < 0) { - throw new Error("a wait node must have a positive duration"); - } - } else if (this.durationMin !== null && this.durationMax !== null) { - // A wait node must have a positive min and max duration. - if (this.durationMin < 0 || this.durationMax < 0) { - throw new Error("a wait node must have a positive minimum and maximum duration"); - } - - // A wait node must not have a minimum duration that exceeds the maximum duration. - if (this.durationMin > this.durationMax) { - throw new Error("a wait node must not have a minimum duration that exceeds the maximum duration"); - } - } else { - // If we have no explicit duration or duration bounds set then we are dealing with a wait node that waits indefinitely. - } - }, - createNodeInstance() { - return new Wait(this.attributes, this.duration, this.durationMin, this.durationMax); - } - }), - ACTION: (): ActionAstNode => ({ - type: "action", - attributes: [], - actionName: "", - actionArguments: [], - validate() {}, - createNodeInstance() { - return new Action(this.attributes, this.actionName!, this.actionArguments!); - } - }), - CONDITION: (): ConditionAstNode => ({ - type: "condition", - attributes: [], - conditionName: "", - conditionArguments: [], - validate() {}, - createNodeInstance() { - return new Condition(this.attributes, this.conditionName!, this.conditionArguments!); - } - }) -}; - -type OtherAstNodes = AstNode[]; - -/** - * Create an array of root AST nodes based on the given definition. - * @param definition The definition to parse the AST nodes from. - * @returns The base definition AST nodes. - */ -export default function buildRootASTNodes(definition: string): RootAstNode[] { - // Swap out any node/attribute argument string literals with a placeholder and get a mapping of placeholders to original values as well as the processed definition. - const { placeholders, processedDefinition } = substituteStringLiterals(definition); - - // Convert the processed definition (with substituted string literals) into an array of raw tokens. - const tokens = parseTokensFromDefinition(processedDefinition); - - // There must be at least 3 tokens for the tree definition to be valid. 'ROOT', '{' and '}'. - if (tokens.length < 3) { - throw new Error("invalid token count"); - } - - // We should have a matching number of '{' and '}' tokens. If not, then there are scopes that have not been properly closed. - if (tokens.filter((token) => token === "{").length !== tokens.filter((token) => token === "}").length) { - throw new Error("scope character mismatch"); - } - - // Create a stack of node children arrays, starting with a definition scope. - const stack: [RootAstNode[], ...OtherAstNodes[]] = [[]]; - const rootScope = stack[0]; - - // We should keep processing the raw tokens until we run out of them. - while (tokens.length) { - // Grab the next token. - const token = tokens.shift(); - - const currentScope = stack[stack.length - 1] as OtherAstNodes; - - // How we create the next AST token depends on the current raw token value. - switch (token!.toUpperCase()) { - case "ROOT": { - // Create a ROOT AST node. - const node = ASTNodeFactories.ROOT(); - - // Push the ROOT node into the current scope. - rootScope.push(node); - - // We may have a root node name defined as an argument. - if (tokens[0] === "[") { - const rootArguments = getArguments(tokens, placeholders); - - // We should have only a single argument that is not an empty string for a root node, which is the root name identifier. - if (rootArguments.length === 1 && rootArguments[0].type === "identifier") { - // The root name will be the first and only node argument. - node.name = rootArguments[0].value as string; - } else { - throw new Error("expected single root name argument"); - } - } - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new ROOT nodes children. - stack.push(node.children!); - break; - } - - case "BRANCH": { - // Create a BRANCH AST node. - const node = ASTNodeFactories.BRANCH(); - - // Push the BRANCH node into the current scope. - currentScope.push(node); - - // We must have arguments defined, as we require a branch name argument. - if (tokens[0] !== "[") { - throw new Error("expected single branch name argument"); - } - - // The branch name will be defined as a node argument. - const branchArguments = getArguments(tokens, placeholders); - - // We should have only a single identifer argument for a branch node, which is the branch name. - if (branchArguments.length === 1 && branchArguments[0].type === "identifier") { - // The branch name will be the first and only node argument. - node.branchName = branchArguments[0].value as string; - } else { - throw new Error("expected single branch name argument"); - } - break; - } - - case "SELECTOR": { - // Create a SELECTOR AST node. - const node = ASTNodeFactories.SELECTOR(); - - // Push the SELECTOR node into the current scope. - currentScope.push(node); - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new SELECTOR nodes children. - stack.push(node.children!); - break; - } - - case "SEQUENCE": { - // Create a SEQUENCE AST node. - const node = ASTNodeFactories.SEQUENCE(); - - // Push the SEQUENCE node into the current scope. - currentScope.push(node); - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new SEQUENCE nodes children. - stack.push(node.children!); - break; - } - - case "PARALLEL": { - // Create a PARALLEL AST node. - const node = ASTNodeFactories.PARALLEL(); - - // Push the PARALLEL node into the current scope. - currentScope.push(node); - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new PARALLEL nodes children. - stack.push(node.children!); - break; - } - - case "LOTTO": { - // Create a LOTTO AST node. - const node = ASTNodeFactories.LOTTO(); - - // Push the LOTTO node into the current scope. - currentScope.push(node); - - // If the next token is a '[' character then some ticket counts have been defined as arguments. - if (tokens[0] === "[") { - // Get the ticket count arguments, each argument must be a number. - node.tickets = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "lotto node ticket counts must be integer values" - ).map((argument) => argument.value as number); - } - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new LOTTO nodes children. - stack.push(node.children!); - break; - } - - case "CONDITION": { - // Create a CONDITION AST node. - const node = ASTNodeFactories.CONDITION(); - - // Push the CONDITION node into the current scope. - currentScope.push(node); - - // We must have arguments defined, as we require a condition function name argument. - if (tokens[0] !== "[") { - throw new Error("expected condition name identifier argument"); - } - - // Grab the condition node arguments. - const conditionArguments = getArguments(tokens, placeholders); - - // We should have at least a single identifier argument for a condition node, which is the condition function name. - if (conditionArguments.length && conditionArguments[0].type === "identifier") { - // The condition function name will be the first node argument. - node.conditionName = conditionArguments.shift()!.value as string; - } else { - throw new Error("expected condition name identifier argument"); - } - - // Only the first argument should have been an identifier, all following arguments must be string, number, boolean or null. - conditionArguments - .filter((arg) => arg.type === "identifier") - .forEach((arg) => { - throw new Error( - "invalid condition node argument value '" + - arg.value + - "', must be string, number, boolean or null" - ); - }); - - // Any node arguments that follow the condition name identifier will be treated as condition function arguments. - node.conditionArguments = conditionArguments; - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - break; - } - - case "FLIP": { - // Create a FLIP AST node. - const node = ASTNodeFactories.FLIP(); - - // Push the Flip node into the current scope. - currentScope.push(node); - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new FLIP nodes children. - stack.push(node.children!); - break; - } - - case "SUCCEED": { - // Create a SUCCEED AST node. - const node = ASTNodeFactories.SUCCEED(); - - // Push the Succeed node into the current scope. - currentScope.push(node); - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new Succeed nodes children. - stack.push(node.children!); - break; - } - - case "FAIL": { - // Create a FAIL AST node. - const node = ASTNodeFactories.FAIL(); - - // Push the Fail node into the current scope. - currentScope.push(node); - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new Fail nodes children. - stack.push(node.children!); - break; - } - - case "WAIT": { - // Create a WAIT AST node. - const node = ASTNodeFactories.WAIT(); - - // Push the WAIT node into the current scope. - currentScope.push(node); - - // The arguments of a wait node are optional. We may have: - // - No node arguments, in which case the wait will be indefinite until it is aborted. - // - One node argument which will be the explicit duration of the wait. - // - Two node arguments which define the min and max duration bounds from which a random duration will be picked. - if (tokens[0] === "[") { - // Get the optional duration and longest duration of the wait. - const nodeArguments = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "wait node durations must be integer values" - ).map((argument) => argument.value); - - // We may have: - // - One node argument which will be the explicit duration of the wait. - // - Two node arguments which define the min and max duration bounds from which a random duration will be picked. - // - Too many arguments, which is not valid. - if (nodeArguments.length === 1) { - // An explicit duration was defined. - node.duration = nodeArguments[0] as number; - } else if (nodeArguments.length === 2) { - // Min and max duration bounds were defined from which a random duration will be picked. - node.durationMin = nodeArguments[0] as number; - node.durationMax = nodeArguments[1] as number; - } else if (nodeArguments.length > 2) { - // An incorrect number of durations was defined. - throw new Error("invalid number of wait node duration arguments defined"); - } - } - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - break; - } - - case "REPEAT": { - // Create a REPEAT AST node. - const node = ASTNodeFactories.REPEAT(); - - // Push the REPEAT node into the current scope. - currentScope.push(node); - - // The arguments of a repeat node are optional. We may have: - // - No node arguments, in which case the repeat note will iterate indefinitely. - // - One node argument which will be the explicit number of iterations to make. - // - Two node arguments which define the min and max iteration bounds from which a random iteration count will be picked. - if (tokens[0] === "[") { - // An iteration count has been defined. Get the iteration and potential maximum iteration of the wait. - const nodeArguments = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "repeat node iteration counts must be integer values" - ).map((argument) => argument.value); - - // We should have got one or two iteration counts. - if (nodeArguments.length === 1) { - // A static iteration count was defined. - node.iterations = nodeArguments[0] as number; - } else if (nodeArguments.length === 2) { - // A minimum and maximum iteration count was defined. - node.iterationsMin = nodeArguments[0] as number; - node.iterationsMax = nodeArguments[1] as number; - } else { - // An incorrect number of iteration counts was defined. - throw new Error("invalid number of repeat node iteration count arguments defined"); - } - } - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new REPEAT nodes children. - stack.push(node.children!); - break; - } - - case "RETRY": { - // Create a RETRY AST node. - const node = ASTNodeFactories.RETRY(); - - // Push the RETRY node into the current scope. - currentScope.push(node); - - // The arguments of a retry node are optional. We may have: - // - No node arguments, in which case the retry note will attempt indefinitely. - // - One node argument which will be the explicit number of attempts to make. - // - Two node arguments which define the min and max attempt bounds from which a random attempt count will be picked. - if (tokens[0] === "[") { - // An attempt count has been defined. Get the attempt count and potential maximum attempt count of the wait. - const nodeArguments = getArguments( - tokens, - placeholders, - (arg) => arg.type === "number" && !!arg.isInteger, - "retry node attempt counts must be integer values" - ).map((argument) => argument.value); - - // We should have got one or two attempt counts. - if (nodeArguments.length === 1) { - // A static attempt count was defined. - node.attempts = nodeArguments[0] as number; - } else if (nodeArguments.length === 2) { - // A minimum and maximum attempt count was defined. - node.attemptsMin = nodeArguments[0] as number; - node.attemptsMax = nodeArguments[1] as number; - } else { - // An incorrect number of attempt counts was defined. - throw new Error("invalid number of retry node attempt count arguments defined"); - } - } - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - - popAndCheck(tokens, "{"); - - // The new scope is that of the new RETRY nodes children. - stack.push(node.children!); - break; - } - - case "ACTION": { - // Create a ACTION AST node. - const node = ASTNodeFactories.ACTION(); - - // Push the ACTION node into the current scope. - currentScope.push(node); - - // We must have arguments defined, as we require an action name argument. - if (tokens[0] !== "[") { - throw new Error("expected action name identifier argument"); - } - - // The action name will be defined as a node argument. - const actionArguments = getArguments(tokens, placeholders); - - // We should have at least one identifer argument for an action node, which is the action name. - if (actionArguments.length && actionArguments[0].type === "identifier") { - // The action name will be the first and only node argument. - node.actionName = actionArguments.shift()!.value as string; - } else { - throw new Error("expected action name identifier argument"); - } - - // Only the first argument should have been an identifier, all following arguments must be string, number, boolean or null. - actionArguments - .filter((arg) => arg.type === "identifier") - .forEach((arg) => { - throw new Error( - "invalid action node argument value '" + - arg.value + - "', must be string, number, boolean or null" - ); - }); - - // Any node arguments that follow the action name identifier will be treated as action function arguments. - node.actionArguments = actionArguments; - - // Try to pick any attributes off of the token stack. - node.attributes = getAttributes(tokens, placeholders); - break; - } - - case "}": { - // The '}' character closes the current scope. - stack.pop(); - break; - } - - default: { - throw new Error(`unexpected token '${token}'`); - } - } - } - - // A function to recursively validate each of the nodes in the AST. - const validateASTNode = (node: Validatable, depth: number): void => { - // Validate the node. - node.validate(depth); - - // Validate each child of the node. - (node.children || []).forEach((child) => validateASTNode(child, depth + 1)); - }; - - // Start node validation from the definition root. - validateASTNode( - { - children: stack[0] as RootAstNode[], - validate(this: { children: RootAstNode[] }) { - // We must have at least one node defined as the definition scope, which should be a root node. - if (this.children.length === 0) { - throw new Error("expected root node to have been defined"); - } - - // Each node at the base of the definition scope MUST be a root node. - for (const definitionLevelNode of this.children) { - if (definitionLevelNode.type !== "root") { - throw new Error("expected root node at base of definition"); - } - } - - // Exactly one root node must not have a name defined. This will be the main root, others will have to be referenced via branch nodes. - if (this.children.filter((definitionLevelNode) => definitionLevelNode.name === null).length !== 1) { - throw new Error("expected single unnamed root node at base of definition to act as main root"); - } - - // No two named root nodes can have matching names. - const rootNodeNames: string[] = []; - for (const definitionLevelNode of this.children) { - if (rootNodeNames.indexOf(definitionLevelNode.name!) !== -1) { - throw new Error(`multiple root nodes found with duplicate name '${definitionLevelNode.name}'`); - } else { - rootNodeNames.push(definitionLevelNode.name!); - } - } - } - }, - 0 - ); - - // Return the root AST nodes. - return stack[0]; -} - -/** - * Pop the next raw token off of the stack and throw an error if it wasn't the expected one. - * @param tokens The array of remaining tokens. - * @param expected An optional string or array or items, one of which must match the next popped token. - * @returns The popped token. - */ -function popAndCheck(tokens: string[], expected: string | string[]) { - // Get and remove the next token. - const popped = tokens.shift(); - - // We were expecting another token. - if (popped === undefined) { - throw new Error("unexpected end of definition"); - } - - // Do we have an expected token/tokens array? - if (expected !== undefined) { - // Check whether the popped token matches at least one of our expected items. - var tokenMatchesExpectation = ([] as string[]) - .concat(expected) - .some((item) => popped.toUpperCase() === item.toUpperCase()); - - // Throw an error if the popped token didn't match any of our expected items. - if (!tokenMatchesExpectation) { - const expectationString = ([] as string[]) - .concat(expected) - .map((item) => "'" + item + "'") - .join(" or "); - - throw new Error(`unexpected token found. Expected '${expectationString}' but got '${popped}'`); - } - } - - // Return the popped token. - return popped; -} - -type Placeholders = { [key: string]: string }; - -/** - * Pull an argument definition list off of the token stack. - * @param tokens The array of remaining tokens. - * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values. - * @param argumentValidator The argument validator function. - * @param validationFailedMessage The exception message to throw if argument validation fails. - * @returns The argument definition list. - */ -function getArguments( - tokens: string[], - stringArgumentPlaceholders: Placeholders, - argumentValidator?: (arg: AnyArgument) => boolean, - validationFailedMessage?: string -) { - // Any lists of arguments will always be wrapped in '[]' for node arguments or '()' for attribute arguments. - // We are looking for a '[' or '(' opener that wraps the argument tokens and the relevant closer. - const closer = popAndCheck(tokens, ["[", "("]) === "[" ? "]" : ")"; - - const argumentListTokens: string[] = []; - const argumentList: AnyArgument[] = []; - - // Grab all tokens between the '[' and ']' or '(' and ')'. - while (tokens.length && tokens[0] !== closer) { - // The next token is part of our arguments list. - argumentListTokens.push(tokens.shift()!); - } - - // Validate the order of the argument tokens. Each token must either be a ',' or a single argument that satisfies the validator. - argumentListTokens.forEach((token, index) => { - // Get whether this token should be an actual argument. - const shouldBeArgumentToken = !(index & 1); - - // If the current token should be an actual argument then validate it,otherwise it should be a ',' token. - if (shouldBeArgumentToken) { - // Get the argument definition. - const argumentDefinition = getArgumentDefinition(token!, stringArgumentPlaceholders); - - // Try to validate the argument. - if (argumentValidator && !argumentValidator(argumentDefinition)) { - throw new Error(validationFailedMessage); - } - - // This is a valid argument! - argumentList.push(argumentDefinition); - } else { - // The current token should be a ',' token. - if (token !== ",") { - throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`); - } - } - }); - - // The arguments list should terminate with a ']' or ')' token, depending on the opener. - popAndCheck(tokens, closer); - - // Return the argument list. - return argumentList; -} - -/** - * Gets an argument value definition. - * @param token The argument token. - * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values. - * @returns An argument value definition. - */ -function getArgumentDefinition(token: string, stringArgumentPlaceholders: Placeholders): AnyArgument { - // Check whether the token represents a null value. - if (token === "null") { - return { - value: null, - type: "null" - } as NullArgument; - } - - // Check whether the token represents a boolean value. - if (token === "true" || token === "false") { - return { - value: token === "true", - type: "boolean" - } as BooleanArgument; - } - - // Check whether the token represents a number value. - // TODO: Relies on broken isNaN - see MDN. - // if (!Number.isNaN(token)) { - if (!isNaN(token as any)) { - return { - value: parseFloat(token), - isInteger: parseFloat(token) === parseInt(token, 10), - type: "number" - } as NumberArgument; - } - - // Check whether the token is a placeholder (e.g. @@0@@) representing a string literal. - if (token.match(/^@@\d+@@$/g)) { - return { - value: stringArgumentPlaceholders[token].replace('\\"', '"'), - type: "string" - } as StringPlaceholderArgument; - } - - // The only remaining option is that the argument value is an identifier. - return { - value: token, - type: "identifier" - } as IdentifierArgument; -} - -/** - * Pull any attributes off of the token stack. - * @param tokens The array of remaining tokens. - * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values. - * @returns An array of attributes defined by any directly following tokens. - */ -function getAttributes(tokens: string[], stringArgumentPlaceholders: Placeholders) { - // Create an array to hold any attributes found. - const attributes: Attribute[] = []; - - // Keep track of names of attribute that we have found on the token stack, as we cannot have duplicates. - const attributesFound: string[] = []; - - // Try to get the attribute factory for the next token. - let attributeFactory = AttributeFactories[(tokens[0] || "").toUpperCase()]; - - // Pull attribute tokens off of the tokens stack until we have no more. - while (attributeFactory) { - // Check to make sure that we have not already created a attribute of this type for this node. - if (attributesFound.indexOf(tokens[0].toUpperCase()) !== -1) { - throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`); - } - - // Add the current attribute type to our array of found attributes. - attributesFound.push(tokens.shift()!.toUpperCase()); - - // Grab any attribute arguments. - const attributeArguments = getArguments(tokens, stringArgumentPlaceholders); - - // The first attribute argument has to be an identifer, this will reference an agent function. - if (attributeArguments.length === 0 || attributeArguments[0].type !== "identifier") { - throw new Error("expected agent function name identifier argument for attribute"); - } - - // Grab the first attribute which is an identifier that will reference an agent function. - const attributeFunctionName = attributeArguments.shift()! as IdentifierArgument; - - // Any remaining attribute arguments must have a type of string, number, boolean or null. - attributeArguments - .filter((arg) => arg.type === "identifier") - .forEach((arg) => { - throw new Error( - "invalid attribute argument value '" + arg.value + "', must be string, number, boolean or null" - ); - }); - - // Create the attribute and add it to the array of attributes found. - attributes.push(attributeFactory(attributeFunctionName.value, attributeArguments)); - - // Try to get the next attribute name token, as there could be multiple. - attributeFactory = AttributeFactories[(tokens[0] || "").toUpperCase()]; - } - - return attributes; -} - -/** - * Swaps out any node/attribute argument string literals with placeholders. - * @param definition The definition. - * @returns An object containing a mapping of placeholders to original string values as well as the processed definition string. - */ -function substituteStringLiterals(definition: string): { - placeholders: { [key: string]: string }; - processedDefinition: string; -} { - // Create an object to hold the mapping of placeholders to original string values. - const placeholders: Placeholders = {}; - - // Replace any string literals wrapped with double quotes in our definition with placeholders to be processed later. - const processedDefinition = definition.replace(/\"(\\.|[^"\\])*\"/g, (match) => { - var strippedMatch = match.substring(1, match.length - 1); - var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch); - - // If we have no existing string literal match then create a new placeholder. - if (!placeholder) { - placeholder = `@@${Object.keys(placeholders).length}@@`; - placeholders[placeholder] = strippedMatch; - } - - return placeholder; - }); - - return { placeholders, processedDefinition }; -} - -/** - * Parse the tree definition into an array of raw tokens. - * @param definition The definition. - * @returns An array of tokens parsed from the definition. - */ -function parseTokensFromDefinition(definition: string): string[] { - // Add some space around various important characters so that they can be plucked out easier as individual tokens. - definition = definition.replace(/\(/g, " ( "); - definition = definition.replace(/\)/g, " ) "); - definition = definition.replace(/\{/g, " { "); - definition = definition.replace(/\}/g, " } "); - definition = definition.replace(/\]/g, " ] "); - definition = definition.replace(/\[/g, " [ "); - definition = definition.replace(/\,/g, " , "); - - // Split the definition into raw token form and return it. - return definition.replace(/\s+/g, " ").trim().split(" "); -} diff --git a/src/attributes/Attribute.ts b/src/attributes/Attribute.ts index e72953f..82803f0 100644 --- a/src/attributes/Attribute.ts +++ b/src/attributes/Attribute.ts @@ -1,4 +1,3 @@ -import { AnyArgument } from "../RootAstNodesBuilder"; import Guard from "./guards/Guard"; export type AttributeDetails = { @@ -6,7 +5,7 @@ export type AttributeDetails = { type: string; /** The attribute arguments. */ - args: AnyArgument[]; + args: any[]; }; /** @@ -15,19 +14,9 @@ export type AttributeDetails = { export default abstract class Attribute { /** * @param type The node attribute type. - * @param args The array of attribute argument definitions. + * @param args The array of attribute arguments. */ - constructor(protected type: string, protected args: AnyArgument[]) {} - - /** - * Gets the type of the attribute. - */ - getType = () => this.type; - - /** - * Gets the array of attribute argument definitions. - */ - getArguments = () => this.args; + constructor(public type: string, public args: any[]) {} /** * Gets the attribute details. diff --git a/src/attributes/callbacks/Callback.ts b/src/attributes/callbacks/Callback.ts index b8b9707..c262690 100644 --- a/src/attributes/callbacks/Callback.ts +++ b/src/attributes/callbacks/Callback.ts @@ -1,5 +1,4 @@ import { Agent } from "../../Agent"; -import { AnyArgument } from "../../RootAstNodesBuilder"; import Attribute, { AttributeDetails } from "../Attribute"; export type CallbackAttributeDetails = { @@ -16,7 +15,7 @@ export default abstract class Callback extends Attribute { * @param args The array of decorator argument definitions. * @param condition The name of the condition function that determines whether the guard is satisfied. */ - constructor(type: string, args: AnyArgument[], private condition: string) { + constructor(type: string, args: any[], private condition: string) { super(type, args); } @@ -35,8 +34,8 @@ export default abstract class Guard extends Attribute { */ getDetails(): GuardAttributeDetails { return { - type: this.getType(), - args: this.getArguments(), + type: this.type, + args: this.args, condition: this.getCondition() }; } diff --git a/src/attributes/guards/Until.ts b/src/attributes/guards/Until.ts index bc9f313..ed3d569 100644 --- a/src/attributes/guards/Until.ts +++ b/src/attributes/guards/Until.ts @@ -1,7 +1,6 @@ import Guard from "./Guard"; import Lookup from "../../Lookup"; import { Agent } from "../../Agent"; -import { AnyArgument } from "../../RootAstNodesBuilder"; /** * An UNTIL guard which is satisfied as long as the given condition remains false. @@ -11,7 +10,7 @@ export default class Until extends Guard { * @param condition The name of the condition function that determines whether the guard is satisfied. * @param args The array of decorator argument definitions. */ - constructor(condition: string, args: AnyArgument[]) { + constructor(condition: string, args: any[]) { super("until", args, condition); } @@ -31,7 +30,28 @@ export default class Until extends Guard { ); } - // Call the condition function to determine whether this guard is satisfied. - return !!!conditionFuncInvoker(this.args); + let conditionFunctionResult; + + try { + // Call the guard condition function to determine the state of this node, the result of which should be a boolean. + conditionFunctionResult = conditionFuncInvoker(this.args); + } catch (error) { + // An uncaught error was thrown. + if (error instanceof Error) { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`); + } else { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`); + } + } + + // The result of calling the guard condition function must be a boolean value. + if (typeof conditionFunctionResult !== "boolean") { + throw new Error( + `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'` + ); + } + + // Return whether this guard is satisfied. + return !conditionFunctionResult; }; } diff --git a/src/attributes/guards/While.ts b/src/attributes/guards/While.ts index 564277a..2dc7fe1 100644 --- a/src/attributes/guards/While.ts +++ b/src/attributes/guards/While.ts @@ -1,7 +1,6 @@ import Guard from "./Guard"; import Lookup from "../../Lookup"; import { Agent } from "../../Agent"; -import { AnyArgument } from "../../RootAstNodesBuilder"; /** * A WHILE guard which is satisfied as long as the given condition remains true. @@ -11,7 +10,7 @@ export default class While extends Guard { * @param condition The name of the condition function that determines whether the guard is satisfied. * @param args The array of decorator argument definitions. */ - constructor(condition: string, args: AnyArgument[]) { + constructor(condition: string, args: any[]) { super("while", args, condition); } @@ -31,7 +30,28 @@ export default class While extends Guard { ); } - // Call the condition function to determine whether this guard is satisfied. - return !!conditionFuncInvoker(this.args); + let conditionFunctionResult; + + try { + // Call the guard condition function to determine the state of this node, the result of which should be a boolean. + conditionFunctionResult = conditionFuncInvoker(this.args); + } catch (error) { + // An uncaught error was thrown. + if (error instanceof Error) { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error.stack}`); + } else { + throw new Error(`guard condition function '${this.getCondition()}' threw: ${error}`); + } + } + + // The result of calling the guard condition function must be a boolean value. + if (typeof conditionFunctionResult !== "boolean") { + throw new Error( + `expected guard condition function '${this.getCondition()}' to return a boolean but returned '${conditionFunctionResult}'` + ); + } + + // Return whether this guard is satisfied. + return conditionFunctionResult; }; } diff --git a/src/index.ts b/src/index.ts index d0e1068..b47a3fa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,8 @@ -import { BehaviourTree, FlattenedTreeNode } from "./BehaviourTree"; import State from "./State"; +import { validateDefinition } from "./BehaviourTreeDefinitionValidator"; +import { convertMDSLToJSON } from "./mdsl/MDSLDefinitionParser"; +import { BehaviourTree, FlattenedTreeNode } from "./BehaviourTree"; +import { BehaviourTreeOptions } from "./BehaviourTreeOptions"; -export { BehaviourTree, State }; -export type { FlattenedTreeNode }; +export { BehaviourTree, State, convertMDSLToJSON, validateDefinition }; +export type { FlattenedTreeNode, BehaviourTreeOptions }; diff --git a/src/mdsl/MDSLDefinitionParser.ts b/src/mdsl/MDSLDefinitionParser.ts new file mode 100644 index 0000000..e5f6158 --- /dev/null +++ b/src/mdsl/MDSLDefinitionParser.ts @@ -0,0 +1,762 @@ +import { + ActionNodeDefinition, + AnyChildNodeDefinition, + AnyNodeDefinition, + BranchNodeDefinition, + ConditionNodeDefinition, + FailNodeDefinition, + FlipNodeDefinition, + LottoNodeDefinition, + ParallelNodeDefinition, + RepeatNodeDefinition, + RetryNodeDefinition, + RootNodeDefinition, + SelectorNodeDefinition, + SequenceNodeDefinition, + SucceedNodeDefinition, + WaitNodeDefinition +} from "../BehaviourTreeDefinition"; +import { + isCompositeNode, + isDecoratorNode, + isLeafNode, + isNullOrUndefined, + isRootNode +} from "../BehaviourTreeDefinitionUtilities"; +import { parseArgumentTokens } from "./MDSLNodeArgumentParser"; +import { parseAttributeTokens } from "./MDSLNodeAttributeParser"; +import { + StringLiteralPlaceholders, + parseTokensFromDefinition, + popAndCheck, + substituteStringLiterals +} from "./MDSLUtilities"; + +/** + * Convert the MDSL tree definition string into an equivalent JSON definition. + * @param definition The tree definition string as MDSL. + * @returns The root node JSON definitions. + */ +export function convertMDSLToJSON(definition: string): RootNodeDefinition[] { + // Swap out any node/attribute argument string literals with a placeholder and get a mapping of placeholders to original values as well as the processed definition. + const { placeholders, processedDefinition } = substituteStringLiterals(definition); + + // Parse our definition definition string into an array of raw tokens. + const tokens = parseTokensFromDefinition(processedDefinition); + + return convertTokensToJSONDefinition(tokens, placeholders); +} + +/** + * Converts the specified tree definition tokens into a JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The root node JSON definitions. + */ +function convertTokensToJSONDefinition( + tokens: string[], + stringLiteralPlaceholders: StringLiteralPlaceholders +): RootNodeDefinition[] { + // There must be at least 3 tokens for the tree definition to be valid. 'ROOT', '{' and '}'. + if (tokens.length < 3) { + throw new Error("invalid token count"); + } + + // We should have a matching number of '{' and '}' tokens. If not, then there are scopes that have not been properly closed. + if (tokens.filter((token) => token === "{").length !== tokens.filter((token) => token === "}").length) { + throw new Error("scope character mismatch"); + } + + // Create an array of tree stack arrays where root nodes will always be at the botton and the current composite/decorator node at the top. + // There should be an element in this array for every root node defined and every element should be an array with a root note as the first element. + // E.g. A definition with two root nodes defined: + // [ + // [root, lotto, sequence], + // [root, selector] + // ] + const treeStacks: [Partial, ...Partial[]][] = []; + + // Create an array of all root node definitions that we create. + const rootNodes: Partial[] = []; + + // A helper function used to push node definitions onto the tree stack. + const pushNode = (node: AnyNodeDefinition) => { + // If the node is a root node then we need to create a new tree stack array with the root node at the root. + if (isRootNode(node)) { + // We need to double-check that this root node is not the child of another node. + // We can do this by checking whether the top tree stack is not empty (contains an existing node) + if (treeStacks[treeStacks.length - 1]?.length) { + throw new Error("a root node cannot be the child of another node"); + } + + // Add the root node definition to our array of all parsed root node definitions. + rootNodes.push(node); + + // Add the root node definition to the root of a new tree stack. + treeStacks.push([node]); + + return; + } + + // All non-root nodes should be pushed after their root nodes so handle cases + // where we may not have any tree stacks or our top-most tree stack is empty. + if (!treeStacks.length || !treeStacks[treeStacks.length - 1].length) { + throw new Error("expected root node at base of definition"); + } + + // Get the current tree stack that we are populating. + const topTreeStack = treeStacks[treeStacks.length - 1]; + + // Get the top-most node in the current tree stack, this will be a composite/decorator node + // for which we will populate its children array if composite or setting its child if a decorator. + const topTreeStackTopNode = topTreeStack[topTreeStack.length - 1] as AnyNodeDefinition; + + // If the top-most node in the current root stack is a composite or decorator + // node then the current node should be added as a child of the top-most node. + if (isCompositeNode(topTreeStackTopNode)) { + topTreeStackTopNode.children = topTreeStackTopNode.children || []; + topTreeStackTopNode.children.push(node); + } else if (isDecoratorNode(topTreeStackTopNode)) { + // If the top node already has a child node set then throw an error as a decorator should only have a single child. + if (topTreeStackTopNode.child) { + throw new Error("a decorator node must only have a single child node"); + } + + topTreeStackTopNode.child = node; + } + + // If the node we are adding is also a composite or decorator node, then we should push it + // onto the current tree stack, as subsequent nodes will be added as its child/children. + if (!isLeafNode(node)) { + topTreeStack.push(node); + } + }; + + // A helper function used to pop the top-most node definition off of the tree stack and return it. + const popNode = (): AnyNodeDefinition | null => { + let poppedNode: AnyNodeDefinition | null = null; + + // Get the current tree stack that we are populating. + const topTreeStack = treeStacks[treeStacks.length - 1]; + + // Pop the top-most node in the current tree stack if there is one. + if (topTreeStack.length) { + poppedNode = topTreeStack.pop() as AnyNodeDefinition; + } + + // We don't want any empty tree stacks in our stack of tree stacks. + if (!topTreeStack.length) { + treeStacks.pop(); + } + + return poppedNode; + }; + + // We should keep processing the raw tokens until we run out of them. + while (tokens.length) { + // Grab the next token. + const token = tokens.shift()!; + + // How we create the next node depends on the current raw token value. + switch (token.toUpperCase()) { + case "ROOT": { + pushNode(createRootNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "SUCCEED": { + pushNode(createSucceedNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "FAIL": { + pushNode(createFailNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "FLIP": { + pushNode(createFlipNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "REPEAT": { + pushNode(createRepeatNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "RETRY": { + pushNode(createRetryNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "SEQUENCE": { + pushNode(createSequenceNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "SELECTOR": { + pushNode(createSelectorNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "PARALLEL": { + pushNode(createParallelNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "LOTTO": { + pushNode(createLottoNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "ACTION": { + pushNode(createActionNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "CONDITION": { + pushNode(createConditionNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "WAIT": { + pushNode(createWaitNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "BRANCH": { + pushNode(createBranchNode(tokens, stringLiteralPlaceholders)); + break; + } + + case "}": { + // The '}' character closes the current scope and means that we have to pop a node off of the current stack. + const poppedNode = popNode(); + + // Now that we have a node definition we can carry out any validation that may require the node to be fully populated. + if (poppedNode) { + validatePoppedNode(poppedNode); + } + + break; + } + + default: { + throw new Error(`unexpected token: ${token}`); + } + } + } + + return rootNodes as RootNodeDefinition[]; +} + +/** + * Creates a root node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The root node JSON definition. + */ +function createRootNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): RootNodeDefinition { + // Create the root node definition. + let node = { + type: "root" + } as Partial; + + // Parse any node arguments, we should only have one if any which will be an identifier argument for the root identifier. + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + + // Check whether any node arguments were defined. + if (nodeArguments.length) { + // We should only have one argument, if any, which will be an identifier argument for the root identifier. + if (nodeArguments.length === 1 && nodeArguments[0].type === "identifier") { + // The root node identifier will be the first and only node argument value. + node.id = nodeArguments[0].value as string; + } else { + throw new Error("expected single root name argument"); + } + } + + // Grab any node attribute definitions and spread them into the node definition. + node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + + // This is a decorator node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the root node definition. + return node as RootNodeDefinition; +} + +/** + * Creates a succeed node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The succeed node JSON definition. + */ +function createSucceedNode( + tokens: string[], + stringLiteralPlaceholders: StringLiteralPlaceholders +): SucceedNodeDefinition { + const node = { + type: "succeed", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + } as SucceedNodeDefinition; + + // This is a decorator node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the succeed node definition. + return node; +} + +/** + * Creates a fail node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The fail node JSON definition. + */ +function createFailNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): FailNodeDefinition { + const node = { + type: "fail", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + } as FailNodeDefinition; + + // This is a decorator node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the fail node definition. + return node; +} + +/** + * Creates a flip node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The flip node JSON definition. + */ +function createFlipNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): FlipNodeDefinition { + const node = { + type: "flip", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + } as FlipNodeDefinition; + + // This is a decorator node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the flip node definition. + return node; +} + +/** + * Creates a repeat node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The repeat node JSON definition. + */ +function createRepeatNode( + tokens: string[], + stringLiteralPlaceholders: StringLiteralPlaceholders +): RepeatNodeDefinition { + let node = { type: "repeat" } as RepeatNodeDefinition; + + // Get the node arguments. + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + + // The arguments of a repeat node are optional. We may have: + // - No node arguments, in which case the repeat note will iterate indefinitely. + // - One node argument which will be the explicit number of iterations to make. + // - Two node arguments which define the min and max iteration bounds from which a random iteration count will be picked. + if (nodeArguments.length) { + // All repeat node arguments MUST be of type number and must be integer. + nodeArguments + .filter((arg) => arg.type !== "number" || !arg.isInteger) + .forEach(() => { + throw new Error(`repeat node iteration counts must be integer values`); + }); + + // We should have got one or two iteration counts. + if (nodeArguments.length === 1) { + // A static iteration count was defined. + node.iterations = nodeArguments[0].value as number; + + // A repeat node must have a positive number of iterations if defined. + if (node.iterations < 0) { + throw new Error("a repeat node must have a positive number of iterations if defined"); + } + } else if (nodeArguments.length === 2) { + // A minimum and maximum iteration count was defined. + node.iterations = [nodeArguments[0].value as number, nodeArguments[1].value as number]; + + // A repeat node must have a positive min and max iteration count if they are defined. + if (node.iterations[0] < 0 || node.iterations[1] < 0) { + throw new Error("a repeat node must have a positive minimum and maximum iteration count if defined"); + } + + // A repeat node must not have an minimum iteration count that exceeds the maximum iteration count. + if (node.iterations[0] > node.iterations[1]) { + throw new Error( + "a repeat node must not have a minimum iteration count that exceeds the maximum iteration count" + ); + } + } else { + // An incorrect number of iteration counts was defined. + throw new Error("invalid number of repeat node iteration count arguments defined"); + } + } + + // Grab any node attribute definitions and spread them into the node definition. + node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + + // This is a decorator node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the repeat node definition. + return node; +} + +/** + * Creates a retry node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The retry node JSON definition. + */ +function createRetryNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): RetryNodeDefinition { + let node = { type: "retry" } as RetryNodeDefinition; + + // Get the node arguments. + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + + // The arguments of a retry node are optional. We may have: + // - No node arguments, in which case the retry note will attempt indefinitely. + // - One node argument which will be the explicit number of attempts to make. + // - Two node arguments which define the min and max attempt bounds from which a random attempt count will be picked. + if (nodeArguments.length) { + // All retry node arguments MUST be of type number and must be integer. + nodeArguments + .filter((arg) => arg.type !== "number" || !arg.isInteger) + .forEach(() => { + throw new Error(`retry node attempt counts must be integer values`); + }); + + // We should have got one or two attempt counts. + if (nodeArguments.length === 1) { + // A static attempt count was defined. + node.attempts = nodeArguments[0].value as number; + + // A retry node must have a positive number of attempts if defined. + if (node.attempts < 0) { + throw new Error("a retry node must have a positive number of attempts if defined"); + } + } else if (nodeArguments.length === 2) { + // A minimum and maximum attempt count was defined. + node.attempts = [nodeArguments[0].value as number, nodeArguments[1].value as number]; + + // A retry node must have a positive min and max attempts count if they are defined. + if (node.attempts[0] < 0 || node.attempts[1] < 0) { + throw new Error("a retry node must have a positive minimum and maximum attempt count if defined"); + } + + // A retry node must not have a minimum attempt count that exceeds the maximum attempt count. + if (node.attempts[0] > node.attempts[1]) { + throw new Error( + "a retry node must not have a minimum attempt count that exceeds the maximum attempt count" + ); + } + } else { + // An incorrect number of attempt counts was defined. + throw new Error("invalid number of retry node attempt count arguments defined"); + } + } + + // Grab any node attribute definitions and spread them into the node definition. + node = { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; + + // This is a decorator node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the retry node definition. + return node; +} + +/** + * Creates a sequence node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The sequence node JSON definition. + */ +function createSequenceNode( + tokens: string[], + stringLiteralPlaceholders: StringLiteralPlaceholders +): SequenceNodeDefinition { + const node = { + type: "sequence", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + } as SequenceNodeDefinition; + + // This is a composite node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the sequence node definition. + return node; +} + +/** + * Creates a selector node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The selector node JSON definition. + */ +function createSelectorNode( + tokens: string[], + stringLiteralPlaceholders: StringLiteralPlaceholders +): SelectorNodeDefinition { + const node = { + type: "selector", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + } as SelectorNodeDefinition; + + // This is a composite node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the selector node definition. + return node; +} + +/** + * Creates a parallel node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The parallel node JSON definition. + */ +function createParallelNode( + tokens: string[], + stringLiteralPlaceholders: StringLiteralPlaceholders +): ParallelNodeDefinition { + const node = { + type: "parallel", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + } as ParallelNodeDefinition; + + // This is a composite node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the parallel node definition. + return node; +} + +/** + * Creates a lotto node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The lotto node JSON definition. + */ +function createLottoNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): LottoNodeDefinition { + // If any node arguments have been defined then they must be our weights. + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + + // All lotto node arguments MUST be of type number and must be positive integers. + nodeArguments + .filter((arg) => arg.type !== "number" || !arg.isInteger || arg.value < 0) + .forEach(() => { + throw new Error(`lotto node weight arguments must be positive integer values`); + }); + + const node = { + type: "lotto", + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + } as LottoNodeDefinition; + + // Apply the weights if any were defined. + if (nodeArguments.length) { + node.weights = nodeArguments.map(({ value }) => value) as number[]; + } + + // This is a composite node, so we expect an opening '{'. + popAndCheck(tokens, "{"); + + // Return the lotto node definition. + return node; +} + +/** + * Creates an action node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The action node JSON definition. + */ +function createActionNode( + tokens: string[], + stringLiteralPlaceholders: StringLiteralPlaceholders +): ActionNodeDefinition { + // Parse any node arguments, we should have at least one which will be an identifier argument for the action name + // and agent function to invoke for the action, all other arguments are to be passed as arguments to that function. + const [actionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders); + + // Our first argument MUST be defined and be an identifier as we require an action name argument. + if (actionNameIdentifier?.type !== "identifier") { + throw new Error("expected action name identifier argument"); + } + + // Only the first argument should have been an identifier, all agent function arguments must be string, number, boolean or null. + agentFunctionArgs + .filter((arg) => arg.type === "identifier") + .forEach((arg) => { + throw new Error( + `invalid action node argument value '${arg.value}', must be string, number, boolean or null` + ); + }); + + // Return the action node definition. + return { + type: "action", + call: actionNameIdentifier.value, + args: agentFunctionArgs.map(({ value }) => value), + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; +} + +/** + * Creates a condition node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The condition node JSON definition. + */ +function createConditionNode( + tokens: string[], + stringLiteralPlaceholders: StringLiteralPlaceholders +): ConditionNodeDefinition { + // Parse any node arguments, we should have at least one which will be an identifier argument for the condition name + // and agent function to invoke for the condition, all other arguments are to be passed as arguments to that function. + const [conditionNameIdentifier, ...agentFunctionArgs] = parseArgumentTokens(tokens, stringLiteralPlaceholders); + + // Our first argument MUST be defined and be an identifier as we require a condition name argument. + if (conditionNameIdentifier?.type !== "identifier") { + throw new Error("expected condition name identifier argument"); + } + + // Only the first argument should have been an identifier, all agent function arguments must be string, number, boolean or null. + agentFunctionArgs + .filter((arg) => arg.type === "identifier") + .forEach((arg) => { + throw new Error( + `invalid condition node argument value '${arg.value}', must be string, number, boolean or null` + ); + }); + + // Return the condition node definition. + return { + type: "condition", + call: conditionNameIdentifier.value, + args: agentFunctionArgs.map(({ value }) => value), + ...parseAttributeTokens(tokens, stringLiteralPlaceholders) + }; +} + +/** + * Creates a wait node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The wait node JSON definition. + */ +function createWaitNode(tokens: string[], stringLiteralPlaceholders: StringLiteralPlaceholders): WaitNodeDefinition { + let node = { type: "wait" } as WaitNodeDefinition; + + // Get the node arguments. + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + + // The arguments of a wait node are optional. We may have: + // - No node arguments, in which case the wait will be indefinite until it is aborted. + // - One node argument which will be the explicit duration of the wait. + // - Two node arguments which define the min and max duration bounds from which a random duration will be picked. + if (nodeArguments.length) { + // All wait node arguments MUST be of type number and must be integer. + nodeArguments + .filter((arg) => arg.type !== "number" || !arg.isInteger) + .forEach(() => { + throw new Error(`wait node durations must be integer values`); + }); + + // We may have: + // - One node argument which will be the explicit duration of the wait. + // - Two node arguments which define the min and max duration bounds from which a random duration will be picked. + // - Too many arguments, which is not valid. + if (nodeArguments.length === 1) { + // An explicit duration was defined. + node.duration = nodeArguments[0].value as number; + + // If an explict duration was defined then it must be a positive number. + if (node.duration < 0) { + throw new Error("a wait node must have a positive duration"); + } + } else if (nodeArguments.length === 2) { + // Min and max duration bounds were defined from which a random duration will be picked. + node.duration = [nodeArguments[0].value as number, nodeArguments[1].value as number]; + + // A wait node must have a positive min and max duration. + if (node.duration[0] < 0 || node.duration[1] < 0) { + throw new Error("a wait node must have a positive minimum and maximum duration"); + } + + // A wait node must not have a minimum duration that exceeds the maximum duration. + if (node.duration[0] > node.duration[1]) { + throw new Error("a wait node must not have a minimum duration that exceeds the maximum duration"); + } + } else if (nodeArguments.length > 2) { + // An incorrect number of duration arguments were defined. + throw new Error("invalid number of wait node duration arguments defined"); + } + } + + // Return the wait node definition. + return { ...node, ...parseAttributeTokens(tokens, stringLiteralPlaceholders) }; +} + +/** + * Creates a branch node JSON definition. + * @param tokens The tree definition tokens. + * @param stringLiteralPlaceholders The substituted string literal placeholders. + * @returns The branch node JSON definition. + */ +function createBranchNode( + tokens: string[], + stringLiteralPlaceholders: StringLiteralPlaceholders +): BranchNodeDefinition { + // Parse any node arguments, we should have one which will be an identifier argument for the root ref. + const nodeArguments = parseArgumentTokens(tokens, stringLiteralPlaceholders); + + // We should have only a single identifer argument for a branch node, which is the root ref. + if (nodeArguments.length !== 1 || nodeArguments[0].type !== "identifier") { + throw new Error("expected single branch name argument"); + } + + // Return the branch node definition. + return { type: "branch", ref: nodeArguments[0].value }; +} + +/** + * Validate a fully-populated node definition that was popped off of the tree stack. + * @param definition The popped node to validate. + */ +function validatePoppedNode(definition: AnyNodeDefinition): void { + // Decorators MUST have a child defined. + if (isDecoratorNode(definition) && isNullOrUndefined(definition.child)) { + throw new Error(`a ${definition.type} node must have a single child node defined`); + } + + // Composites MUST have at least one child defined. + if (isCompositeNode(definition) && !definition.children?.length) { + throw new Error(`a ${definition.type} node must have at least a single child node defined`); + } + + // We need to make sure that lotto nodes that have weights defined have a number of weights matching the number of child nodes. + if (definition.type === "lotto") { + // Check whether a 'weights' property has been defined, if it has we expect it to be an array of weights. + if (typeof definition.weights !== "undefined") { + // Check that the weights property is an array of positive integers with an element for each child node element. + if (definition.weights.length !== definition.children.length) { + throw new Error( + "expected a number of weight arguments matching the number of child nodes for lotto node" + ); + } + } + } +} diff --git a/src/mdsl/MDSLNodeArgumentParser.ts b/src/mdsl/MDSLNodeArgumentParser.ts new file mode 100644 index 0000000..84e8523 --- /dev/null +++ b/src/mdsl/MDSLNodeArgumentParser.ts @@ -0,0 +1,151 @@ +import { StringLiteralPlaceholders, popAndCheck } from "./MDSLUtilities"; + +/** + * A type representing any node function argument. + */ +type Argument = { + /** + * The argument value. + */ + value: T; + /** + * The argument type, used for validation. + */ + type: string; +}; + +type NullArgument = Argument & { + type: "null"; +}; + +type BooleanArgument = Argument & { + type: "boolean"; +}; + +type NumberArgument = Argument & { + type: "number"; + /** + * A flag defining whether the number argument value is a valid integer. (used for validation) + */ + isInteger: boolean; +}; + +type StringPlaceholderArgument = Argument & { + type: "string"; +}; + +type IdentifierArgument = Argument & { + type: "identifier"; +}; + +/** + * A type representing a reference to any node function argument. + */ +type AnyArgument = NullArgument | BooleanArgument | NumberArgument | StringPlaceholderArgument | IdentifierArgument; + +/** + * Parse an array of argument definitions from the specified tokens array. + * @param tokens The array tokens to parse the argument definitions from. + * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values. + * @param argumentValidator The argument validator function. + * @param validationFailedMessage The exception message to throw if argument validation fails. + * @returns An array of argument definitions parsed from the specified tokens array. + */ +export function parseArgumentTokens( + tokens: string[], + stringArgumentPlaceholders: StringLiteralPlaceholders +): AnyArgument[] { + const argumentList: AnyArgument[] = []; + + // If the next token is not a '[' or '(' then we have no arguments to parse. + if (!["[", "("].includes(tokens[0])) { + return argumentList; + } + + // Any lists of arguments will always be wrapped in '[]' for node arguments or '()' for attribute arguments. + // We are looking for a '[' or '(' opener that wraps the argument tokens and the relevant closer. + const closingToken = popAndCheck(tokens, ["[", "("]) === "[" ? "]" : ")"; + + const argumentListTokens: string[] = []; + + // Grab all tokens between the '[' and ']' or '(' and ')'. + while (tokens.length && tokens[0] !== closingToken) { + // The next token is part of our arguments list. + argumentListTokens.push(tokens.shift()!); + } + + // Validate the order of the argument tokens. Each token must either be a ',' or a single argument that satisfies the validator. + argumentListTokens.forEach((token, index) => { + // Get whether this token should be an actual argument. + const shouldBeArgumentToken = !(index & 1); + + // If the current token should be an actual argument then validate it, otherwise it should be a ',' token. + if (shouldBeArgumentToken) { + // Get the argument definition. + const argumentDefinition = getArgumentDefinition(token, stringArgumentPlaceholders); + + // This is a valid argument! + argumentList.push(argumentDefinition); + } else { + // The current token should be a ',' token. + if (token !== ",") { + throw new Error(`invalid argument list, expected ',' or ']' but got '${token}'`); + } + } + }); + + // The arguments list should terminate with a ']' or ')' token, depending on the opener. + popAndCheck(tokens, closingToken); + + // Return the arguments. + return argumentList; +} + +/** + * Gets an argument value definition. + * @param token The argument token. + * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values. + * @returns An argument value definition. + */ +function getArgumentDefinition(token: string, stringArgumentPlaceholders: StringLiteralPlaceholders): AnyArgument { + // Check whether the token represents a null value. + if (token === "null") { + return { + value: null, + type: "null" + } as NullArgument; + } + + // Check whether the token represents a boolean value. + if (token === "true" || token === "false") { + return { + value: token === "true", + type: "boolean" + } as BooleanArgument; + } + + // Check whether the token represents a number value. + // TODO: Relies on broken isNaN - see MDN. + // if (!Number.isNaN(token)) { + if (!isNaN(token as any)) { + return { + value: parseFloat(token), + isInteger: parseFloat(token) === parseInt(token, 10), + type: "number" + } as NumberArgument; + } + + // Check whether the token is a placeholder (e.g. @@0@@) representing a string literal. + if (token.match(/^@@\d+@@$/g)) { + return { + value: stringArgumentPlaceholders[token].replace('\\"', '"'), + type: "string" + } as StringPlaceholderArgument; + } + + // The only remaining option is that the argument value is an identifier. + return { + value: token, + type: "identifier" + } as IdentifierArgument; +} diff --git a/src/mdsl/MDSLNodeAttributeParser.ts b/src/mdsl/MDSLNodeAttributeParser.ts new file mode 100644 index 0000000..f8cfe02 --- /dev/null +++ b/src/mdsl/MDSLNodeAttributeParser.ts @@ -0,0 +1,75 @@ +import { NodeAttributeDefinition } from "../BehaviourTreeDefinition"; +import { parseArgumentTokens } from "./MDSLNodeArgumentParser"; +import { StringLiteralPlaceholders } from "./MDSLUtilities"; + +/** + * A type defining the attribute definitions of a node. + */ +type NodeAttributes = { + while?: NodeAttributeDefinition; + until?: NodeAttributeDefinition; + entry?: NodeAttributeDefinition; + exit?: NodeAttributeDefinition; + step?: NodeAttributeDefinition; +}; + +/** + * Parse any node attribute definitions from the specified tokens array. + * @param tokens The array of remaining tokens. + * @param stringArgumentPlaceholders The mapping of string literal node argument placeholders to original values. + * @returns An object of attribute definitions defined by any directly following tokens. + */ +export function parseAttributeTokens( + tokens: string[], + stringArgumentPlaceholders: StringLiteralPlaceholders +): NodeAttributes { + const nodeAttributeNames: (keyof NodeAttributes)[] = ["while", "until", "entry", "exit", "step"]; + + // Create an object to hold any attributes found. + const attributes: NodeAttributes = {}; + + // Try to get the name of the attribute for the next token. + let nextAttributeName = tokens[0]?.toLowerCase() as keyof NodeAttributes; + + // Pull attribute tokens as well as their arguments off of the tokens stack until we have no more. + while (nodeAttributeNames.includes(nextAttributeName)) { + // Check to make sure that we have not already created an attribute definition of this type. + if (attributes[nextAttributeName]) { + throw new Error(`duplicate attribute '${tokens[0].toUpperCase()}' found for node`); + } + + // Remove the attribute name token from the array of tokens. + tokens.shift(); + + // Grab the attribute arguments, assuming the first to be an identifier. + const [attributeCallIdentifier, ...attributeArguments] = parseArgumentTokens( + tokens, + stringArgumentPlaceholders + ); + + // The first attribute argument has to be an identifer, this will reference an agent function. + if (attributeCallIdentifier?.type !== "identifier") { + throw new Error("expected agent function or registered function name identifier argument for attribute"); + } + + // Any attribute arguments (other than the expected call identifier) must have a type of string, number, boolean or null. + attributeArguments + .filter((arg) => arg.type === "identifier") + .forEach((arg) => { + throw new Error( + `invalid attribute argument value '${arg.value}', must be string, number, boolean or null` + ); + }); + + // Create the attribute definition and add it to the object of attribute definitions found. + attributes[nextAttributeName] = { + call: attributeCallIdentifier.value, + args: attributeArguments.map(({ value }) => value) + }; + + // Try to get the next attribute name token, as there could be multiple. + nextAttributeName = tokens[0]?.toLowerCase() as keyof NodeAttributes; + } + + return attributes; +} diff --git a/src/mdsl/MDSLUtilities.ts b/src/mdsl/MDSLUtilities.ts new file mode 100644 index 0000000..036d61f --- /dev/null +++ b/src/mdsl/MDSLUtilities.ts @@ -0,0 +1,86 @@ +/** + * A type defining an object that holds a reference to substitued string literals parsed from the definition. + */ +export type StringLiteralPlaceholders = { [key: string]: string }; + +/** + * Pop the next raw token from the specified array of tokens and throw an error if it wasn't the expected one. + * @param tokens The array of tokens. + * @param expected An optional string or array or items, one of which must match the next popped token. + * @returns The popped token. + */ +export function popAndCheck(tokens: string[], expected?: string | string[]): string { + // Get and remove the next token. + const popped = tokens.shift(); + + // We were expecting another token but there aren't any. + if (popped === undefined) { + throw new Error("unexpected end of definition"); + } + + // Do we have an expected token/tokens array? + if (expected != undefined) { + // Get an array of expected values, if the popped token matches any then we are all good. + const expectedValues = typeof expected === "string" ? [expected] : expected; + + // Check whether the popped token matches at least one of our expected items. + var tokenMatchesExpectation = expectedValues.some((item) => popped.toUpperCase() === item.toUpperCase()); + + // Throw an error if the popped token didn't match any of our expected items. + if (!tokenMatchesExpectation) { + const expectationString = expectedValues.map((item) => "'" + item + "'").join(" or "); + throw new Error("unexpected token found. Expected " + expectationString + " but got '" + popped + "'"); + } + } + + // Return the popped token. + return popped; +} + +/** + * Swaps out any node/attribute argument string literals with placeholders. + * @param definition The definition. + * @returns An object containing a mapping of placeholders to original string values as well as the processed definition string. + */ +export function substituteStringLiterals(definition: string): { + placeholders: StringLiteralPlaceholders; + processedDefinition: string; +} { + // Create an object to hold the mapping of placeholders to original string values. + const placeholders: StringLiteralPlaceholders = {}; + + // Replace any string literals wrapped with double quotes in our definition with placeholders to be processed later. + const processedDefinition = definition.replace(/\"(\\.|[^"\\])*\"/g, (match) => { + var strippedMatch = match.substring(1, match.length - 1); + var placeholder = Object.keys(placeholders).find((key) => placeholders[key] === strippedMatch); + + // If we have no existing string literal match then create a new placeholder. + if (!placeholder) { + placeholder = `@@${Object.keys(placeholders).length}@@`; + placeholders[placeholder] = strippedMatch; + } + + return placeholder; + }); + + return { placeholders, processedDefinition }; +} + +/** + * Parse the tree definition into an array of raw tokens. + * @param definition The definition. + * @returns An array of tokens parsed from the definition. + */ +export function parseTokensFromDefinition(definition: string): string[] { + // Add some space around various important characters so that they can be plucked out easier as individual tokens. + definition = definition.replace(/\(/g, " ( "); + definition = definition.replace(/\)/g, " ) "); + definition = definition.replace(/\{/g, " { "); + definition = definition.replace(/\}/g, " } "); + definition = definition.replace(/\]/g, " ] "); + definition = definition.replace(/\[/g, " [ "); + definition = definition.replace(/\,/g, " , "); + + // Split the definition into raw token form and return it. + return definition.replace(/\s+/g, " ").trim().split(" "); +} diff --git a/src/nodes/Node.ts b/src/nodes/Node.ts index e1f87e5..67ee974 100644 --- a/src/nodes/Node.ts +++ b/src/nodes/Node.ts @@ -1,4 +1,7 @@ +import { BehaviourTreeOptions } from "../BehaviourTreeOptions"; +import State, { AnyState } from "../State"; import { Agent } from "../Agent"; +import Leaf from "./leaf/Leaf"; import Attribute from "../attributes/Attribute"; import Entry from "../attributes/callbacks/Entry"; import Exit from "../attributes/callbacks/Exit"; @@ -6,10 +9,6 @@ import Step from "../attributes/callbacks/Step"; import Guard from "../attributes/guards/Guard"; import GuardPath from "../attributes/guards/GuardPath"; import GuardUnsatisifedException from "../attributes/guards/GuardUnsatisifedException"; -import { BehaviourTreeOptions } from "../BehaviourTreeOptions"; -import { AnyArgument } from "../RootAstNodesBuilder"; -import State, { AnyState } from "../State"; -import Leaf from "./leaf/Leaf"; /** * A base node. @@ -33,7 +32,7 @@ export default abstract class Node { * @param attributes The node attributes. * @param args The node argument definitions. */ - constructor(private type: string, private attributes: Attribute[], private args: AnyArgument[]) {} + constructor(private type: string, private attributes: Attribute[], private args: any[]) {} /** * Called when the node is being updated. @@ -88,8 +87,7 @@ export default abstract class Node { getAttribute(type: "step" | "STEP"): Step; getAttribute(type: string): Attribute { return ( - this.getAttributes().filter((decorator) => decorator.getType().toUpperCase() === type.toUpperCase())[0] || - null + this.getAttributes().filter((decorator) => decorator.type.toUpperCase() === type.toUpperCase())[0] || null ); } diff --git a/src/nodes/composite/Lotto.ts b/src/nodes/composite/Lotto.ts index d41823a..46d586d 100644 --- a/src/nodes/composite/Lotto.ts +++ b/src/nodes/composite/Lotto.ts @@ -15,10 +15,10 @@ import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; export default class Lotto extends Composite { /** * @param attributes The node attributes. - * @param tickets The child node tickets. + * @param weights The child node weights. * @param children The child nodes. */ - constructor(attributes: Attribute[], private tickets: number[], children: Node[]) { + constructor(attributes: Attribute[], private weights: number[] | undefined, children: Node[]) { super("lotto", attributes, children); } @@ -40,7 +40,7 @@ export default class Lotto extends Composite { // Hook up the optional 'random' behaviour tree function option to the one used by 'lotto-draw'. random: options.random, // Pass in each child node as a participant in the lotto draw with their respective ticket count. - participants: this.children.map((child, index) => [child, this.tickets[index] || 1]) + participants: this.children.map((child, index) => [child, this.weights?.[index] || 1]) }); // Randomly pick a child based on ticket weighting, this will become the active child for this composite node. @@ -64,5 +64,5 @@ export default class Lotto extends Composite { /** * Gets the name of the node. */ - getName = () => (this.tickets.length ? `LOTTO [${this.tickets.join(",")}]` : "LOTTO"); + getName = () => (this.weights ? `LOTTO [${this.weights.join(",")}]` : "LOTTO"); } diff --git a/src/nodes/leaf/Action.ts b/src/nodes/leaf/Action.ts index 3292910..0af7ed5 100644 --- a/src/nodes/leaf/Action.ts +++ b/src/nodes/leaf/Action.ts @@ -1,10 +1,24 @@ -import Leaf from "./Leaf"; +import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; import State, { CompleteState } from "../../State"; -import Lookup from "../../Lookup"; import { Agent } from "../../Agent"; +import Leaf from "./Leaf"; +import Lookup from "../../Lookup"; import Attribute from "../../attributes/Attribute"; -import { AnyArgument } from "../../RootAstNodesBuilder"; -import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; + +/** + * The type representing a resolved/rejected update promise. + */ +type UpdatePromiseResult = { + /** + * Whether the promise was resolved rather than rejected. + */ + isResolved: boolean; + + /** + * The promise resolved value or rejection reason. + */ + value: any; +}; /** * An Action leaf node. @@ -16,7 +30,7 @@ export default class Action extends Leaf { * @param actionName The action name. * @param actionArguments The array of action argument definitions. */ - constructor(attributes: Attribute[], private actionName: string, private actionArguments: AnyArgument[]) { + constructor(attributes: Attribute[], private actionName: string, private actionArguments: any[]) { super("action", attributes, actionArguments); } @@ -28,7 +42,7 @@ export default class Action extends Leaf { /** * The finished state result of an update promise. */ - private updatePromiseStateResult: CompleteState | null = null; + private updatePromiseResult: UpdatePromiseResult | null = null; /** * Called when the node is being updated. @@ -36,16 +50,32 @@ export default class Action extends Leaf { * @param options The behaviour tree options object. */ protected onUpdate(agent: Agent, options: BehaviourTreeOptions): void { - // If the result of this action depends on an update promise then there is nothing to do until - // it resolves, unless there has been a value set as a result of the update promise resolving. + // If the result of this action depends on an update promise then there is nothing to do until it settles. if (this.isUsingUpdatePromise) { - // Check whether the update promise has resolved with a state value. - if (this.updatePromiseStateResult) { - // Set the state of this node to match the state returned by the promise. - this.setState(this.updatePromiseStateResult); + // Are we still waiting for our update promise to settle? + if (!this.updatePromiseResult) { + return; } - return; + const { isResolved, value } = this.updatePromiseResult; + + // Our update promise settled, was it resolved or rejected? + if (isResolved) { + // Our promise resolved so check to make sure the result is a valid finished state. + if (value !== State.SUCCEEDED && value !== State.FAILED) { + throw new Error( + "action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned" + ); + } + + // Set the state of this node to match the state returned by the promise. + this.setState(value); + + return; + } else { + // The promise was rejected, which isn't great. + throw new Error(`action function '${this.actionName}' promise rejected with '${value}'`); + } } // Attempt to get the invoker for the action function. @@ -58,38 +88,48 @@ export default class Action extends Leaf { ); } - // Call the action function, the result of which may be: - // - The finished state of this action node. - // - A promise to return a finished node state. - // - Undefined if the node should remain in the running state. - const updateResult = actionFuncInvoker(this.actionArguments) as CompleteState | Promise; + let actionFunctionResult; + + try { + // Call the action function, the result of which may be: + // - The finished state of this action node. + // - A promise to return a finished node state. + // - Undefined if the node should remain in the running state. + actionFunctionResult = actionFuncInvoker(this.actionArguments) as CompleteState | Promise; + } catch (error) { + // An uncaught error was thrown. + if (error instanceof Error) { + throw new Error(`action function '${this.actionName}' threw: ${error.stack}`); + } else { + throw new Error(`action function '${this.actionName}' threw: ${error}`); + } + } - if (updateResult instanceof Promise) { - updateResult.then( + if (actionFunctionResult instanceof Promise) { + actionFunctionResult.then( (result) => { - // If 'isUpdatePromisePending' is null then the promise was cleared as it was resolving, probably via an abort of reset. + // If 'isUpdatePromisePending' is not set then the promise was cleared as it was resolving, probably via an abort of reset. if (!this.isUsingUpdatePromise) { return; } - // Check to make sure the result is a valid finished state. - if (result !== State.SUCCEEDED && result !== State.FAILED) { - throw new Error( - "action node promise resolved with an invalid value, expected a State.SUCCEEDED or State.FAILED value to be returned" - ); - } - - // Set pending update promise state result to be processed on next update. - this.updatePromiseStateResult = result; + // Set the resolved update promise result so that it can be handled on the next update of this node. + this.updatePromiseResult = { + isResolved: true, + value: result + }; }, (reason) => { - // If 'isUpdatePromisePending' is null then the promise was cleared as it was resolving, probably via an abort of reset. + // If 'isUpdatePromisePending' is not set then the promise was cleared as it was resolving, probably via an abort or reset. if (!this.isUsingUpdatePromise) { return; } - // Just throw whatever was returned as the rejection argument. - throw new Error(reason); + // Set the rejected update promise result so that it can be handled on the next update of this node. + this.updatePromiseResult = { + isResolved: false, + value: reason + }; } ); @@ -100,10 +140,10 @@ export default class Action extends Leaf { this.isUsingUpdatePromise = true; } else { // Validate the returned value. - this.validateUpdateResult(updateResult); + this.validateUpdateResult(actionFunctionResult); // Set the state of this node, this may be undefined, which just means that the node is still in the 'RUNNING' state. - this.setState(updateResult || State.RUNNING); + this.setState(actionFunctionResult || State.RUNNING); } } @@ -121,22 +161,23 @@ export default class Action extends Leaf { // There is no longer an update promise that we care about. this.isUsingUpdatePromise = false; - this.updatePromiseStateResult = null; + this.updatePromiseResult = null; }; /** * Validate the result of an update function call. * @param result The result of an update function call. */ - private validateUpdateResult = (result: CompleteState | boolean) => { + private validateUpdateResult = (result: CompleteState | State.RUNNING) => { switch (result) { case State.SUCCEEDED: case State.FAILED: + case State.RUNNING: case undefined: return; default: throw new Error( - `action '${this.actionName}' 'onUpdate' returned an invalid response, expected an optional State.SUCCEEDED or State.FAILED value to be returned` + `expected action function '${this.actionName}' to return an optional State.SUCCEEDED or State.FAILED value but returned '${result}'` ); } }; diff --git a/src/nodes/leaf/Condition.ts b/src/nodes/leaf/Condition.ts index f9358d1..c0fb824 100644 --- a/src/nodes/leaf/Condition.ts +++ b/src/nodes/leaf/Condition.ts @@ -1,10 +1,9 @@ -import Leaf from "./Leaf"; +import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; import State from "../../State"; -import Lookup from "../../Lookup"; import { Agent } from "../../Agent"; +import Leaf from "./Leaf"; +import Lookup from "../../Lookup"; import Attribute from "../../attributes/Attribute"; -import { AnyArgument } from "../../RootAstNodesBuilder"; -import { BehaviourTreeOptions } from "../../BehaviourTreeOptions"; /** * A Condition leaf node. @@ -16,7 +15,7 @@ export default class Condition extends Leaf { * @param conditionName The name of the condition function. * @param conditionArguments The array of condition argument definitions. */ - constructor(attributes: Attribute[], private conditionName: string, private conditionArguments: AnyArgument[]) { + constructor(attributes: Attribute[], private conditionName: string, private conditionArguments: any[]) { super("condition", attributes, conditionArguments); } @@ -36,8 +35,29 @@ export default class Condition extends Leaf { ); } - // Call the condition function to determine the state of this node. - this.setState(!!conditionFuncInvoker(this.conditionArguments) ? State.SUCCEEDED : State.FAILED); + let conditionFunctionResult; + + try { + // Call the condition function to determine the state of this node, the result of which should be a boolean. + conditionFunctionResult = conditionFuncInvoker(this.conditionArguments); + } catch (error) { + // An uncaught error was thrown. + if (error instanceof Error) { + throw new Error(`condition function '${this.conditionName}' threw: ${error.stack}`); + } else { + throw new Error(`condition function '${this.conditionName}' threw: ${error}`); + } + } + + // The result of calling the condition function must be a boolean value. + if (typeof conditionFunctionResult !== "boolean") { + throw new Error( + `expected condition function '${this.conditionName}' to return a boolean but returned '${conditionFunctionResult}'` + ); + } + + // Set the state of this node based on the result of calling the condition function. + this.setState(!!conditionFunctionResult ? State.SUCCEEDED : State.FAILED); } /** diff --git a/test/BehaviourTree.spec.ts b/test/BehaviourTree.spec.ts new file mode 100644 index 0000000..80f1875 --- /dev/null +++ b/test/BehaviourTree.spec.ts @@ -0,0 +1,306 @@ +import { assert } from "chai"; + +import { BehaviourTree, State } from "../src/index"; +import { RootNodeDefinition } from "../src/BehaviourTreeDefinition"; + +import { findNode } from "./TestUtilities"; + +describe("A BehaviourTree instance", () => { + describe("has initialisation logic that", () => { + describe("should error when", () => { + it("the tree definition argument is not defined", () => { + assert.throws(() => new BehaviourTree(null as any, {}), Error, "tree definition not defined"); + assert.throws(() => new BehaviourTree(undefined as any, {}), Error, "tree definition not defined"); + }); + + it("the agent object is not defined", () => { + assert.throws( + () => new BehaviourTree("", undefined as any), + Error, + "the agent must be an object and not null" + ); + assert.throws( + () => new BehaviourTree("", null as any), + Error, + "the agent must be an object and not null" + ); + assert.throws( + () => new BehaviourTree("", 42 as any), + Error, + "the agent must be an object and not null" + ); + }); + }); + + describe("should not error when the tree definition argument is a valid definition", () => { + it("(MDSL)", () => { + const definition = "root { action [test] }"; + assert.doesNotThrow(() => new BehaviourTree(definition, {}), Error); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "test" + } + }; + assert.doesNotThrow(() => new BehaviourTree(definition, {}), Error); + }); + }); + }); + + describe("has a 'getState' function that returns the state of the root node", () => { + it("(MDSL)", () => { + const definition = "root { action [getActionResult] }"; + const agent = { getActionResult: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(tree.getState(), State.READY); + + tree.step(); + + assert.strictEqual(tree.getState(), State.RUNNING); + + agent.getActionResult = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(tree.getState(), State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "getActionResult" + } + }; + const agent = { getActionResult: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(tree.getState(), State.READY); + + tree.step(); + + assert.strictEqual(tree.getState(), State.RUNNING); + + agent.getActionResult = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(tree.getState(), State.SUCCEEDED); + }); + }); + + describe("has an 'isRunning' function that returns a flag defining whether the tree is in a running state", () => { + it("(MDSL)", () => { + const definition = "root { action [getActionResult] }"; + const agent = { getActionResult: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(tree.isRunning(), false); + + tree.step(); + + assert.strictEqual(tree.isRunning(), true); + + agent.getActionResult = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(tree.isRunning(), false); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "getActionResult" + } + }; + const agent = { getActionResult: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(tree.isRunning(), false); + + tree.step(); + + assert.strictEqual(tree.isRunning(), true); + + agent.getActionResult = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(tree.isRunning(), false); + }); + }); + + describe("has a 'reset' function that resets the tree from the root node outwards to each nested node, giving each a state of READY", () => { + it("(MDSL)", () => { + const definition = "root { sequence { action [getActionResult] } }"; + const agent = { getActionResult: () => State.SUCCEEDED }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "sequence").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult").state, State.SUCCEEDED); + + tree.reset(); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult").state, State.READY); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + children: [ + { + type: "action", + call: "getActionResult" + } + ] + } + }; + const agent = { getActionResult: () => State.SUCCEEDED }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "sequence").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult").state, State.SUCCEEDED); + + tree.reset(); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult").state, State.READY); + }); + }); + + describe("has a 'step' function that updates all nodes in sequence unless a node is left in the running state", () => { + it("(MDSL)", () => { + const definition = + "root { sequence { action [getActionResult0] action [getActionResult1] action [getActionResult2] action [getActionResult3] } }"; + const agent = { + getActionResult0: () => State.SUCCEEDED, + getActionResult1: () => State.SUCCEEDED, + getActionResult2: () => State.RUNNING, + getActionResult3: () => State.SUCCEEDED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "action", "getActionResult0").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult2").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult3").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "getActionResult0").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult2").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "getActionResult3").state, State.READY); + + agent.getActionResult2 = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "getActionResult0").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult2").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult3").state, State.SUCCEEDED); + + agent.getActionResult2 = () => State.RUNNING; + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "getActionResult0").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult2").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "getActionResult3").state, State.READY); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + children: [ + { + type: "action", + call: "getActionResult0" + }, + { + type: "action", + call: "getActionResult1" + }, + { + type: "action", + call: "getActionResult2" + }, + { + type: "action", + call: "getActionResult3" + } + ] + } + }; + const agent = { + getActionResult0: () => State.SUCCEEDED, + getActionResult1: () => State.SUCCEEDED, + getActionResult2: () => State.RUNNING, + getActionResult3: () => State.SUCCEEDED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "action", "getActionResult0").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult2").state, State.READY); + assert.strictEqual(findNode(tree, "action", "getActionResult3").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "getActionResult0").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult2").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "getActionResult3").state, State.READY); + + agent.getActionResult2 = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "getActionResult0").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult2").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult3").state, State.SUCCEEDED); + + agent.getActionResult2 = () => State.RUNNING; + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "getActionResult0").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "getActionResult2").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "getActionResult3").state, State.READY); + }); + }); +}); diff --git a/test/BehaviourTreeBuilder.spec.ts b/test/BehaviourTreeBuilder.spec.ts new file mode 100644 index 0000000..582a8b5 --- /dev/null +++ b/test/BehaviourTreeBuilder.spec.ts @@ -0,0 +1,142 @@ +import { assert } from "chai"; + +import { RootNodeDefinition } from "../src/BehaviourTreeDefinition"; +import { BehaviourTree } from "../src/index"; +import buildRootNode from "../src/BehaviourTreeBuilder"; +import Sequence from "../src/nodes/composite/Sequence"; +import Action from "../src/nodes/leaf/Action"; +import Flip from "../src/nodes/decorator/Flip"; + +describe("The BehaviourTreeBuilder class has a buildRootNode function which takes an array of root node JSON definitions and", () => { + beforeEach(() => BehaviourTree.unregisterAll()); + + it("returns a populated Root node instance based on those definitions", () => { + const definition: RootNodeDefinition[] = [ + { + type: "root", + child: { + type: "sequence", + children: [ + { + type: "action", + call: "noop" + } + ] + } + } + ]; + + const rootNode = buildRootNode(definition); + + assert.isDefined(rootNode); + assert.strictEqual(rootNode.getType(), "root"); + + const sequenceNode = rootNode.getChildren()[0] as Sequence; + assert.isDefined(sequenceNode); + assert.strictEqual(sequenceNode.getType(), "sequence"); + assert.strictEqual(sequenceNode.getChildren().length, 1); + + const actionNode = sequenceNode.getChildren()[0] as Action; + assert.isDefined(actionNode); + assert.strictEqual(actionNode.getType(), "action"); + }); + + describe("resolves branch node definitions", () => { + describe("where the referenced subtree root node is", () => { + it("included as part of the definition", () => { + const definition: RootNodeDefinition[] = [ + { + type: "root", + child: { + type: "flip", + child: { + type: "branch", + ref: "sub-tree" + } + } + }, + { + type: "root", + id: "sub-tree", + child: { + type: "action", + call: "noop" + } + } + ]; + + const rootNode = buildRootNode(definition); + + assert.isDefined(rootNode); + assert.strictEqual(rootNode.getType(), "root"); + + const flipNode = rootNode.getChildren()[0] as Flip; + assert.isDefined(flipNode); + assert.strictEqual(flipNode.getType(), "flip"); + assert.strictEqual(flipNode.getChildren().length, 1); + + const actionNode = flipNode.getChildren()[0] as Action; + assert.isDefined(actionNode); + assert.strictEqual(actionNode.getType(), "action"); + }); + + it("globally registered", () => { + BehaviourTree.register("sub-tree", { + type: "root", + child: { + type: "action", + call: "noop" + } + }); + + const definition: RootNodeDefinition[] = [ + { + type: "root", + child: { + type: "flip", + child: { + type: "branch", + ref: "sub-tree" + } + } + } + ]; + + const rootNode = buildRootNode(definition); + + assert.isDefined(rootNode); + assert.strictEqual(rootNode.getType(), "root"); + + const flipNode = rootNode.getChildren()[0] as Flip; + assert.isDefined(flipNode); + assert.strictEqual(flipNode.getType(), "flip"); + assert.strictEqual(flipNode.getChildren().length, 1); + + const actionNode = flipNode.getChildren()[0] as Action; + assert.isDefined(actionNode); + assert.strictEqual(actionNode.getType(), "action"); + }); + }); + + it("and errors if any referenced subtree root node is not included as part of the definition and is not a globally registered subtree", () => { + const definition: RootNodeDefinition[] = [ + { + type: "root", + child: { + type: "flip", + child: { + type: "branch", + ref: "sub-tree" + } + } + } + ]; + + assert.throws( + () => buildRootNode(definition), + Error, + "primary tree has branch node that references root node 'sub-tree' which has not been defined" + ); + }); + }); +}); diff --git a/test/BehaviourTreeDefinitionValidator.spec.ts b/test/BehaviourTreeDefinitionValidator.spec.ts new file mode 100644 index 0000000..f1d2002 --- /dev/null +++ b/test/BehaviourTreeDefinitionValidator.spec.ts @@ -0,0 +1,178 @@ +import { assert } from "chai"; + +import { validateDefinition } from "../src/index"; + +describe("The validateDefinition function takes a tree definition as an argument and", () => { + // Helper function to carry out the validation and verify the expected result. + const verifyResult = (definition: any, success: boolean, errorMessage: string) => { + // Do the actual validation. + const result = validateDefinition(definition); + + // Verify the result matches the expected succeeded state and error message. + assert.deepEqual(result, success ? { succeeded: true } : { succeeded: false, errorMessage }); + }; + + describe("returns a validation failure when", () => { + describe("the definition doesn't contain a main root node (has no root node identifier defined)", () => { + it("(MDSL)", () => { + verifyResult( + "root [not-main-root] { action [noop] }", + false, + "expected single unnamed root node at base of definition to act as main root" + ); + }); + + it("(JSON)", () => { + const definition = { + id: "not-main-root", + type: "root", + child: { + type: "action", + call: "noop" + } + }; + + // The definition can be either an array (of root node definitions) or an object (the single primary root node definition), verify both. + verifyResult( + definition, + false, + "expected single root node without 'id' property defined to act as main root" + ); + verifyResult( + [definition], + false, + "expected single root node without 'id' property defined to act as main root" + ); + }); + }); + + describe("the root node in the definition is not actually a root node", () => { + it("(MDSL)", () => { + verifyResult("action [noop]", false, "expected root node at base of definition"); + }); + + it("(JSON)", () => { + const definition = { + type: "action", + call: "noop" + }; + + // The definition can be either an array (of root node definitions) or an object (the single primary root node definition), verify both. + verifyResult( + definition, + false, + "expected root node at base of definition but got node of type 'action'" + ); + verifyResult( + [definition], + false, + "expected root node at base of definition but got node of type 'action'" + ); + }); + }); + + describe("there are duplicate root node identifiers", () => { + it("(MDSL)", () => { + verifyResult( + "root { action [noop] } root [sub-root-node] { action [noop] } root [sub-root-node] { action [noop] }", + false, + "multiple root nodes found with duplicate name 'sub-root-node'" + ); + }); + + it("(JSON)", () => { + verifyResult( + [ + { + type: "root", + child: { + type: "action", + call: "noop" + } + }, + { + id: "sub-root-node", + type: "root", + child: { + type: "action", + call: "noop" + } + }, + { + id: "sub-root-node", + type: "root", + child: { + type: "action", + call: "noop" + } + } + ], + false, + "multiple root nodes found with duplicate 'id' property value of 'sub-root-node'" + ); + }); + }); + + describe("there are circular dependencies found in any branch node references", () => { + it("(MDSL)", () => { + verifyResult( + "root { branch [RN_A] } root [RN_A] { branch [RN_B] } root [RN_B] { branch [RN_C] } root [RN_C] { branch [RN_A] }", + false, + "circular dependency found in branch node references: RN_A => RN_B => RN_C => RN_A" + ); + }); + + it("(JSON)", () => { + verifyResult( + [ + { + type: "root", + child: { + type: "branch", + ref: "RN_A" + } + }, + { + id: "RN_A", + type: "root", + child: { + type: "branch", + ref: "RN_B" + } + }, + { + id: "RN_B", + type: "root", + child: { + type: "branch", + ref: "RN_C" + } + }, + { + id: "RN_C", + type: "root", + child: { + type: "branch", + ref: "RN_A" + } + } + ], + false, + "circular dependency found in branch node references: RN_A => RN_B => RN_C => RN_A" + ); + }); + }); + }); + + describe("returns a validation failure when the definition is", () => { + it("null", () => verifyResult(null, false, "definition is null or undefined")); + + it("undefined", () => verifyResult(undefined, false, "definition is null or undefined")); + + it("an unexpected type", () => { + verifyResult(true, false, "unexpected definition type of 'boolean'"); + verifyResult(false, false, "unexpected definition type of 'boolean'"); + verifyResult(42, false, "unexpected definition type of 'number'"); + }); + }); +}); diff --git a/test/TestUtilities.ts b/test/TestUtilities.ts new file mode 100644 index 0000000..fc05e01 --- /dev/null +++ b/test/TestUtilities.ts @@ -0,0 +1,25 @@ +import { BehaviourTree, FlattenedTreeNode } from "../src/index"; +import { AnyNodeDefinition } from "../src/BehaviourTreeDefinition"; + +/** + * Get the flattened tree node for the specified node type and caption from the given behaviour tree instance, or error if it doesn't exist. + * @param tree The behaviour tree instance. + * @param type The type of the node to get. + * @param caption The caption of the node to get. + * @returns The flattened tree node for the specified node type and caption from the given behaviour tree instance. + */ +export function findNode(tree: BehaviourTree, type: AnyNodeDefinition["type"], caption?: string): FlattenedTreeNode { + const node = tree + .getFlattenedNodeDetails() + .find((node) => node.type === type && (!caption || node.caption === caption)); + + if (!node) { + throw new Error( + caption + ? `cannot find flattened tree node with type: '${type}' caption: '${caption}'` + : `cannot find flattened tree node with type: '${type}'` + ); + } + + return node; +} diff --git a/test/attributes/callbacks/Entry.spec.ts b/test/attributes/callbacks/Entry.spec.ts new file mode 100644 index 0000000..7119b62 --- /dev/null +++ b/test/attributes/callbacks/Entry.spec.ts @@ -0,0 +1,102 @@ +import { assert } from "chai"; +import sinon from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +describe("An Entry callback node attribute", () => { + describe("on tree initialisation", () => { + describe("will error if no function name is defined", () => { + it("(MDSL)", () => { + const definition = "root { action [noop] entry() }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected agent function or registered function name identifier argument for attribute" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "noop", + entry: {} as any + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected 'call' property for attribute 'entry' to be a non-empty string for 'action' node at depth '1'" + ); + }); + }); + }); + + describe("when the node is updated as part of a tree step will call the entry function if it is the first time the node is being updated", () => { + it("(MDSL)", () => { + const definition = `root entry(onEntry, "root") { action [someAction] entry(onEntry, "action") }`; + const agent = { + someAction: () => State.RUNNING, + onEntry: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + assert.isNotTrue(agent.onEntry.called); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.onEntry.calledTwice); + assert.isTrue(agent.onEntry.calledWith("root")); + assert.isTrue(agent.onEntry.calledWith("action")); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onEntry.calledTwice); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + entry: { + call: "onEntry", + args: ["root"] + }, + child: { + type: "action", + entry: { + call: "onEntry", + args: ["action"] + }, + call: "someAction" + } + }; + const agent = { + someAction: () => State.RUNNING, + onEntry: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + assert.isNotTrue(agent.onEntry.called); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.onEntry.calledTwice); + assert.isTrue(agent.onEntry.calledWith("root")); + assert.isTrue(agent.onEntry.calledWith("action")); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onEntry.calledTwice); + }); + }); +}); diff --git a/test/attributes/callbacks/Exit.spec.ts b/test/attributes/callbacks/Exit.spec.ts new file mode 100644 index 0000000..45e671b --- /dev/null +++ b/test/attributes/callbacks/Exit.spec.ts @@ -0,0 +1,233 @@ +import { assert } from "chai"; +import sinon from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +describe("An Exit callback node attribute", () => { + describe("on tree initialisation", () => { + describe("will error if no function name is defined", () => { + it("(MDSL)", () => { + const definition = "root { action [noop] exit() }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected agent function or registered function name identifier argument for attribute" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "noop", + exit: {} as any + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected 'call' property for attribute 'exit' to be a non-empty string for 'action' node at depth '1'" + ); + }); + }); + }); + + describe("when the node is updated as part of a tree step will call the exit function when the node moves out of the RUNNING state", () => { + describe("and to a SUCCEEDED state", () => { + it("(MDSL)", () => { + const definition = `root exit(onExit, "root") { action [someAction] exit(onExit, "action") }`; + const agent = { + someAction: () => State.RUNNING, + onExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isNotTrue(agent.onExit.called); + assert.isTrue(tree.isRunning()); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onExit.calledTwice); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: true, aborted: false }), "root")); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: true, aborted: false }), "action")); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + exit: { + call: "onExit", + args: ["root"] + }, + child: { + type: "action", + exit: { + call: "onExit", + args: ["action"] + }, + call: "someAction" + } + }; + const agent = { + someAction: () => State.RUNNING, + onExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isNotTrue(agent.onExit.called); + assert.isTrue(tree.isRunning()); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onExit.calledTwice); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: true, aborted: false }), "root")); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: true, aborted: false }), "action")); + }); + }); + + describe("and to a FAILED state when the node execution", () => { + describe("was not aborted", () => { + it("(MDSL)", () => { + const definition = `root exit(onExit, "root") { action [someAction] exit(onExit, "action") }`; + const agent = { + someAction: () => State.RUNNING, + onExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isNotTrue(agent.onExit.called); + assert.isTrue(tree.isRunning()); + + agent.someAction = () => State.FAILED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onExit.calledTwice); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: false, aborted: false }), "root")); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: false, aborted: false }), "action")); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + exit: { + call: "onExit", + args: ["root"] + }, + child: { + type: "action", + exit: { + call: "onExit", + args: ["action"] + }, + call: "someAction" + } + }; + const agent = { + someAction: () => State.RUNNING, + onExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isNotTrue(agent.onExit.called); + assert.isTrue(tree.isRunning()); + + agent.someAction = () => State.FAILED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onExit.calledTwice); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: false, aborted: false }), "root")); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: false, aborted: false }), "action")); + }); + }); + + describe("was aborted", () => { + it("(MDSL)", () => { + const definition = `root exit(onExit, "root") { action [someAction] exit(onExit, "action") while(someCondition) }`; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => true, + onExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isNotTrue(agent.onExit.called); + assert.isTrue(tree.isRunning()); + + // Cause the running action node to be aborted on the next tree step. + agent.someCondition = () => false; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onExit.calledTwice); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: false, aborted: false }), "root")); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: false, aborted: true }), "action")); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + exit: { + call: "onExit", + args: ["root"] + }, + child: { + type: "action", + exit: { + call: "onExit", + args: ["action"] + }, + while: { + call: "someCondition" + }, + call: "someAction" + } + }; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => true, + onExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isNotTrue(agent.onExit.called); + assert.isTrue(tree.isRunning()); + + // Cause the running action node to be aborted on the next tree step. + agent.someCondition = () => false; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onExit.calledTwice); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: false, aborted: false }), "root")); + assert.isTrue(agent.onExit.calledWith(sinon.match({ succeeded: false, aborted: true }), "action")); + }); + }); + }); + }); +}); diff --git a/test/attributes/callbacks/Step.spec.ts b/test/attributes/callbacks/Step.spec.ts new file mode 100644 index 0000000..b2297af --- /dev/null +++ b/test/attributes/callbacks/Step.spec.ts @@ -0,0 +1,122 @@ +import { assert } from "chai"; +import sinon from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +describe("A Step callback node attribute", () => { + describe("on tree initialisation", () => { + describe("will error if no function name is defined", () => { + it("(MDSL)", () => { + const definition = "root { action [noop] step() }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected agent function or registered function name identifier argument for attribute" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "noop", + step: {} as any + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected 'call' property for attribute 'step' to be a non-empty string for 'action' node at depth '1'" + ); + }); + }); + }); + + describe("when the node is updated as part of a tree step will call the step function", () => { + it("(MDSL)", () => { + const definition = `root step(onRootStep, "root") { action [someAction] step(onActionStep, "action") }`; + const agent = { + someAction: () => State.RUNNING, + onRootStep: sinon.stub(), + onActionStep: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + assert.isNotTrue(agent.onRootStep.called); + assert.isNotTrue(agent.onActionStep.called); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.onRootStep.calledOnce); + assert.isTrue(agent.onActionStep.calledOnce); + assert.isTrue(agent.onRootStep.calledWith("root")); + assert.isTrue(agent.onActionStep.calledWith("action")); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.onRootStep.calledTwice); + assert.isTrue(agent.onActionStep.calledTwice); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onRootStep.calledThrice); + assert.isTrue(agent.onActionStep.calledThrice); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + step: { + call: "onRootStep", + args: ["root"] + }, + child: { + type: "action", + step: { + call: "onActionStep", + args: ["action"] + }, + call: "someAction" + } + }; + const agent = { + someAction: () => State.RUNNING, + onRootStep: sinon.stub(), + onActionStep: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + assert.isNotTrue(agent.onRootStep.called); + assert.isNotTrue(agent.onActionStep.called); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.onRootStep.calledOnce); + assert.isTrue(agent.onActionStep.calledOnce); + assert.isTrue(agent.onRootStep.calledWith("root")); + assert.isTrue(agent.onActionStep.calledWith("action")); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.onRootStep.calledTwice); + assert.isTrue(agent.onActionStep.calledTwice); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.onRootStep.calledThrice); + assert.isTrue(agent.onActionStep.calledThrice); + }); + }); +}); diff --git a/test/attributes/guards/Until.spec.ts b/test/attributes/guards/Until.spec.ts new file mode 100644 index 0000000..3cd1fa0 --- /dev/null +++ b/test/attributes/guards/Until.spec.ts @@ -0,0 +1,316 @@ +import { assert } from "chai"; +import sinon from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; +import { findNode } from "../../TestUtilities"; + +describe("An Until guard node attribute", () => { + describe("on tree initialisation", () => { + describe("will error if no function name is defined", () => { + it("(MDSL)", () => { + const definition = "root { action [noop] until() }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected agent function or registered function name identifier argument for attribute" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "noop", + until: {} as any + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected 'call' property for attribute 'until' to be a non-empty string for 'action' node at depth '1'" + ); + }); + }); + }); + + describe("when the node is updated as part of a tree step will call the guard function and", () => { + describe("abort the running node and an child nodes and move them to a FAILED state if the function returns a value of true", () => { + it("(MDSL)", () => { + const definition = `root { sequence until(someCondition, "condition-argument") { action [someAction] exit(onActionExit) } }`; + const agent = { + someAction: () => State.RUNNING, + someCondition: sinon.stub().returns(false), + onActionExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action").state, State.RUNNING); + + agent.someCondition.returns(true); + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.isTrue(agent.onActionExit.calledWith(sinon.match({ succeeded: false, aborted: true }))); + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action").state, State.READY); + + tree.reset(); + + assert.isFalse(tree.isRunning()); + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action").state, State.READY); + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action").state, State.READY); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + until: { + call: "someCondition", + args: ["condition-argument"] + }, + children: [ + { + type: "action", + exit: { + call: "onActionExit" + }, + call: "someAction" + } + ] + } + }; + const agent = { + someAction: () => State.RUNNING, + someCondition: sinon.stub().returns(false), + onActionExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action").state, State.RUNNING); + + agent.someCondition.returns(true); + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.isTrue(agent.onActionExit.calledWith(sinon.match({ succeeded: false, aborted: true }))); + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action").state, State.READY); + + tree.reset(); + + assert.isFalse(tree.isRunning()); + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action").state, State.READY); + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action").state, State.READY); + }); + }); + + describe("does nothing if the function returns a value of false", () => { + it("(MDSL)", () => { + const definition = `root { sequence until(someCondition, "condition-argument") { action [someAction] } }`; + const agent = { + someAction: () => State.RUNNING, + someCondition: sinon.stub().returns(false) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action").state, State.RUNNING); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "sequence").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action").state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + until: { + call: "someCondition", + args: ["condition-argument"] + }, + children: [ + { + type: "action", + call: "someAction" + } + ] + } + }; + const agent = { + someAction: () => State.RUNNING, + someCondition: sinon.stub().returns(false) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action").state, State.RUNNING); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "sequence").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action").state, State.SUCCEEDED); + }); + }); + + describe("throws an error if the guard condition function ", () => { + describe("does not return a boolean value", () => { + it("(MDSL)", () => { + const definition = `root { sequence until(someCondition, "condition-argument") { action [someAction] } }`; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => "invalid-response" + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "expected guard condition function 'someCondition' to return a boolean but returned 'invalid-response'" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + until: { + call: "someCondition", + args: ["condition-argument"] + }, + children: [ + { + type: "action", + call: "someAction" + } + ] + } + }; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => "invalid-response" + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "expected guard condition function 'someCondition' to return a boolean but returned 'invalid-response'" + ); + }); + }); + + describe("throws an exception", () => { + it("(MDSL)", () => { + const definition = `root { sequence until(someCondition, "condition-argument") { action [someAction] } }`; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => { + throw new Error("some-error"); + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: guard condition function 'someCondition' threw: Error: some-error" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + until: { + call: "someCondition", + args: ["condition-argument"] + }, + children: [ + { + type: "action", + call: "someAction" + } + ] + } + }; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => { + throw new Error("some-error"); + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: guard condition function 'someCondition' threw: Error: some-error" + ); + }); + }); + }); + }); +}); diff --git a/test/attributes/guards/While.spec.ts b/test/attributes/guards/While.spec.ts new file mode 100644 index 0000000..2b68817 --- /dev/null +++ b/test/attributes/guards/While.spec.ts @@ -0,0 +1,316 @@ +import { assert } from "chai"; +import sinon from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; +import { findNode } from "../../TestUtilities"; + +describe("A While guard node attribute", () => { + describe("on tree initialisation", () => { + describe("will error if no function name is defined", () => { + it("(MDSL)", () => { + const definition = "root { action [noop] while() }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected agent function or registered function name identifier argument for attribute" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "noop", + while: {} as any + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected 'call' property for attribute 'while' to be a non-empty string for 'action' node at depth '1'" + ); + }); + }); + }); + + describe("when the node is updated as part of a tree step will call the guard function and", () => { + describe("abort the running node and an child nodes and move them to a FAILED state if the function returns a value of false", () => { + it("(MDSL)", () => { + const definition = `root { sequence while(someCondition, "condition-argument") { action [someAction] exit(onActionExit) } }`; + const agent = { + someAction: () => State.RUNNING, + someCondition: sinon.stub().returns(true), + onActionExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action").state, State.RUNNING); + + agent.someCondition.returns(false); + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.isTrue(agent.onActionExit.calledWith(sinon.match({ succeeded: false, aborted: true }))); + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action").state, State.READY); + + tree.reset(); + + assert.isFalse(tree.isRunning()); + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action").state, State.READY); + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action").state, State.READY); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + while: { + call: "someCondition", + args: ["condition-argument"] + }, + children: [ + { + type: "action", + exit: { + call: "onActionExit" + }, + call: "someAction" + } + ] + } + }; + const agent = { + someAction: () => State.RUNNING, + someCondition: sinon.stub().returns(true), + onActionExit: sinon.stub() + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action").state, State.RUNNING); + + agent.someCondition.returns(false); + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.isTrue(agent.onActionExit.calledWith(sinon.match({ succeeded: false, aborted: true }))); + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action").state, State.READY); + + tree.reset(); + + assert.isFalse(tree.isRunning()); + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action").state, State.READY); + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action").state, State.READY); + }); + }); + + describe("does nothing if the function returns a value of true", () => { + it("(MDSL)", () => { + const definition = `root { sequence while(someCondition, "condition-argument") { action [someAction] } }`; + const agent = { + someAction: () => State.RUNNING, + someCondition: sinon.stub().returns(true) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action").state, State.RUNNING); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "sequence").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action").state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + while: { + call: "someCondition", + args: ["condition-argument"] + }, + children: [ + { + type: "action", + call: "someAction" + } + ] + } + }; + const agent = { + someAction: () => State.RUNNING, + someCondition: sinon.stub().returns(true) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + assert.isTrue(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action").state, State.RUNNING); + + agent.someAction = () => State.SUCCEEDED; + + tree.step(); + + assert.isFalse(tree.isRunning()); + assert.isTrue(agent.someCondition.calledWith("condition-argument")); + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "sequence").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action").state, State.SUCCEEDED); + }); + }); + + describe("throws an error if the guard condition function ", () => { + describe("does not return a boolean value", () => { + it("(MDSL)", () => { + const definition = `root { sequence while(someCondition, "condition-argument") { action [someAction] } }`; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => "invalid-response" + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "expected guard condition function 'someCondition' to return a boolean but returned 'invalid-response'" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + while: { + call: "someCondition", + args: ["condition-argument"] + }, + children: [ + { + type: "action", + call: "someAction" + } + ] + } + }; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => "invalid-response" + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "expected guard condition function 'someCondition' to return a boolean but returned 'invalid-response'" + ); + }); + }); + + describe("throws an exception", () => { + it("(MDSL)", () => { + const definition = `root { sequence while(someCondition, "condition-argument") { action [someAction] } }`; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => { + throw new Error("some-error"); + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: guard condition function 'someCondition' threw: Error: some-error" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + while: { + call: "someCondition", + args: ["condition-argument"] + }, + children: [ + { + type: "action", + call: "someAction" + } + ] + } + }; + const agent = { + someAction: () => State.RUNNING, + someCondition: () => { + throw new Error("some-error"); + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: guard condition function 'someCondition' threw: Error: some-error" + ); + }); + }); + }); + }); +}); diff --git a/test/behaviourTree.test.js b/test/behaviourTree.test.js deleted file mode 100644 index cd35a6e..0000000 --- a/test/behaviourTree.test.js +++ /dev/null @@ -1,143 +0,0 @@ -const mistreevous = require("../dist/index"); -const chai = require("chai"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => - tree.getFlattenedNodeDetails().find((node) => node.type === type && node.caption === caption); - -describe("A BehaviourTree instance", () => { - describe("has initialisation logic that", () => { - describe("should error when", () => { - it("the tree definition argument is not a string", () => { - assert.throws( - () => new mistreevous.BehaviourTree(null, {}), - Error, - "the tree definition must be a string" - ); - }); - - it("the tree definition argument is not a valid tree definition", () => { - assert.throws( - () => new mistreevous.BehaviourTree("", {}), - Error, - "error parsing tree: invalid token count" - ); - }); - - it("the tree definition argument contains unexpected tokens", () => { - assert.throws( - () => new mistreevous.BehaviourTree("invalid-token { }", {}), - Error, - "error parsing tree: unexpected token 'invalid-token'" - ); - }); - - it("the agent object is not defined", () => { - assert.throws(() => new mistreevous.BehaviourTree("", undefined), Error, "the agent must be defined"); - }); - }); - - it("should not error when the tree definition argument is a valid definition", () => { - assert.doesNotThrow(() => new mistreevous.BehaviourTree("root { action [test] }", {}), Error); - }); - }); - - it("has a 'getState' function that returns the state of the root node", () => { - let actionResult = undefined; - - const definition = "root { action [getActionResult] }"; - const agent = { getActionResult: () => actionResult }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - assert.strictEqual(tree.getState(), mistreevous.State.READY); - - tree.step(); - - assert.strictEqual(tree.getState(), mistreevous.State.RUNNING); - - actionResult = mistreevous.State.SUCCEEDED; - - tree.step(); - - assert.strictEqual(tree.getState(), mistreevous.State.SUCCEEDED); - }); - - it("has an 'isRunning' function that returns a flag defining whether the tree is in a running state", () => { - let actionResult = undefined; - - const definition = "root { action [getActionResult] }"; - const agent = { getActionResult: () => actionResult }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - assert.strictEqual(tree.isRunning(), false); - - tree.step(); - - assert.strictEqual(tree.isRunning(), true); - - actionResult = mistreevous.State.SUCCEEDED; - - tree.step(); - - assert.strictEqual(tree.isRunning(), false); - }); - - it("has a 'reset' function that resets the tree from the root node outwards to each nested node, giving each a state of READY", () => { - const definition = "root { action [getActionResult] }"; - const agent = { getActionResult: () => mistreevous.State.SUCCEEDED }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - assert.strictEqual(tree.getState(), mistreevous.State.READY); - - tree.step(); - - assert.strictEqual(tree.getState(), mistreevous.State.SUCCEEDED); - - tree.reset(); - - assert.strictEqual(tree.getState(), mistreevous.State.READY); - }); - - it("has a 'step' function that .....", () => { - const definition = - "root { sequence { action [getActionResult0] action [getActionResult1] action [getActionResult2] action [getActionResult3] } }"; - const agent = { - getActionResult0: () => mistreevous.State.SUCCEEDED, - getActionResult1: () => mistreevous.State.SUCCEEDED, - getActionResult2: () => undefined, - getActionResult3: () => mistreevous.State.SUCCEEDED - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - assert.strictEqual(findNode(tree, "action", "getActionResult0").state, mistreevous.State.READY); - assert.strictEqual(findNode(tree, "action", "getActionResult1").state, mistreevous.State.READY); - assert.strictEqual(findNode(tree, "action", "getActionResult2").state, mistreevous.State.READY); - assert.strictEqual(findNode(tree, "action", "getActionResult3").state, mistreevous.State.READY); - - tree.step(); - - assert.strictEqual(findNode(tree, "action", "getActionResult0").state, mistreevous.State.SUCCEEDED); - assert.strictEqual(findNode(tree, "action", "getActionResult1").state, mistreevous.State.SUCCEEDED); - assert.strictEqual(findNode(tree, "action", "getActionResult2").state, mistreevous.State.RUNNING); - assert.strictEqual(findNode(tree, "action", "getActionResult3").state, mistreevous.State.READY); - - agent.getActionResult2 = () => mistreevous.State.SUCCEEDED; - - tree.step(); - - assert.strictEqual(findNode(tree, "action", "getActionResult0").state, mistreevous.State.SUCCEEDED); - assert.strictEqual(findNode(tree, "action", "getActionResult1").state, mistreevous.State.SUCCEEDED); - assert.strictEqual(findNode(tree, "action", "getActionResult2").state, mistreevous.State.SUCCEEDED); - assert.strictEqual(findNode(tree, "action", "getActionResult3").state, mistreevous.State.SUCCEEDED); - - agent.getActionResult2 = () => undefined; - - tree.step(); - - assert.strictEqual(findNode(tree, "action", "getActionResult0").state, mistreevous.State.SUCCEEDED); - assert.strictEqual(findNode(tree, "action", "getActionResult1").state, mistreevous.State.SUCCEEDED); - assert.strictEqual(findNode(tree, "action", "getActionResult2").state, mistreevous.State.RUNNING); - assert.strictEqual(findNode(tree, "action", "getActionResult3").state, mistreevous.State.READY); - }); -}); diff --git a/test/mdsl/MDSLDefinitionParser.spec.ts b/test/mdsl/MDSLDefinitionParser.spec.ts new file mode 100644 index 0000000..a2ef418 --- /dev/null +++ b/test/mdsl/MDSLDefinitionParser.spec.ts @@ -0,0 +1,204 @@ +import { assert } from "chai"; + +import { RootNodeDefinition } from "../../src/BehaviourTreeDefinition"; +import { convertMDSLToJSON } from "../../src/mdsl/MDSLDefinitionParser"; + +describe("The MDSLDefinitionParser class has a convertMDSLToJSON function which takes a string MDSL definition and", () => { + describe("errors when", () => { + it("there is an invalid token count", () => { + const mdslDefinition = "root {"; + + assert.throws(() => convertMDSLToJSON(mdslDefinition), Error, "invalid token count"); + }); + + it("there is a scope character mismatch", () => { + const mdslDefinition = "root { flip { action [noop] }"; + + assert.throws(() => convertMDSLToJSON(mdslDefinition), Error, "scope character mismatch"); + }); + + it("a root node is not defined as the base of the definition", () => { + const mdslDefinition = "flip { action [noop] }"; + + assert.throws(() => convertMDSLToJSON(mdslDefinition), Error, "expected root node at base of definition"); + }); + + it("a root node is the child of another node", () => { + const mdslDefinition = "root { flip { root { action [noop] } } }"; + + assert.throws( + () => convertMDSLToJSON(mdslDefinition), + Error, + "a root node cannot be the child of another node" + ); + }); + + it("a decorator node has multiple child nodes", () => { + const mdslDefinition = "root { flip { action [noop] action [noop] } }"; + + assert.throws( + () => convertMDSLToJSON(mdslDefinition), + Error, + "a decorator node must only have a single child node" + ); + }); + }); + + it("returns the corresponding JSON definition", () => { + const mdslDefinition = ` + root { + selector while(IsHungry) { + sequence { + condition [HasDollars, 15] + action [OrderFood, "Pizza"] entry(PlayMusic, "pizza-song") + } + sequence { + condition [HasIngredient, "Steak"] + condition [HasIngredient, "Lobster"] + action [CookFood, "Surf 'n' Turf"] + } + sequence { + condition [HasIngredient, "Egg"] + branch [CookEggs] + } + sequence { + condition [HasIngredient, "Oats"] + action [CookFood, "Gruel"] + } + action [Starve] entry(OnStarveEntry) exit(OnStarveExit) + } + } + + root [CookEggs] { + lotto [100, 100, 5] { + action [CookFood, "Omelette"] + action [CookFood, "Scrambled Eggs"] + action [CookFood, "Fried Egg Surprise!"] + } + } + `; + + const expectedOutputDefintion: RootNodeDefinition[] = [ + { + type: "root", + child: { + type: "selector", + while: { + call: "IsHungry", + args: [] + }, + children: [ + { + type: "sequence", + children: [ + { + type: "condition", + call: "HasDollars", + args: [15] + }, + { + type: "action", + call: "OrderFood", + args: ["Pizza"], + entry: { + call: "PlayMusic", + args: ["pizza-song"] + } + } + ] + }, + { + type: "sequence", + children: [ + { + type: "condition", + call: "HasIngredient", + args: ["Steak"] + }, + { + type: "condition", + call: "HasIngredient", + args: ["Lobster"] + }, + { + type: "action", + call: "CookFood", + args: ["Surf 'n' Turf"] + } + ] + }, + { + type: "sequence", + children: [ + { + type: "condition", + call: "HasIngredient", + args: ["Egg"] + }, + { + type: "branch", + ref: "CookEggs" + } + ] + }, + { + type: "sequence", + children: [ + { + type: "condition", + call: "HasIngredient", + args: ["Oats"] + }, + { + type: "action", + call: "CookFood", + args: ["Gruel"] + } + ] + }, + { + type: "action", + call: "Starve", + args: [], + entry: { + call: "OnStarveEntry", + args: [] + }, + exit: { + call: "OnStarveExit", + args: [] + } + } + ] + } + }, + { + type: "root", + id: "CookEggs", + child: { + type: "lotto", + weights: [100, 100, 5], + children: [ + { + type: "action", + call: "CookFood", + args: ["Omelette"] + }, + { + type: "action", + call: "CookFood", + args: ["Scrambled Eggs"] + }, + { + type: "action", + call: "CookFood", + args: ["Fried Egg Surprise!"] + } + ] + } + } + ]; + + assert.deepEqual(convertMDSLToJSON(mdslDefinition), expectedOutputDefintion); + }); +}); diff --git a/test/nodes/composite/Lotto.spec.ts b/test/nodes/composite/Lotto.spec.ts new file mode 100644 index 0000000..1c4d400 --- /dev/null +++ b/test/nodes/composite/Lotto.spec.ts @@ -0,0 +1,470 @@ +import { assert } from "chai"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +import { findNode } from "../../TestUtilities"; + +describe("A Lotto node", () => { + describe("on tree initialisation", () => { + describe("will error if the node does not have at least one child", () => { + it("(MDSL)", () => { + const definition = "root { lotto {} }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a lotto node must have at least a single child" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "lotto", + children: [] + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected non-empty 'children' array to be defined for lotto node at depth '1'" + ); + }); + }); + + describe("will error if any optional weights are defined and", () => { + describe("are not positive integer values", () => { + it("(MDSL)", () => { + let definition = "root { lotto [-1] { action [SomeAction] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: lotto node weight arguments must be positive integer values" + ); + + definition = "root { lotto [1.234] { action [SomeAction] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: lotto node weight arguments must be positive integer values" + ); + + definition = 'root { lotto ["some-string"] { action [SomeAction] } }'; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: lotto node weight arguments must be positive integer values" + ); + + definition = "root { lotto [false] { action [SomeAction] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: lotto node weight arguments must be positive integer values" + ); + }); + + it("(JSON)", () => { + let definition: RootNodeDefinition = { + type: "root", + child: { + type: "lotto", + weights: [-1], + children: [ + { + type: "action", + call: "SomeAction" + } + ] + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '1'" + ); + + definition = { + type: "root", + child: { + type: "lotto", + weights: [1.234], + children: [ + { + type: "action", + call: "SomeAction" + } + ] + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '1'" + ); + + definition = { + type: "root", + child: { + type: "lotto", + weights: ["some-string"], + children: [ + { + type: "action", + call: "SomeAction" + } + ] + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '1'" + ); + + definition = { + type: "root", + child: { + type: "lotto", + weights: [false], + children: [ + { + type: "action", + call: "SomeAction" + } + ] + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '1'" + ); + }); + }); + + describe("the number of weights does not match the number of child nodes", () => { + it("(MDSL)", () => { + const definition = "root { lotto [1] { action [SomeAction] action [SomeOtherAction] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected a number of weight arguments matching the number of child nodes for lotto node" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "lotto", + weights: [1], + children: [ + { + type: "action", + call: "SomeAction" + }, + { + type: "action", + call: "SomeOtherAction" + } + ] + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected an array of positive integer weight values with a length matching the number of child nodes for 'weights' property if defined for lotto node at depth '1'" + ); + }); + }); + }); + }); + + describe("when updated as part of a tree step will", () => { + describe("when initially in the READY state will select a child node at random to be the single active child node", () => { + it("(MDSL)", () => { + const definition = "root { lotto { condition [IsTrue] } }"; + const agent = { IsTrue: () => true }; + const tree = new BehaviourTree(definition, agent); + + let lottoNode = findNode(tree, "lotto"); + let childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.READY); + assert.strictEqual(childNode.state, State.READY); + + tree.step(); + + lottoNode = findNode(tree, "lotto"); + childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.SUCCEEDED); + assert.strictEqual(childNode.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "lotto", + children: [ + { + type: "condition", + call: "IsTrue" + } + ] + } + }; + const agent = { IsTrue: () => true }; + const tree = new BehaviourTree(definition, agent); + + let lottoNode = findNode(tree, "lotto"); + let childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.READY); + assert.strictEqual(childNode.state, State.READY); + + tree.step(); + + lottoNode = findNode(tree, "lotto"); + childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.SUCCEEDED); + assert.strictEqual(childNode.state, State.SUCCEEDED); + }); + }); + + describe("will use the 'random' function to select the single active child if it was defined as a behaviour tree option", () => { + it("(MDSL)", () => { + const definition = + "root { lotto { condition [IsFalse] condition [IsFalse] condition [IsFalse] condition [IsTrue] condition [IsFalse] condition [IsFalse] } }"; + const agent = { + IsTrue: () => true, + IsFalse: () => false + }; + const options = { + // Usually this would return a new pseudo-random number each time, but for the sake of this test we + // just want to make sure that the number we return actually has an impact on which child node of the + // lotto node is selected. A value of 0.6 should always result in the fourth child out of six being picked. + random: () => 0.6 + }; + const tree = new BehaviourTree(definition, agent, options); + + let lottoNode = findNode(tree, "lotto"); + assert.strictEqual(lottoNode.state, State.READY); + + tree.step(); + + // Check that the lotto node has moved into the SUCCEEDED state. This would only + // have happened if the fourth condition node was selected by the lotto node. + lottoNode = findNode(tree, "lotto"); + assert.strictEqual(lottoNode.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "lotto", + children: [ + { + type: "condition", + call: "IsFalse" + }, + { + type: "condition", + call: "IsFalse" + }, + { + type: "condition", + call: "IsFalse" + }, + { + type: "condition", + call: "IsTrue" + }, + { + type: "condition", + call: "IsFalse" + }, + { + type: "condition", + call: "IsFalse" + } + ] + } + }; + const agent = { + IsTrue: () => true, + IsFalse: () => false + }; + const options = { + // Usually this would return a new pseudo-random number each time, but for the sake of this test we + // just want to make sure that the number we return actually has an impact on which child node of the + // lotto node is selected. A value of 0.6 should always result in the fourth child out of six being picked. + random: () => 0.6 + }; + const tree = new BehaviourTree(definition, agent, options); + + let lottoNode = findNode(tree, "lotto"); + assert.strictEqual(lottoNode.state, State.READY); + + tree.step(); + + // Check that the lotto node has moved into the SUCCEEDED state. This would only + // have happened if the fourth condition node was selected by the lotto node. + lottoNode = findNode(tree, "lotto"); + assert.strictEqual(lottoNode.state, State.SUCCEEDED); + }); + }); + + it("can optionally have weights defined and will use them to influence the selection of the single active child", () => { + // TODO Need to figure out how to spy on createLotto as there is no other way to test this. + }); + + describe("move to the SUCCESS state if the selected child node moves to the SUCCESS state", () => { + it("(MDSL)", () => { + const definition = "root { lotto { condition [IsTrue] } }"; + const agent = { IsTrue: () => true }; + const tree = new BehaviourTree(definition, agent); + + let lottoNode = findNode(tree, "lotto"); + let childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.READY); + assert.strictEqual(childNode.state, State.READY); + + tree.step(); + + lottoNode = findNode(tree, "lotto"); + childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.SUCCEEDED); + assert.strictEqual(childNode.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "lotto", + children: [ + { + type: "condition", + call: "IsTrue" + } + ] + } + }; + const agent = { IsTrue: () => true }; + const tree = new BehaviourTree(definition, agent); + + let lottoNode = findNode(tree, "lotto"); + let childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.READY); + assert.strictEqual(childNode.state, State.READY); + + tree.step(); + + lottoNode = findNode(tree, "lotto"); + childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.SUCCEEDED); + assert.strictEqual(childNode.state, State.SUCCEEDED); + }); + }); + + describe("move to the FAILED state if the selected child node moves to the FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { lotto { condition [IsFalse] } }"; + const agent = { IsFalse: () => false }; + const tree = new BehaviourTree(definition, agent); + + let lottoNode = findNode(tree, "lotto"); + let childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.READY); + assert.strictEqual(childNode.state, State.READY); + + tree.step(); + + lottoNode = findNode(tree, "lotto"); + childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.FAILED); + assert.strictEqual(childNode.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "lotto", + children: [ + { + type: "condition", + call: "IsFalse" + } + ] + } + }; + const agent = { IsFalse: () => false }; + const tree = new BehaviourTree(definition, agent); + + let lottoNode = findNode(tree, "lotto"); + let childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.READY); + assert.strictEqual(childNode.state, State.READY); + + tree.step(); + + lottoNode = findNode(tree, "lotto"); + childNode = findNode(tree, "condition"); + assert.strictEqual(lottoNode.state, State.FAILED); + assert.strictEqual(childNode.state, State.FAILED); + }); + }); + + describe("move to the RUNNING state if the selected child node is in the RUNNING state", () => { + it("(MDSL)", () => { + const definition = "root { lotto { action [someAction] } }"; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let lottoNode = findNode(tree, "lotto"); + let actionNode = findNode(tree, "action"); + assert.strictEqual(lottoNode.state, State.READY); + assert.strictEqual(actionNode.state, State.READY); + + tree.step(); + + lottoNode = findNode(tree, "lotto"); + actionNode = findNode(tree, "action"); + assert.strictEqual(lottoNode.state, State.RUNNING); + assert.strictEqual(actionNode.state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "lotto", + children: [ + { + type: "action", + call: "someAction" + } + ] + } + }; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let lottoNode = findNode(tree, "lotto"); + let actionNode = findNode(tree, "action"); + assert.strictEqual(lottoNode.state, State.READY); + assert.strictEqual(actionNode.state, State.READY); + + tree.step(); + + lottoNode = findNode(tree, "lotto"); + actionNode = findNode(tree, "action"); + assert.strictEqual(lottoNode.state, State.RUNNING); + assert.strictEqual(actionNode.state, State.RUNNING); + }); + }); + }); +}); diff --git a/test/nodes/composite/Lotto.test.js b/test/nodes/composite/Lotto.test.js deleted file mode 100644 index b0d580c..0000000 --- a/test/nodes/composite/Lotto.test.js +++ /dev/null @@ -1,119 +0,0 @@ -const mistreevous = require("../../../dist/index"); -const chai = require("chai"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => tree.getFlattenedNodeDetails().find((node) => node.type === type); - -describe("A Lotto node", () => { - describe("on tree initialisation", () => { - it("will error if the node does not have at least one child", () => { - const definition = "root { lotto {} }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: a lotto node must have at least a single child" - ); - }); - }); - - describe("when updated as part of a tree step will", () => { - it("when initially in the READY state will select a child node at random to be the single active child node", () => { - const definition = "root { lotto { condition [IsTrue] } }"; - const agent = { IsTrue: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let lottoNode = findNode(tree, "lotto"); - let childNode = findNode(tree, "condition"); - assert.strictEqual(lottoNode.state, mistreevous.State.READY); - assert.strictEqual(childNode.state, mistreevous.State.READY); - - tree.step(); - - lottoNode = findNode(tree, "lotto"); - childNode = findNode(tree, "condition"); - assert.strictEqual(lottoNode.state, mistreevous.State.SUCCEEDED); - assert.strictEqual(childNode.state, mistreevous.State.SUCCEEDED); - }); - - it("will use the 'random' function to select the single active child if it was defined as a behaviour tree option", () => { - const definition = - "root { lotto { condition [IsFalse] condition [IsFalse] condition [IsFalse] condition [IsTrue] condition [IsFalse] condition [IsFalse] } }"; - const agent = { - IsTrue: () => true, - IsFalse: () => false - }; - const options = { - // Usually this would return a new pseudo-random number each time, but for the sake of this test we - // just want to make sure that the number we return actually has an impact on which child node of the - // lotto node is selected. A value of 0.6 should always result in the fourth child out of six being picked. - random: () => 0.6 - }; - const tree = new mistreevous.BehaviourTree(definition, agent, options); - - let lottoNode = findNode(tree, "lotto"); - assert.strictEqual(lottoNode.state, mistreevous.State.READY); - - tree.step(); - - // Check that the lotto node has moved into the SUCCEEDED state. This would only - // have happened if the fourth condition node was selected by the lotto node. - lottoNode = findNode(tree, "lotto"); - assert.strictEqual(lottoNode.state, mistreevous.State.SUCCEEDED); - }); - - it("move to the SUCCESS state if the selected child node moves to the SUCCESS state", () => { - const definition = "root { lotto { condition [IsTrue] } }"; - const agent = { IsTrue: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let lottoNode = findNode(tree, "lotto"); - let childNode = findNode(tree, "condition"); - assert.strictEqual(lottoNode.state, mistreevous.State.READY); - assert.strictEqual(childNode.state, mistreevous.State.READY); - - tree.step(); - - lottoNode = findNode(tree, "lotto"); - childNode = findNode(tree, "condition"); - assert.strictEqual(lottoNode.state, mistreevous.State.SUCCEEDED); - assert.strictEqual(childNode.state, mistreevous.State.SUCCEEDED); - }); - - it("move to the FAILED state if the selected child node moves to the FAILED state", () => { - const definition = "root { lotto { condition [IsFalse] } }"; - const agent = { IsFalse: () => false }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let lottoNode = findNode(tree, "lotto"); - let childNode = findNode(tree, "condition"); - assert.strictEqual(lottoNode.state, mistreevous.State.READY); - assert.strictEqual(childNode.state, mistreevous.State.READY); - - tree.step(); - - lottoNode = findNode(tree, "lotto"); - childNode = findNode(tree, "condition"); - assert.strictEqual(lottoNode.state, mistreevous.State.FAILED); - assert.strictEqual(childNode.state, mistreevous.State.FAILED); - }); - - it("move to the RUNNING state if the selected child node does not move to the SUCCESS or FAILED state", () => { - const definition = "root { lotto { action [someAction] } }"; - const agent = { someAction: () => {} }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let lottoNode = findNode(tree, "lotto"); - let actionNode = findNode(tree, "action"); - assert.strictEqual(lottoNode.state, mistreevous.State.READY); - assert.strictEqual(actionNode.state, mistreevous.State.READY); - - tree.step(); - - lottoNode = findNode(tree, "lotto"); - actionNode = findNode(tree, "action"); - assert.strictEqual(lottoNode.state, mistreevous.State.RUNNING); - assert.strictEqual(actionNode.state, mistreevous.State.RUNNING); - }); - }); -}); diff --git a/test/nodes/composite/Parallel.spec.ts b/test/nodes/composite/Parallel.spec.ts new file mode 100644 index 0000000..525cf24 --- /dev/null +++ b/test/nodes/composite/Parallel.spec.ts @@ -0,0 +1,269 @@ +import { assert } from "chai"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +import { findNode } from "../../TestUtilities"; + +describe("A Parallel node", () => { + describe("on tree initialisation", () => { + describe("will error if the node does not have at least one child", () => { + it("(MDSL)", () => { + const definition = "root { parallel {} }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a parallel node must have at least a single child" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "parallel", + children: [] + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected non-empty 'children' array to be defined for parallel node at depth '1'" + ); + }); + }); + }); + + describe("when updated as part of a tree step will", () => { + describe("update each child node concurrently", () => { + it("(MDSL)", () => { + const definition = "root { parallel { action [actionRunning1] action [actionRunning2] } }"; + const agent = { + actionRunning1: () => State.RUNNING, + actionRunning2: () => State.RUNNING + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "parallel").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionRunning1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionRunning2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "parallel").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionRunning1").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionRunning2").state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "parallel", + children: [ + { + type: "action", + call: "actionRunning1" + }, + { + type: "action", + call: "actionRunning2" + } + ] + } + }; + const agent = { + actionRunning1: () => State.RUNNING, + actionRunning2: () => State.RUNNING + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "parallel").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionRunning1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionRunning2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "parallel").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionRunning1").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionRunning2").state, State.RUNNING); + }); + }); + + describe("move to the FAILED state if any child node moves to the FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { parallel { action [action1] action [action2] } }"; + const agent = { + action1: () => State.RUNNING, + action2: () => State.RUNNING + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "parallel").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "parallel").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action1").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action2").state, State.RUNNING); + + agent.action2 = () => State.FAILED; + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "parallel").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "action1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action2").state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "parallel", + children: [ + { + type: "action", + call: "action1" + }, + { + type: "action", + call: "action2" + } + ] + } + }; + const agent = { + action1: () => State.RUNNING, + action2: () => State.RUNNING + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "parallel").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "parallel").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action1").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action2").state, State.RUNNING); + + agent.action2 = () => State.FAILED; + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "parallel").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "action1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action2").state, State.FAILED); + }); + }); + + describe("move to the SUCCEEDED state if any child node moves to the SUCCEEDED state", () => { + it("(MDSL)", () => { + const definition = "root { parallel { action [action1] action [action2] } }"; + const agent = { + action1: () => State.RUNNING, + action2: () => State.RUNNING + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "parallel").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "parallel").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action1").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action2").state, State.RUNNING); + + agent.action1 = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "parallel").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "action2").state, State.RUNNING); + + agent.action2 = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "parallel").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "action1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "action2").state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "parallel", + children: [ + { + type: "action", + call: "action1" + }, + { + type: "action", + call: "action2" + } + ] + } + }; + const agent = { + action1: () => State.RUNNING, + action2: () => State.RUNNING + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "parallel").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "action2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "parallel").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action1").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action2").state, State.RUNNING); + + agent.action1 = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "parallel").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "action1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "action2").state, State.RUNNING); + + agent.action2 = () => State.SUCCEEDED; + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "parallel").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "action1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "action2").state, State.SUCCEEDED); + }); + }); + }); +}); diff --git a/test/nodes/composite/Selector.spec.ts b/test/nodes/composite/Selector.spec.ts new file mode 100644 index 0000000..ab80bd3 --- /dev/null +++ b/test/nodes/composite/Selector.spec.ts @@ -0,0 +1,237 @@ +import { assert } from "chai"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +import { findNode } from "../../TestUtilities"; + +describe("A Selector node", () => { + describe("on tree initialisation", () => { + describe("will error if the node does not have at least one child", () => { + it("(MDSL)", () => { + const definition = "root { selector {} }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a selector node must have at least a single child" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "selector", + children: [] + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected non-empty 'children' array to be defined for selector node at depth '1'" + ); + }); + }); + }); + + describe("when updated as part of a tree step will", () => { + describe("move to the FAILED state if all child nodes move to the FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { selector { action [actionFail1] action [actionFail2] } }"; + const agent = { + actionFail1: () => State.FAILED, + actionFail2: () => State.FAILED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "selector").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "selector").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionFail1").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionFail2").state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "selector", + children: [ + { + type: "action", + call: "actionFail1" + }, + { + type: "action", + call: "actionFail2" + } + ] + } + }; + const agent = { + actionFail1: () => State.FAILED, + actionFail2: () => State.FAILED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "selector").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "selector").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionFail1").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionFail2").state, State.FAILED); + }); + }); + + describe("move to the SUCCEEDED state if any child node moves to the SUCCEEDED state", () => { + it("(MDSL)", () => { + const definition = + "root { selector { action [actionFail] action [actionSucceed1] action [actionSucceed2] } }"; + const agent = { + actionSucceed1: () => State.SUCCEEDED, + actionSucceed2: () => State.SUCCEEDED, + actionFail: () => State.FAILED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "selector").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "selector").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionSucceed1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionSucceed2").state, State.READY); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "selector", + children: [ + { + type: "action", + call: "actionFail" + }, + { + type: "action", + call: "actionSucceed1" + }, + { + type: "action", + call: "actionSucceed2" + } + ] + } + }; + const agent = { + actionSucceed1: () => State.SUCCEEDED, + actionSucceed2: () => State.SUCCEEDED, + actionFail: () => State.FAILED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "selector").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "selector").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionSucceed1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionSucceed2").state, State.READY); + }); + }); + + describe("move to the RUNNING state if any child node is in the RUNNING state", () => { + it("(MDSL)", () => { + const definition = + "root { selector { action [actionFail] action [actionRunning] action [actionSucceed] } }"; + const agent = { + actionSucceed: () => State.SUCCEEDED, + actionRunning: () => State.RUNNING, + actionFail: () => State.FAILED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "selector").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionRunning").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "selector").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionRunning").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "selector", + children: [ + { + type: "action", + call: "actionFail" + }, + { + type: "action", + call: "actionRunning" + }, + { + type: "action", + call: "actionSucceed" + } + ] + } + }; + const agent = { + actionSucceed: () => State.SUCCEEDED, + actionRunning: () => State.RUNNING, + actionFail: () => State.FAILED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "selector").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionRunning").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "selector").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionRunning").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + }); + }); + }); +}); diff --git a/test/nodes/composite/Sequence.spec.ts b/test/nodes/composite/Sequence.spec.ts new file mode 100644 index 0000000..dd6a1b0 --- /dev/null +++ b/test/nodes/composite/Sequence.spec.ts @@ -0,0 +1,226 @@ +import { assert } from "chai"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +import { findNode } from "../../TestUtilities"; + +describe("A Sequence node", () => { + describe("on tree initialisation", () => { + describe("will error if the node does not have at least one child", () => { + it("(MDSL)", () => { + const definition = "root { sequence {} }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a sequence node must have at least a single child" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + children: [] + } + }; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected non-empty 'children' array to be defined for sequence node at depth '1'" + ); + }); + }); + }); + + describe("when updated as part of a tree step will", () => { + describe("move to the FAILED state if any child node moves to the FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { sequence { action [actionFail] action [actionSucceed] } }"; + const agent = { + actionFail: () => State.FAILED, + actionSucceed: () => State.SUCCEEDED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + children: [ + { + type: "action", + call: "actionFail" + }, + { + type: "action", + call: "actionSucceed" + } + ] + } + }; + const agent = { + actionFail: () => State.FAILED, + actionSucceed: () => State.SUCCEEDED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + assert.strictEqual(findNode(tree, "sequence").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.FAILED); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + }); + }); + + describe("move to the SUCCEEDED state if all child nodes move to the SUCCEEDED state", () => { + it("(MDSL)", () => { + const definition = "root { sequence { action [actionSucceed1] action [actionSucceed2] } }"; + const agent = { + actionSucceed1: () => State.SUCCEEDED, + actionSucceed2: () => State.SUCCEEDED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "sequence").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionSucceed1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionSucceed2").state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + children: [ + { + type: "action", + call: "actionSucceed1" + }, + { + type: "action", + call: "actionSucceed2" + } + ] + } + }; + const agent = { + actionSucceed1: () => State.SUCCEEDED, + actionSucceed2: () => State.SUCCEEDED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed1").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed2").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "sequence").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionSucceed1").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionSucceed2").state, State.SUCCEEDED); + }); + }); + + describe("move to the RUNNING state if any child node is in the RUNNING state", () => { + it("(MDSL)", () => { + const definition = + "root { sequence { action [actionSucceed] action [actionRunning] action [actionFail] } }"; + const agent = { + actionSucceed: () => State.SUCCEEDED, + actionRunning: () => State.RUNNING, + actionFail: () => State.FAILED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionRunning").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionRunning").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "sequence", + children: [ + { + type: "action", + call: "actionSucceed" + }, + { + type: "action", + call: "actionRunning" + }, + { + type: "action", + call: "actionFail" + } + ] + } + }; + const agent = { + actionSucceed: () => State.SUCCEEDED, + actionRunning: () => State.RUNNING, + actionFail: () => State.FAILED + }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + assert.strictEqual(findNode(tree, "sequence").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionRunning").state, State.READY); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + assert.strictEqual(findNode(tree, "sequence").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionSucceed").state, State.SUCCEEDED); + assert.strictEqual(findNode(tree, "action", "actionRunning").state, State.RUNNING); + assert.strictEqual(findNode(tree, "action", "actionFail").state, State.READY); + }); + }); + }); +}); diff --git a/test/nodes/decorator/Fail.spec.ts b/test/nodes/decorator/Fail.spec.ts new file mode 100644 index 0000000..c441bfa --- /dev/null +++ b/test/nodes/decorator/Fail.spec.ts @@ -0,0 +1,157 @@ +import { assert } from "chai"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; +import { Agent } from "../../../src/Agent"; + +import { findNode } from "../../TestUtilities"; + +describe("A Fail node", () => { + describe("on tree initialisation", () => { + describe("will error if the node does not have a single child", () => { + it("(MDSL)", () => { + const definition = "root { fail {} }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a fail node must have a single child node defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "fail" + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected property 'child' to be defined for fail node at depth '1'" + ); + }); + }); + }); + + describe("when updated as part of a tree step will", () => { + describe("move to the FAILED state if the child node moves to the", () => { + describe("FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { fail { condition [someCondition] } }"; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "fail", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + + describe("SUCCESS state", () => { + it("(MDSL)", () => { + const definition = "root { fail { condition [someCondition] } }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "fail", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + }); + + describe("move to the RUNNING state if the child node moves to the RUNNING state", () => { + it("(MDSL)", () => { + const definition = "root { fail { action [someAction] } }"; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "fail", + child: { + type: "action", + call: "someAction" + } + } + }; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "fail"); + assert.strictEqual(node.state, State.RUNNING); + }); + }); + }); +}); diff --git a/test/nodes/decorator/Flip.spec.ts b/test/nodes/decorator/Flip.spec.ts new file mode 100644 index 0000000..5081102 --- /dev/null +++ b/test/nodes/decorator/Flip.spec.ts @@ -0,0 +1,154 @@ +import { assert } from "chai"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +import { findNode } from "../../TestUtilities"; + +describe("A Flip node", () => { + describe("on tree initialisation", () => { + describe("will error if the node does not have a single child", () => { + it("(MDSL)", () => { + const definition = "root { flip {} }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a flip node must have a single child node defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "flip" + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected property 'child' to be defined for flip node at depth '1'" + ); + }); + }); + }); + + describe("when updated as part of a tree step will", () => { + describe("move to the SUCCESS state if the child node moves to the FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { flip { condition [someCondition] } }"; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "flip", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + + describe("move to the FAILED state if the child node moves to the SUCCESS state", () => { + it("(MDSL)", () => { + const definition = "root { flip { condition [someCondition] } }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "flip", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + + describe("move to the RUNNING state if the child node does not move to the SUCCESS or FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { flip { action [someAction] } }"; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "flip", + child: { + type: "action", + call: "someAction" + } + } + }; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "flip"); + assert.strictEqual(node.state, State.RUNNING); + }); + }); + }); +}); diff --git a/test/nodes/decorator/Repeat.spec.ts b/test/nodes/decorator/Repeat.spec.ts new file mode 100644 index 0000000..057213b --- /dev/null +++ b/test/nodes/decorator/Repeat.spec.ts @@ -0,0 +1,494 @@ +import { assert } from "chai"; +import sinon, { SinonSandbox } from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; +import { Agent } from "../../../src/Agent"; + +import { findNode } from "../../TestUtilities"; + +describe("A Repeat node", () => { + describe("on tree initialisation", () => { + describe("will error if", () => { + describe("the node does not have a single child", () => { + it("(MDSL)", () => { + const definition = "root { repeat {} }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a repeat node must have a single child node defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "repeat" + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected property 'child' to be defined for repeat node at depth '1'" + ); + }); + }); + + describe("the defined node arguments are not integers", () => { + it("(MDSL)", () => { + const definition = "root { repeat ['not', 'integers'] { condition [someCondition] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: repeat node iteration counts must be integer values" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "repeat", + iterations: ["not", "integers"], + child: { + type: "condition", + call: "someCondition" + } + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected array containing two integer values for 'iterations' property if defined for repeat node at depth '1'" + ); + }); + }); + + describe("a negative iteration count node argument was defined", () => { + it("(MDSL)", () => { + const definition = "root { repeat [-1] { condition [someCondition] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a repeat node must have a positive number of iterations if defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "repeat", + iterations: -1, + child: { + type: "condition", + call: "someCondition" + } + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected positive iterations count for 'iterations' property if defined for repeat node at depth '1'" + ); + }); + }); + + describe("more than two node arguments are defined", () => { + it("(MDSL)", () => { + const definition = "root { repeat [0, 10, 20] { condition [someCondition] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: invalid number of repeat node iteration count arguments defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "repeat", + iterations: [0, 10, 20], + child: { + type: "condition", + call: "someCondition" + } + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected array containing two integer values for 'iterations' property if defined for repeat node at depth '1'" + ); + }); + }); + + describe("a minimum iteration count node argument is defined that is greater than the maximum iteration count node argument", () => { + it("(MDSL)", () => { + const definition = "root { repeat [10, 5] { condition [someCondition] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a repeat node must not have a minimum iteration count that exceeds the maximum iteration count" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "repeat", + iterations: [10, 5], + child: { + type: "condition", + call: "someCondition" + } + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected minimum iterations count that does not exceed the maximum iterations count for 'iterations' property if defined for repeat node at depth '1'" + ); + }); + }); + }); + }); + + describe("when updated as part of a tree step", () => { + var sandbox: SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.replace(globalThis.Math, "random", () => 0.5); + }); + + afterEach(() => sandbox.restore()); + + describe("will move to the FAILED state if the child node moves to the FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { repeat { condition [someCondition] } }"; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "repeat", "REPEAT"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "repeat", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "repeat", "REPEAT"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + + describe("will move to the RUNNING state if the child node moves to the SUCCEEDED state", () => { + it("(MDSL)", () => { + const definition = "root { repeat { condition [someCondition] } }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "repeat", "REPEAT"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT"); + assert.strictEqual(node.state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "repeat", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "repeat", "REPEAT"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT"); + assert.strictEqual(node.state, State.RUNNING); + }); + }); + + describe("and an iteration count node argument is defined will attempt to re-run the child node until the iteration count is reached", () => { + it("(MDSL)", () => { + const definition = "root { repeat [3] { condition [someCondition] } }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "repeat", "REPEAT 3x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 3x"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "repeat", + iterations: 3, + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "repeat", "REPEAT 3x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 3x"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + + describe("and minumum and maximum iteration count node arguments are defined", () => { + describe("and if the 'random' behaviour tree option is not defined will pick an iteration count from between the minimum and maximum bounds using Math.random", () => { + it("(MDSL)", () => { + // We have spied on Math.random to always return 0.5 for the sake of this test, so our iteration count should always be 4. + const definition = "root { repeat [2, 6] { condition [someCondition] } }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + // We have spied on Math.random to always return 0.5 for the sake of this test, so our iteration count should always be 4. + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "repeat", + iterations: [2, 6], + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-6x"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + + describe("and if the 'random' behaviour tree option is defined will use it to pick an iteration count from between the minimum and maximum bounds", () => { + it("(MDSL)", () => { + const definition = "root { repeat [2, 10] { condition [someCondition] } }"; + const agent = { someCondition: () => true }; + const options = { + // Usually this would return a new pseudo-random number each time, but for the sake of this test we + // just want to make sure that the number we return actually has an impact on the iteration count picked. + // A value of 0.2 should always result in an iteration count of 3. + random: () => 0.2 + }; + const tree = new BehaviourTree(definition, agent, options); + + let node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "repeat", + iterations: [2, 10], + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => true }; + const options = { + // Usually this would return a new pseudo-random number each time, but for the sake of this test we + // just want to make sure that the number we return actually has an impact on the iteration count picked. + // A value of 0.2 should always result in an iteration count of 3. + random: () => 0.2 + }; + const tree = new BehaviourTree(definition, agent, options); + + let node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "repeat", "REPEAT 2x-10x"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + }); + }); +}); diff --git a/test/nodes/decorator/Retry.spec.ts b/test/nodes/decorator/Retry.spec.ts new file mode 100644 index 0000000..59df623 --- /dev/null +++ b/test/nodes/decorator/Retry.spec.ts @@ -0,0 +1,494 @@ +import { assert } from "chai"; +import sinon, { SinonSandbox } from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; +import { Agent } from "../../../src/Agent"; + +import { findNode } from "../../TestUtilities"; + +describe("A Retry node", () => { + describe("on tree initialisation", () => { + describe("will error if", () => { + describe("the node does not have a single child", () => { + it("(MDSL)", () => { + const definition = "root { retry {} }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a retry node must have a single child node defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "retry" + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected property 'child' to be defined for retry node at depth '1'" + ); + }); + }); + + describe("the defined node arguments are not integers", () => { + it("(MDSL)", () => { + const definition = "root { retry ['not', 'integers'] { condition [someCondition] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: retry node attempt counts must be integer values" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "retry", + attempts: ["not", "integers"], + child: { + type: "condition", + call: "someCondition" + } + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected array containing two integer values for 'attempts' property if defined for retry node at depth '1'" + ); + }); + }); + + describe("a negative attempts count node argument was defined", () => { + it("(MDSL)", () => { + const definition = "root { retry [-1] { condition [someCondition] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a retry node must have a positive number of attempts if defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "retry", + attempts: -1, + child: { + type: "condition", + call: "someCondition" + } + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected positive attempts count for 'attempts' property if defined for retry node at depth '1'" + ); + }); + }); + + describe("more than two node arguments are defined", () => { + it("(MDSL)", () => { + const definition = "root { retry [0, 10, 20] { condition [someCondition] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: invalid number of retry node attempt count arguments defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "retry", + attempts: [0, 10, 20], + child: { + type: "condition", + call: "someCondition" + } + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected array containing two integer values for 'attempts' property if defined for retry node at depth '1'" + ); + }); + }); + + describe("a minimum iteration count node argument is defined that is greater than the maximum iteration count node argument", () => { + it("(MDSL)", () => { + const definition = "root { retry [10, 5] { condition [someCondition] } }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a retry node must not have a minimum attempt count that exceeds the maximum attempt count" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "retry", + attempts: [10, 5], + child: { + type: "condition", + call: "someCondition" + } + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected minimum attempts count that does not exceed the maximum attempts count for 'attempts' property if defined for retry node at depth '1'" + ); + }); + }); + }); + }); + + describe("when updated as part of a tree step", () => { + var sandbox: SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.replace(globalThis.Math, "random", () => 0.5); + }); + + afterEach(() => sandbox.restore()); + + describe("will move to the SUCCEEDED state if the child node moves to the SUCCEEDED state", () => { + it("(MDSL)", () => { + const definition = "root { retry { condition [someCondition] } }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "retry", "RETRY"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "retry", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "retry", "RETRY"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + + describe("will move to the RUNNING state if the child node moves to the FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { retry { condition [someCondition] } }"; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "retry", "RETRY"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY"); + assert.strictEqual(node.state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "retry", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "retry", "RETRY"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY"); + assert.strictEqual(node.state, State.RUNNING); + }); + }); + + describe("and an attempt count node argument is defined will attempt to re-run the child node until the attempt count is reached", () => { + it("(MDSL)", () => { + const definition = "root { retry [3] { condition [someCondition] } }"; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "retry", "RETRY 3x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 3x"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "retry", + attempts: 3, + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "retry", "RETRY 3x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 3x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 3x"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + + describe("and minumum and maximum attempt count node arguments are defined", () => { + describe("and if the 'random' behaviour tree option is not defined will pick an attempt count from between the minimum and maximum bounds using Math.random", () => { + it("(MDSL)", () => { + // We have spied on Math.random to always return 0.5 for the sake of this test, so our attempt count should always be 4. + const definition = "root { retry [2, 6] { condition [someCondition] } }"; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + // We have spied on Math.random to always return 0.5 for the sake of this test, so our attempt count should always be 4. + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "retry", + attempts: [2, 6], + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-6x"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + + describe("and if the 'random' behaviour tree option is defined will use it to pick an attempt count from between the minimum and maximum bounds", () => { + it("(MDSL)", () => { + const definition = "root { retry [2, 10] { condition [someCondition] } }"; + const agent = { someCondition: () => false }; + const options = { + // Usually this would return a new pseudo-random number each time, but for the sake of this test we + // just want to make sure that the number we return actually has an impact on the attempt count picked. + // A value of 0.2 should always result in an attempt count of 3. + random: () => 0.2 + }; + const tree = new BehaviourTree(definition, agent, options); + + let node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "retry", + attempts: [2, 10], + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => false }; + const options = { + // Usually this would return a new pseudo-random number each time, but for the sake of this test we + // just want to make sure that the number we return actually has an impact on the attempt count picked. + // A value of 0.2 should always result in an attempt count of 3. + random: () => 0.2 + }; + const tree = new BehaviourTree(definition, agent, options); + + let node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.exists(node); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "retry", "RETRY 2x-10x"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + }); + }); +}); diff --git a/test/nodes/decorator/Root.spec.ts b/test/nodes/decorator/Root.spec.ts new file mode 100644 index 0000000..a224197 --- /dev/null +++ b/test/nodes/decorator/Root.spec.ts @@ -0,0 +1,131 @@ +import { assert } from "chai"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; +import { Agent } from "../../../src/Agent"; + +import { findNode } from "../../TestUtilities"; + +describe("A Root node", () => { + describe("on tree initialisation", () => { + describe("will error if the node does not have a single child", () => { + it("(MDSL)", () => { + const definition = "root { }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a root node must have a single child node defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root" + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected property 'child' to be defined for root node" + ); + }); + }); + }); + + describe("when updated as part of a tree step will", () => { + describe("move to the SUCCESS state if the child node moves to the SUCCESS state", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition] }"; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition" + } + }; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.FAILED); + }); + }); + + describe("move to the FAILED state if the child node moves to the FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition] }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition" + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.SUCCEEDED); + }); + }); + + describe("move to the RUNNING state if the child node does not move to the SUCCESS or FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { action [someAction] }"; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "someAction" + } + }; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "root").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "root").state, State.RUNNING); + }); + }); + }); +}); diff --git a/test/nodes/decorator/Succeed.spec.ts b/test/nodes/decorator/Succeed.spec.ts new file mode 100644 index 0000000..ed57452 --- /dev/null +++ b/test/nodes/decorator/Succeed.spec.ts @@ -0,0 +1,157 @@ +import { assert } from "chai"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; +import { Agent } from "../../../src/Agent"; + +import { findNode } from "../../TestUtilities"; + +describe("A Succeed node", () => { + describe("on tree initialisation", () => { + describe("will error if the node does not have a single child", () => { + it("(MDSL)", () => { + const definition = "root { succeed {} }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a succeed node must have a single child node defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "succeed" + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected property 'child' to be defined for succeed node at depth '1'" + ); + }); + }); + }); + + describe("when updated as part of a tree step will", () => { + describe("move to the SUCCEEDED state if the child node moves to the", () => { + describe("FAILED state", () => { + it("(MDSL)", () => { + const definition = "root { succeed { condition [someCondition] } }"; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "succeed", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + + describe("SUCCESS state", () => { + it("(MDSL)", () => { + const definition = "root { succeed { condition [someCondition] } }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "succeed", + child: { + type: "condition", + call: "someCondition" + } + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + }); + + describe("move to the RUNNING state if the child node moves to the RUNNING state", () => { + it("(MDSL)", () => { + const definition = "root { succeed { action [someAction] } }"; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "succeed", + child: { + type: "action", + call: "someAction" + } + } + }; + const agent = { someAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "succeed"); + assert.strictEqual(node.state, State.RUNNING); + }); + }); + }); +}); diff --git a/test/nodes/decorator/fail.test.js b/test/nodes/decorator/fail.test.js deleted file mode 100644 index bad5278..0000000 --- a/test/nodes/decorator/fail.test.js +++ /dev/null @@ -1,66 +0,0 @@ -const mistreevous = require("../../../dist/index"); -const chai = require("chai"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => - tree.getFlattenedNodeDetails().find((node) => node.type === type && node.caption === caption); - -describe("A Fail node", () => { - describe("on tree initialisation", () => { - it("will error if the node does not have a single child", () => { - const definition = "root { fail {} }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: a fail node must have a single child" - ); - }); - }); - - describe("when updated as part of a tree step will", () => { - describe("move to the FAILED state if the child node moves to the", () => { - it("FAILED state", () => { - const definition = "root { fail { condition [someCondition] } }"; - const agent = { someCondition: () => false }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "fail", "FAIL"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "fail", "FAIL"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - - it("SUCCESS state", () => { - const definition = "root { fail { condition [someCondition] } }"; - const agent = { someCondition: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "fail", "FAIL"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "fail", "FAIL"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - }); - - it("move to the RUNNING state if the child node does not move to the SUCCESS or FAILED state", () => { - const definition = "root { fail { action [someAction] } }"; - const agent = { someAction: () => {} }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "fail", "FAIL"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "fail", "FAIL"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - }); - }); -}); diff --git a/test/nodes/decorator/flip.test.js b/test/nodes/decorator/flip.test.js deleted file mode 100644 index 33f8d66..0000000 --- a/test/nodes/decorator/flip.test.js +++ /dev/null @@ -1,64 +0,0 @@ -const mistreevous = require("../../../dist/index"); -const chai = require("chai"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => - tree.getFlattenedNodeDetails().find((node) => node.type === type && node.caption === caption); - -describe("A Flip node", () => { - describe("on tree initialisation", () => { - it("will error if the node does not have a single child", () => { - const definition = "root { flip {} }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: a flip node must have a single child" - ); - }); - }); - - describe("when updated as part of a tree step will", () => { - it("move to the SUCCESS state if the child node moves to the FAILED state", () => { - const definition = "root { flip { condition [someCondition] } }"; - const agent = { someCondition: () => false }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "flip", "FLIP"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "flip", "FLIP"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - it("move to the FAILED state if the child node moves to the SUCCESS state", () => { - const definition = "root { flip { condition [someCondition] } }"; - const agent = { someCondition: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "flip", "FLIP"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "flip", "FLIP"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - - it("move to the RUNNING state if the child node does not move to the SUCCESS or FAILED state", () => { - const definition = "root { flip { action [someAction] } }"; - const agent = { someAction: () => {} }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "flip", "FLIP"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "flip", "FLIP"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - }); - }); -}); diff --git a/test/nodes/decorator/repeat.test.js b/test/nodes/decorator/repeat.test.js deleted file mode 100644 index a61cd8f..0000000 --- a/test/nodes/decorator/repeat.test.js +++ /dev/null @@ -1,166 +0,0 @@ -const mistreevous = require("../../../dist/index"); -const chai = require("chai"); -const sinon = require("sinon"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => - tree.getFlattenedNodeDetails().find((node) => node.type === type && node.caption === caption); - -describe("A Repeat node", () => { - describe("on tree initialisation", () => { - it("will error if the node does not have a single child", () => { - const definition = "root { repeat {} }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: a repeat node must have a single child" - ); - }); - }); - - describe("when updated as part of a tree step", () => { - var sandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - sandbox.replace(globalThis.Math, "random", () => 0.5); - }); - - afterEach(() => sandbox.restore()); - - it("will move to the FAILED state if the child node moves to the FAILED state", () => { - const definition = "root { repeat { condition [someCondition] } }"; - const agent = { someCondition: () => false }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "repeat", "REPEAT"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - - it("will move to the RUNNING state if the child node moves to the SUCCEEDED state", () => { - const definition = "root { repeat { condition [someCondition] } }"; - const agent = { someCondition: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "repeat", "REPEAT"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - }); - - it("and an iteration count node argument is defined will attempt to re-run the child node until the iteration count is reached", () => { - const definition = "root { repeat [3] { condition [someCondition] } }"; - const agent = { someCondition: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "repeat", "REPEAT 3x"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 3x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 3x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 3x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 3x"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - describe("and minumum and maximum iteration count node arguments are defined", () => { - it("and if the 'random' behaviour tree option is not defined will pick an iteration count from between the minimum and maximum bounds using Math.random", () => { - // We have spied on Math.random to always return 0.5 for the sake of this test, so our iteration count should always be 4. - const definition = "root { repeat [2, 6] { condition [someCondition] } }"; - const agent = { someCondition: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "repeat", "REPEAT 2x-6x"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - it("and if the 'random' behaviour tree option is defined will use it to pick an iteration count from between the minimum and maximum bounds", () => { - const definition = "root { repeat [2, 10] { condition [someCondition] } }"; - const agent = { someCondition: () => true }; - const options = { - // Usually this would return a new pseudo-random number each time, but for the sake of this test we - // just want to make sure that the number we return actually has an impact on the iteration count picked. - // A value of 0.2 should always result in an iteration count of 3. - random: () => 0.2 - }; - const tree = new mistreevous.BehaviourTree(definition, agent, options); - - let node = findNode(tree, "repeat", "REPEAT 2x-10x"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 2x-10x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 2x-10x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 2x-10x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "repeat", "REPEAT 2x-10x"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - }); - }); -}); diff --git a/test/nodes/decorator/retry.test.js b/test/nodes/decorator/retry.test.js deleted file mode 100644 index 5076c76..0000000 --- a/test/nodes/decorator/retry.test.js +++ /dev/null @@ -1,166 +0,0 @@ -const mistreevous = require("../../../dist/index"); -const chai = require("chai"); -const sinon = require("sinon"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => - tree.getFlattenedNodeDetails().find((node) => node.type === type && node.caption === caption); - -describe("A Retry node", () => { - describe("on tree initialisation", () => { - it("will error if the node does not have a single child", () => { - const definition = "root { retry {} }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: a retry node must have a single child" - ); - }); - }); - - describe("when updated as part of a tree step", () => { - var sandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - sandbox.replace(globalThis.Math, "random", () => 0.5); - }); - - afterEach(() => sandbox.restore()); - - it("will move to the SUCCEEDED state if the child node moves to the SUCCEEDED state", () => { - const definition = "root { retry { condition [someCondition] } }"; - const agent = { someCondition: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "retry", "RETRY"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "retry", "RETRY"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - it("will move to the RUNNING state if the child node moves to the FAILED state", () => { - const definition = "root { retry { condition [someCondition] } }"; - const agent = { someCondition: () => false }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "retry", "RETRY"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "retry", "RETRY"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - }); - - it("and an attempt count node argument is defined will attempt to re-run the child node until the attempt count is reached", () => { - const definition = "root { retry [3] { condition [someCondition] } }"; - const agent = { someCondition: () => false }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "retry", "RETRY 3x"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 3x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 3x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 3x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 3x"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - - describe("and minumum and maximum attempt count node arguments are defined", () => { - it("and if the 'random' behaviour tree option is not defined will pick an attempt count from between the minimum and maximum bounds using Math.random", () => { - // We have spied on Math.random to always return 0.5 for the sake of this test, so our attempt count should always be 4. - const definition = "root { retry [2, 6] { condition [someCondition] } }"; - const agent = { someCondition: () => false }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "retry", "RETRY 2x-6x"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 2x-6x"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - - it("and if the 'random' behaviour tree option is defined will use it to pick an attempt count from between the minimum and maximum bounds", () => { - const definition = "root { retry [2, 10] { condition [someCondition] } }"; - const agent = { someCondition: () => false }; - const options = { - // Usually this would return a new pseudo-random number each time, but for the sake of this test we - // just want to make sure that the number we return actually has an impact on the attempt count picked. - // A value of 0.2 should always result in an attempt count of 3. - random: () => 0.2 - }; - const tree = new mistreevous.BehaviourTree(definition, agent, options); - - let node = findNode(tree, "retry", "RETRY 2x-10x"); - assert.exists(node); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 2x-10x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 2x-10x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 2x-10x"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "retry", "RETRY 2x-10x"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - }); - }); -}); diff --git a/test/nodes/decorator/succeed.test.js b/test/nodes/decorator/succeed.test.js deleted file mode 100644 index d5723dc..0000000 --- a/test/nodes/decorator/succeed.test.js +++ /dev/null @@ -1,66 +0,0 @@ -const mistreevous = require("../../../dist/index"); -const chai = require("chai"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => - tree.getFlattenedNodeDetails().find((node) => node.type === type && node.caption === caption); - -describe("A Succeed node", () => { - describe("on tree initialisation", () => { - it("will error if the node does not have a single child", () => { - const definition = "root { succeed {} }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: a succeed node must have a single child" - ); - }); - }); - - describe("when updated as part of a tree step will", () => { - describe("move to the SUCCEEDED state if the child node moves to the", () => { - it("FAILED state", () => { - const definition = "root { succeed { condition [someCondition] } }"; - const agent = { someCondition: () => false }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "succeed", "SUCCEED"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "succeed", "SUCCEED"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - it("SUCCESS state", () => { - const definition = "root { succeed { condition [someCondition] } }"; - const agent = { someCondition: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "succeed", "SUCCEED"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "succeed", "SUCCEED"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - }); - - it("move to the RUNNING state if the child node does not move to the SUCCESS or FAILED state", () => { - const definition = "root { succeed { action [someAction] } }"; - const agent = { someAction: () => {} }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "succeed", "SUCCEED"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "succeed", "SUCCEED"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - }); - }); -}); diff --git a/test/nodes/leaf/Action.spec.ts b/test/nodes/leaf/Action.spec.ts new file mode 100644 index 0000000..5246c4f --- /dev/null +++ b/test/nodes/leaf/Action.spec.ts @@ -0,0 +1,738 @@ +import { assert } from "chai"; +import sinon from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +import { findNode } from "../../TestUtilities"; + +describe("An Action node", () => { + beforeEach(() => BehaviourTree.unregisterAll()); + + describe("on tree initialisation", () => { + describe("will error if no action function name is defined", () => { + it("(MDSL)", () => { + const definition = "root { action [] }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected action name identifier argument" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "action" + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected non-empty string for 'call' property of action node at depth '1'" + ); + }); + }); + }); + + describe("when updated as part of a tree step", () => { + describe("will call the function defined by the first node argument", () => { + describe("when the referenced function is", () => { + describe("a registered function", () => { + it("(MDSL)", () => { + const registeredActionFunction = sinon.stub().returns(State.SUCCEEDED); + BehaviourTree.register("doAction", registeredActionFunction); + + const definition = `root { action [doAction, "some-argument"] }`; + const agent = { mock: "agent" }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + const node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.SUCCEEDED); + assert.isTrue(registeredActionFunction.calledWith(agent, "some-argument")); + }); + + it("(JSON)", () => { + const registeredActionFunction = sinon.stub().returns(State.SUCCEEDED); + BehaviourTree.register("doAction", registeredActionFunction); + + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction", + args: ["some-argument"] + } + }; + const agent = { mock: "agent" }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + const node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.SUCCEEDED); + assert.isTrue(registeredActionFunction.calledWith(agent, "some-argument")); + }); + }); + + describe("an agent function", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction] }"; + const agent = { doAction: () => State.SUCCEEDED }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + const node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + const agent = { doAction: () => State.SUCCEEDED }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + const node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + }); + + describe("and will error if", () => { + describe("there is no agent function or registered function that matches the action name", () => { + it("(MDSL)", () => { + const definition = "root { action [DoTheThing] }"; + let tree: BehaviourTree; + assert.doesNotThrow(() => (tree = new BehaviourTree(definition, {})), Error); + assert.throws( + () => tree.step(), + Error, + "error stepping tree: cannot update action node as the action 'DoTheThing' function is not defined on the agent and has not been registered" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "DoTheThing" + } + }; + let tree: BehaviourTree; + assert.doesNotThrow(() => (tree = new BehaviourTree(definition, {})), Error); + assert.throws( + () => tree.step(), + Error, + "error stepping tree: cannot update action node as the action 'DoTheThing' function is not defined on the agent and has not been registered" + ); + }); + }); + + describe("the action function", () => { + describe("throws an error object", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction] }"; + const agent = { + doAction: () => { + throw new Error("some-error"); + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: action function 'doAction' threw: Error: some-error" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + const agent = { + doAction: () => { + throw new Error("some-error"); + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: action function 'doAction' threw: Error: some-error" + ); + }); + }); + + describe("throws something that isn't an error object", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction] }"; + const agent = { + doAction: () => { + throw "some-error"; + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: action function 'doAction' threw: some-error" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + const agent = { + doAction: () => { + throw "some-error"; + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: action function 'doAction' threw: some-error" + ); + }); + }); + + describe("returns an unexpected value", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction] }"; + const agent = { + doAction: () => "invalid-result" + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: expected action function 'doAction' to return an optional State.SUCCEEDED or State.FAILED value but returned 'invalid-result'" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + const agent = { + doAction: () => "invalid-result" + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: expected action function 'doAction' to return an optional State.SUCCEEDED or State.FAILED value but returned 'invalid-result'" + ); + }); + }); + + describe("returns a rejected promise", () => { + it("(MDSL)", (done) => { + const definition = "root { action [doAction] }"; + const result: Promise = new Promise(() => { + throw new Error("some-error"); + }); + const agent = { doAction: () => result }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.RUNNING); + + setTimeout(() => { + assert.throws( + () => tree.step(), + Error, + "error stepping tree: action function 'doAction' promise rejected with 'Error: some-error'" + ); + + done(); + }, 0); + }); + + it("(JSON)", (done) => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + const result: Promise = new Promise(() => { + throw new Error("some-error"); + }); + const agent = { doAction: () => result }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.RUNNING); + + setTimeout(() => { + assert.throws( + () => tree.step(), + Error, + "error stepping tree: action function 'doAction' promise rejected with 'Error: some-error'" + ); + + done(); + }, 0); + }); + }); + }); + }); + + describe("and move to", () => { + describe("the SUCCESS state if the function returns a value of State.SUCCEEDED", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction] }"; + const agent = { doAction: () => State.SUCCEEDED }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + const agent = { doAction: () => State.SUCCEEDED }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + + describe("the FAILED state if the function returns a value of State.FAILED", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction] }"; + const agent = { doAction: () => State.FAILED }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + const agent = { doAction: () => State.FAILED }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + + describe("the RUNNING state if", () => { + describe("the function returns undefined", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction] }"; + const agent = { doAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + const agent = { doAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.RUNNING); + }); + }); + + describe("the function returns a value of State.RUNNING", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction] }"; + const agent = { doAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.RUNNING); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + const agent = { doAction: () => State.RUNNING }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "action", "doAction"); + assert.strictEqual(node.state, State.RUNNING); + }); + }); + + describe("the function returns a promise to return a value of State.SUCCEEDED or State.FAILED", () => { + it("(MDSL)", (done) => { + const definition = "root { action [doAction] }"; + + const result: Promise = new Promise((resolve) => resolve(State.SUCCEEDED)); + const agent = { doAction: () => result }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.RUNNING); + + setTimeout(() => { + tree.step(); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.SUCCEEDED); + + done(); + }, 0); + }); + + it("(JSON)", (done) => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction" + } + }; + + const result: Promise = new Promise((resolve) => resolve(State.SUCCEEDED)); + const agent = { doAction: () => result }; + const tree = new BehaviourTree(definition, agent); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.READY); + + tree.step(); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.RUNNING); + + setTimeout(() => { + tree.step(); + + assert.strictEqual(findNode(tree, "action", "doAction").state, State.SUCCEEDED); + + done(); + }, 0); + }); + }); + }); + }); + + describe("and pass any node arguments that follow the action name identifier argument where", () => { + describe("the argument is a", () => { + describe("string", () => { + it("(MDSL)", () => { + const definition = 'root { action [doAction, "hello world!"] }'; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, "hello world!") + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction", + args: ["hello world!"] + } + }; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, "hello world!") + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("string with escaped quotes", () => { + it("(MDSL)", () => { + const definition = 'root { action [doAction, "hello \\" world!"] }'; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, 'hello " world!') + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction", + args: ['hello " world!'] + } + }; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, 'hello " world!') + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("number", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction, 23.4567] }"; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, 23.4567) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction", + args: [23.4567] + } + }; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, 23.4567) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("boolean 'true' literal", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction, true] }"; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, true) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction", + args: [true] + } + }; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, true) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("boolean 'false' literal", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction, false] }"; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, false) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction", + args: [false] + } + }; + const agent = { + doAction: (arg: any) => assert.strictEqual(arg, false) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("null", () => { + it("(MDSL)", () => { + const definition = "root { action [doAction, null] }"; + const agent = { + doAction: (arg: any) => assert.isNull(arg) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction", + args: [null] + } + }; + const agent = { + doAction: (arg: any) => assert.isNull(arg) + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + }); + + describe("there are multiple arguments", () => { + it("(MDSL)", () => { + const definition = 'root { action [doAction, 1.23, "hello world!", false, null] }'; + const agent = { + doAction: (arg0: any, arg1: any, arg2: any, arg3: any) => { + assert.strictEqual(arg0, 1.23); + assert.strictEqual(arg1, "hello world!"); + assert.strictEqual(arg2, false); + assert.strictEqual(arg3, null); + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "action", + call: "doAction", + args: [1.23, "hello world!", false, null] + } + }; + const agent = { + doAction: (arg0: any, arg1: any, arg2: any, arg3: any) => { + assert.strictEqual(arg0, 1.23); + assert.strictEqual(arg1, "hello world!"); + assert.strictEqual(arg2, false); + assert.strictEqual(arg3, null); + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + }); + }); + }); +}); diff --git a/test/nodes/leaf/Condition.spec.ts b/test/nodes/leaf/Condition.spec.ts new file mode 100644 index 0000000..d6d127b --- /dev/null +++ b/test/nodes/leaf/Condition.spec.ts @@ -0,0 +1,588 @@ +import { assert } from "chai"; +import sinon from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; + +import { findNode } from "../../TestUtilities"; + +describe("A Condition node", () => { + beforeEach(() => BehaviourTree.unregisterAll()); + + describe("on tree initialisation", () => { + describe("will error if no condition function name is defined", () => { + it("(MDSL)", () => { + const definition = "root { condition [] }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected condition name identifier argument" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "condition" + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected non-empty string for 'call' property of condition node at depth '1'" + ); + }); + }); + }); + + describe("when updated as part of a tree step", () => { + describe("will call the condition function", () => { + describe("when the referenced function is", () => { + describe("a registered function", () => { + it("(MDSL)", () => { + const registeredConditionFunction = sinon.stub().returns(true); + BehaviourTree.register("someCondition", registeredConditionFunction); + + const definition = `root { condition [someCondition, "some-argument"] }`; + const agent = { mock: "agent" }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + const node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.SUCCEEDED); + assert.isTrue(registeredConditionFunction.calledWith(agent, "some-argument")); + }); + + it("(JSON)", () => { + const registeredConditionFunction = sinon.stub().returns(true); + BehaviourTree.register("someCondition", registeredConditionFunction); + + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition", + args: ["some-argument"] + } + }; + const agent = { mock: "agent" }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + const node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.SUCCEEDED); + assert.isTrue(registeredConditionFunction.calledWith(agent, "some-argument")); + }); + }); + + describe("an agent function", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition] }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + const node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition" + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + + const node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + }); + + describe("and will error if", () => { + describe("there is no agent function or registered function that matches the condition name", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition] }"; + let tree: BehaviourTree; + assert.doesNotThrow(() => (tree = new BehaviourTree(definition, {})), Error); + assert.throws( + () => tree.step(), + Error, + "error stepping tree: cannot update condition node as the condition 'someCondition' function is not defined on the agent and has not been registered" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition" + } + }; + let tree: BehaviourTree; + assert.doesNotThrow(() => (tree = new BehaviourTree(definition, {})), Error); + assert.throws( + () => tree.step(), + Error, + "error stepping tree: cannot update condition node as the condition 'someCondition' function is not defined on the agent and has not been registered" + ); + }); + }); + + describe("the agent function", () => { + describe("throws an error object", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition] }"; + const agent = { + someCondition: () => { + throw new Error("some-error"); + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: condition function 'someCondition' threw: Error: some-error" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition" + } + }; + const agent = { + someCondition: () => { + throw new Error("some-error"); + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: condition function 'someCondition' threw: Error: some-error" + ); + }); + }); + + describe("throws something that isn't an error object", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition] }"; + const agent = { + someCondition: () => { + throw "some-error"; + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: condition function 'someCondition' threw: some-error" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition" + } + }; + const agent = { + someCondition: () => { + throw "some-error"; + } + }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: condition function 'someCondition' threw: some-error" + ); + }); + }); + + describe("does not return a boolean value", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition] }"; + const agent = { someCondition: () => null }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: expected condition function 'someCondition' to return a boolean but returned 'null'" + ); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition" + } + }; + const agent = { someCondition: () => null }; + const tree = new BehaviourTree(definition, agent); + + assert.throws( + () => tree.step(), + Error, + "error stepping tree: expected condition function 'someCondition' to return a boolean but returned 'null'" + ); + }); + }); + }); + }); + + describe("and move to", () => { + describe("the SUCCESS state if the function returns a truthy value", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition] }"; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition" + } + }; + const agent = { someCondition: () => true }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + + describe("the FAILED state if the function returns a falsy value", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition] }"; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition" + } + }; + const agent = { someCondition: () => false }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "condition", "someCondition"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + }); + + describe("and pass any node arguments that follow the condition name identifier argument where", () => { + describe("the argument is a", () => { + describe("string", () => { + it("(MDSL)", () => { + const definition = 'root { condition [someCondition, "hello world!"] }'; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, "hello world!"); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition", + args: ["hello world!"] + } + }; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, "hello world!"); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("string with escaped quotes", () => { + it("(MDSL)", () => { + const definition = 'root { condition [someCondition, "hello \\" world!"] }'; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, 'hello " world!'); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition", + args: ['hello " world!'] + } + }; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, 'hello " world!'); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("number", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition, 23.4567] }"; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, 23.4567); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition", + args: [23.4567] + } + }; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, 23.4567); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("boolean 'true' literal", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition, true] }"; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, true); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition", + args: [true] + } + }; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, true); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("boolean 'false' literal", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition, false] }"; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, false); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition", + args: [false] + } + }; + const agent = { + someCondition: (arg: any) => { + assert.strictEqual(arg, false); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + + describe("null", () => { + it("(MDSL)", () => { + const definition = "root { condition [someCondition, null] }"; + const agent = { + someCondition: (arg: any) => { + assert.isNull(arg); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition", + args: [null] + } + }; + const agent = { + someCondition: (arg: any) => { + assert.isNull(arg); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + }); + + describe("there are multiple arguments", () => { + it("(MDSL)", () => { + const definition = 'root { condition [someCondition, 1.23, "hello world!", false, null] }'; + const agent = { + someCondition: (arg0: any, arg1: any, arg2: any, arg3: any) => { + assert.strictEqual(arg0, 1.23); + assert.strictEqual(arg1, "hello world!"); + assert.strictEqual(arg2, false); + assert.strictEqual(arg3, null); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "condition", + call: "someCondition", + args: [1.23, "hello world!", false, null] + } + }; + const agent = { + someCondition: (arg0: any, arg1: any, arg2: any, arg3: any) => { + assert.strictEqual(arg0, 1.23); + assert.strictEqual(arg1, "hello world!"); + assert.strictEqual(arg2, false); + assert.strictEqual(arg3, null); + return true; + } + }; + const tree = new BehaviourTree(definition, agent); + + tree.step(); + }); + }); + }); + }); + }); +}); diff --git a/test/nodes/leaf/Wait.spec.ts b/test/nodes/leaf/Wait.spec.ts new file mode 100644 index 0000000..6c923aa --- /dev/null +++ b/test/nodes/leaf/Wait.spec.ts @@ -0,0 +1,448 @@ +import { assert } from "chai"; +import sinon, { SinonFakeTimers, SinonSandbox } from "sinon"; + +import { BehaviourTree, State } from "../../../src/index"; +import { RootNodeDefinition } from "../../../src/BehaviourTreeDefinition"; +import { Agent } from "../../../src/Agent"; + +import { findNode } from "../../TestUtilities"; + +describe("A Wait node", () => { + describe("on tree initialisation", () => { + describe("will error if", () => { + describe("the defined node arguments are not integers", () => { + it("(MDSL)", () => { + const definition = "root { wait ['not', 'integers'] }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: wait node durations must be integer values" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "wait", + duration: ["not", "integers"] + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected array containing two integer values for 'duration' property if defined for wait node at depth '1'" + ); + }); + }); + + describe("a negative duration node argument was defined", () => { + it("(MDSL)", () => { + const definition = "root { wait [-1] }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a wait node must have a positive duration" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "wait", + duration: -1 + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected positive duration value for 'duration' property if defined for wait node at depth '1'" + ); + }); + }); + + describe("more than two node arguments are defined", () => { + it("(MDSL)", () => { + const definition = "root { wait [0, 1000, 4000] }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: invalid number of wait node duration arguments defined" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "wait", + duration: [0, 1000, 4000] + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected array containing two integer values for 'duration' property if defined for wait node at depth '1'" + ); + }); + }); + + describe("a minimum duration bound node argument is defined that is greater than the maximum duration bound node argument", () => { + it("(MDSL)", () => { + const definition = "root { wait [1000, 500] }"; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: a wait node must not have a minimum duration that exceeds the maximum duration" + ); + }); + + it("(JSON)", () => { + const definition = { + type: "root", + child: { + type: "wait", + duration: [1000, 500] + } + } as any; + assert.throws( + () => new BehaviourTree(definition, {}), + Error, + "invalid definition: expected minimum duration value that does not exceed the maximum duration value for 'duration' property if defined for wait node at depth '1'" + ); + }); + }); + }); + }); + + describe("when updated as part of a tree step", () => { + var clock: SinonFakeTimers; + var sandbox: SinonSandbox; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + sandbox = sinon.createSandbox(); + sandbox.replace(globalThis.Math, "random", () => 0.5); + }); + + afterEach(() => { + clock.restore(); + sandbox.restore(); + }); + + describe("and an explicit duration was defined will move to the SUCCEEDED state if the duration has expired", () => { + it("(MDSL)", () => { + const definition = "root { wait [100] }"; + const tree = new BehaviourTree(definition, {}); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(99); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(1); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "wait", + duration: 100 + } + }; + const tree = new BehaviourTree(definition, {}); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(99); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(1); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + + describe("and min and max durations were defined", () => { + describe("will select a random duration between the min and max durations and move to the SUCCEEDED state if the duration has expired", () => { + it("(MDSL)", () => { + const definition = "root { wait [5000, 10000] }"; + const tree = new BehaviourTree(definition, {}); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + // We have spied on Math.random to always return 0.5 for the sake of this test, so our actual duration 'should' be 7500ms. + clock.tick(7499); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(1); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "wait", + duration: [5000, 10000] + } + }; + const tree = new BehaviourTree(definition, {}); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + // We have spied on Math.random to always return 0.5 for the sake of this test, so our actual duration 'should' be 7500ms. + clock.tick(7499); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(1); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + + describe("will use the 'random' function to select the duration between the min and max durations if it was defined as a behaviour tree option and move to the SUCCEEDED state if the duration has expired", () => { + it("(MDSL)", () => { + const definition = "root { wait [5000, 10000] }"; + const options = { + // Usually this would return a new pseudo-random number each time, but for the sake of this test we + // just want to make sure that the number we return actually has an impact on which duration is picked. + random: () => 0.2 + }; + const tree = new BehaviourTree(definition, {}, options); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + // Our 'random' function option will always return 0.2 for the sake of this test, so our actual duration 'should' be 6000ms. + clock.tick(5999); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(1); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "wait", + duration: [5000, 10000] + } + }; + const options = { + // Usually this would return a new pseudo-random number each time, but for the sake of this test we + // just want to make sure that the number we return actually has an impact on which duration is picked. + random: () => 0.2 + }; + const tree = new BehaviourTree(definition, {}, options); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + // Our 'random' function option will always return 0.2 for the sake of this test, so our actual duration 'should' be 6000ms. + clock.tick(5999); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(1); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + }); + + describe("and an explicit duration or min and max durations were not defined will stay in the RUNNING state until aborted", () => { + it("(MDSL)", () => { + const definition = "root { wait while(CanWait) }"; + let canWait = true; + const agent = { CanWait: () => canWait }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(1000000); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + canWait = false; + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.FAILED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "wait", + while: { + call: "CanWait" + } + } + }; + let canWait = true; + const agent = { CanWait: () => canWait }; + const tree = new BehaviourTree(definition, agent); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + clock.tick(1000000); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + canWait = false; + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.FAILED); + }); + }); + + describe("will use the 'getDeltaTime' function to update the elapsed duration if it was defined as a behaviour tree option", () => { + it("(MDSL)", () => { + const definition = "root { wait [1000] }"; + const tree = new BehaviourTree(definition, {}, { getDeltaTime: () => 0.5 }); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + + it("(JSON)", () => { + const definition: RootNodeDefinition = { + type: "root", + child: { + type: "wait", + duration: 1000 + } + }; + const tree = new BehaviourTree(definition, {}, { getDeltaTime: () => 0.5 }); + + let node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.READY); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.RUNNING); + + tree.step(); + + node = findNode(tree, "wait"); + assert.strictEqual(node.state, State.SUCCEEDED); + }); + }); + }); +}); diff --git a/test/nodes/leaf/action.test.js b/test/nodes/leaf/action.test.js deleted file mode 100644 index c4f274d..0000000 --- a/test/nodes/leaf/action.test.js +++ /dev/null @@ -1,208 +0,0 @@ -const mistreevous = require("../../../dist/index"); -const chai = require("chai"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => - tree.getFlattenedNodeDetails().find((node) => node.type === type && node.caption === caption); - -describe("An Action node", () => { - describe("on tree initialisation", () => { - it("will error if an action name identifier is not the first node argument", () => { - const definition = "root { action [] }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: expected action name identifier argument" - ); - }); - }); - - describe("when updated as part of a tree step", () => { - describe("will call the function defined by the first node argument", () => { - describe("when the referenced function is", () => { - it("a registered function", () => { - mistreevous.BehaviourTree.register("doAction", () => { - return mistreevous.State.SUCCEEDED; - }); - - const definition = "root { action [doAction] }"; - const tree = new mistreevous.BehaviourTree(definition, {}); - - tree.step(); - - node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - it("an agent function", () => { - const definition = "root { action [doAction] }"; - const agent = { doAction: () => mistreevous.State.SUCCEEDED }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - - node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - }); - - it("and will error if there is no agent function or registered function that matches the action name", () => { - const definition = "root { action [DoTheThing] }"; - let tree; - assert.doesNotThrow(() => (tree = new mistreevous.BehaviourTree(definition, {})), Error); - assert.throws( - () => tree.step(), - Error, - "error stepping tree: cannot update action node as the action 'DoTheThing' function is not defined on the agent and has not been registered" - ); - }); - - describe("and move to", () => { - it("the SUCCESS state if the function returns a value of State.SUCCEEDED", () => { - const definition = "root { action [doAction] }"; - const agent = { doAction: () => mistreevous.State.SUCCEEDED }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - it("the FAILED state if the function returns a value of State.FAILED", () => { - const definition = "root { action [doAction] }"; - const agent = { doAction: () => mistreevous.State.FAILED }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - - describe("the RUNNING state if", () => { - it("the function returns undefined", () => { - const definition = "root { action [doAction] }"; - const agent = { doAction: () => {} }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - }); - - it("the function returns a promise to return a value of State.SUCCEEDED or State.FAILED", (done) => { - const result = new Promise((resolve) => resolve(mistreevous.State.SUCCEEDED)); - - const definition = "root { action [doAction] }"; - const agent = { doAction: () => result }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - result - .then(() => tree.step()) - .then(() => { - node = findNode(tree, "action", "doAction"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }) - .then(done); - }); - }); - }); - - describe("and pass any node arguments that follow the action name identifier argument where", () => { - describe("the argument is a", () => { - it("string", () => { - const definition = 'root { action [doAction, "hello world!"] }'; - const agent = { - doAction: (arg) => assert.strictEqual(arg, "hello world!") - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("string with escaped quotes", () => { - const definition = 'root { action [doAction, "hello \\" world!"] }'; - const agent = { - doAction: (arg) => assert.strictEqual(arg, 'hello " world!') - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("number", () => { - const definition = "root { action [doAction, 23.4567] }"; - const agent = { - doAction: (arg) => assert.strictEqual(arg, 23.4567) - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("boolean 'true' literal", () => { - const definition = "root { action [doAction, true] }"; - const agent = { - doAction: (arg) => assert.strictEqual(arg, true) - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("boolean 'false' literal", () => { - const definition = "root { action [doAction, false] }"; - const agent = { - doAction: (arg) => assert.strictEqual(arg, false) - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("null", () => { - const definition = "root { action [doAction, null] }"; - const agent = { - doAction: (arg) => assert.isNull(arg) - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - }); - - it("there are multiple arguments", () => { - const definition = 'root { action [doAction, 1.23, "hello world!", false, null] }'; - const agent = { - doAction: (arg0, arg1, arg2, arg3) => { - assert.strictEqual(arg0, 1.23); - assert.strictEqual(arg1, "hello world!"); - assert.strictEqual(arg2, false); - assert.strictEqual(arg3, null); - } - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - }); - }); - }); -}); diff --git a/test/nodes/leaf/condition.test.js b/test/nodes/leaf/condition.test.js deleted file mode 100644 index 939d172..0000000 --- a/test/nodes/leaf/condition.test.js +++ /dev/null @@ -1,171 +0,0 @@ -const mistreevous = require("../../../dist/index"); -const chai = require("chai"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => - tree.getFlattenedNodeDetails().find((node) => node.type === type && node.caption === caption); - -describe("A Condition node", () => { - describe("on tree initialisation", () => { - it("will error if a condition name identifier is not the first node argument", () => { - const definition = "root { condition [] }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: expected condition name identifier argument" - ); - }); - }); - - describe("when updated as part of a tree step", () => { - describe("will call the function defined by the first node argument", () => { - describe("when the referenced function is", () => { - it("a registered function", () => { - mistreevous.BehaviourTree.register("someCondition", () => { - return true; - }); - - const definition = "root { condition [someCondition] }"; - const tree = new mistreevous.BehaviourTree(definition, {}); - - tree.step(); - - node = findNode(tree, "condition", "someCondition"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - it("an agent function", () => { - const definition = "root { condition [someCondition] }"; - const agent = { someCondition: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - - node = findNode(tree, "condition", "someCondition"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - }); - - it("and will error if there is no agent function or registered function that matches the condition name", () => { - const definition = "root { condition [someCondition] }"; - let tree; - assert.doesNotThrow(() => (tree = new mistreevous.BehaviourTree(definition, {})), Error); - assert.throws( - () => tree.step(), - Error, - "error stepping tree: cannot update condition node as the condition 'someCondition' function is not defined on the agent and has not been registered" - ); - }); - - describe("and move to", () => { - it("the SUCCESS state if the function returns a truthy value", () => { - const definition = "root { condition [someCondition] }"; - const agent = { someCondition: () => true }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "condition", "someCondition"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "condition", "someCondition"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - it("the FAILED state if the function returns a falsy value", () => { - const definition = "root { condition [someCondition] }"; - const agent = { someCondition: () => false }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "condition", "someCondition"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "condition", "someCondition"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - }); - - describe("and pass any node arguments that follow the condition name identifier argument where", () => { - describe("the argument is a", () => { - it("string", () => { - const definition = 'root { condition [someCondition, "hello world!"] }'; - const agent = { - someCondition: (arg) => assert.strictEqual(arg, "hello world!") - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("string with escaped quotes", () => { - const definition = 'root { condition [someCondition, "hello \\" world!"] }'; - const agent = { - someCondition: (arg) => assert.strictEqual(arg, 'hello " world!') - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("number", () => { - const definition = "root { condition [someCondition, 23.4567] }"; - const agent = { - someCondition: (arg) => assert.strictEqual(arg, 23.4567) - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("boolean 'true' literal", () => { - const definition = "root { condition [someCondition, true] }"; - const agent = { - someCondition: (arg) => assert.strictEqual(arg, true) - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("boolean 'false' literal", () => { - const definition = "root { condition [someCondition, false] }"; - const agent = { - someCondition: (arg) => assert.strictEqual(arg, false) - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - - it("null", () => { - const definition = "root { condition [someCondition, null] }"; - const agent = { - someCondition: (arg) => assert.isNull(arg) - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - }); - - it("there are multiple arguments", () => { - const definition = 'root { condition [someCondition, 1.23, "hello world!", false, null] }'; - const agent = { - someCondition: (arg0, arg1, arg2, arg3) => { - assert.strictEqual(arg0, 1.23); - assert.strictEqual(arg1, "hello world!"); - assert.strictEqual(arg2, false); - assert.strictEqual(arg3, null); - } - }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - tree.step(); - }); - }); - }); - }); -}); diff --git a/test/nodes/leaf/wait.test.js b/test/nodes/leaf/wait.test.js deleted file mode 100644 index 0dc3e1b..0000000 --- a/test/nodes/leaf/wait.test.js +++ /dev/null @@ -1,203 +0,0 @@ -const mistreevous = require("../../../dist/index"); -const chai = require("chai"); -const sinon = require("sinon"); - -var assert = chai.assert; - -const findNode = (tree, type, caption) => - tree.getFlattenedNodeDetails().find((node) => node.type === type && (!caption || node.caption === caption)); - -describe("A Wait node", () => { - describe("on tree initialisation", () => { - describe("will error if", () => { - it("the defined node arguments are not integers", () => { - const definition = "root { wait ['not', 'integers'] }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: wait node durations must be integer values" - ); - }); - - it("a negative duration node argument was defined", () => { - const definition = "root { wait [-1] }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: a wait node must have a positive duration" - ); - }); - - it("more than two node arguments are defined", () => { - const definition = "root { wait [0, 1000, 4000] }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: invalid number of wait node duration arguments defined" - ); - }); - - it("a minimum duration bound node argument is defined that is greater than the maximum duration bound node argument", () => { - const definition = "root { wait [1000, 500] }"; - assert.throws( - () => new mistreevous.BehaviourTree(definition, {}), - Error, - "error parsing tree: a wait node must not have a minimum duration that exceeds the maximum duration" - ); - }); - }); - }); - - describe("when updated as part of a tree step", () => { - var clock; - var sandbox; - - beforeEach(() => { - clock = sinon.useFakeTimers(); - sandbox = sinon.createSandbox(); - sandbox.replace(globalThis.Math, "random", () => 0.5); - }); - - afterEach(() => { - clock.restore(); - sandbox.restore(); - }); - - it("and an explicit duration was defined will move to the SUCCEEDED state if the duration has expired", () => { - const definition = "root { wait [100] }"; - const tree = new mistreevous.BehaviourTree(definition, {}); - - let node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - clock.tick(99); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - clock.tick(1); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - describe("and min and max durations were defined", () => { - it("will select a random duration between the min and max durations and move to the SUCCEEDED state if the duration has expired", () => { - const definition = "root { wait [5000, 10000] }"; - const tree = new mistreevous.BehaviourTree(definition, {}); - - let node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - // We have spied on Math.random to always return 0.5 for the sake of this test, so our actual duration 'should' be 7500ms. - clock.tick(7499); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - clock.tick(1); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - - it("will use the 'random' function to select the duration between the min and max durations if it was defined as a behaviour tree option and move to the SUCCEEDED state if the duration has expired", () => { - const definition = "root { wait [5000, 10000] }"; - const options = { - // Usually this would return a new pseudo-random number each time, but for the sake of this test we - // just want to make sure that the number we return actually has an impact on which duration is picked. - random: () => 0.2 - }; - const tree = new mistreevous.BehaviourTree(definition, {}, options); - - let node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - // Our 'random' function option will always return 0.2 for the sake of this test, so our actual duration 'should' be 6000ms. - clock.tick(5999); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - clock.tick(1); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - }); - - it("and an explicit duration or min and max durations were not defined will stay in the RUNNING state until aborted", () => { - const definition = "root { wait while(CanWait) }"; - let canWait = true; - const agent = { CanWait: () => canWait }; - const tree = new mistreevous.BehaviourTree(definition, agent); - - let node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - clock.tick(1000000); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - canWait = false; - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.FAILED); - }); - - it("will use the 'getDeltaTime' function to update the elapsed duration if it was defined as a behaviour tree option", () => { - const definition = "root { wait [1000] }"; - const tree = new mistreevous.BehaviourTree(definition, {}, { getDeltaTime: () => 0.5 }); - - let node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.READY); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.RUNNING); - - tree.step(); - - node = findNode(tree, "wait"); - assert.strictEqual(node.state, mistreevous.State.SUCCEEDED); - }); - }); -});