UNPKG

behaviortree

Version:

A JavaScript implementation of Behavior Trees. They are useful for implementing AIs. For Browsers and NodeJS.

379 lines (378 loc) 14.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-env jest */ const constants_1 = require("./constants"); const BehaviorTree_1 = __importDefault(require("./BehaviorTree")); const Task_1 = __importDefault(require("./Task")); const InvertDecorator_1 = __importDefault(require("./decorators/InvertDecorator")); const Introspector_1 = __importDefault(require("./Introspector")); const Parallel_1 = __importDefault(require("./Parallel")); const Selector_1 = __importDefault(require("./Selector")); const Sequence_1 = __importDefault(require("./Sequence")); const Random_1 = __importDefault(require("./Random")); describe('Introspector', () => { let bTree; let blackboard; let introspector; const simpleTask = new Task_1.default({ name: 'The Task', start: function (blackboard) { ++blackboard.start; }, run: function (blackboard) { ++blackboard.run; return blackboard.result; }, end: function (blackboard) { ++blackboard.end; } }); const failingTask = new Task_1.default({ name: 'Bumm', run: function () { return constants_1.FAILURE; } }); const runningTask = new Task_1.default({ name: 'Forest', run: function () { return constants_1.RUNNING; } }); BehaviorTree_1.default.register('simpleTask', simpleTask); BehaviorTree_1.default.register('failingTask', failingTask); BehaviorTree_1.default.register('runningTask', runningTask); beforeEach(() => { blackboard = { start: 0, run: 0, end: 0, result: constants_1.SUCCESS }; introspector = new Introspector_1.default(); }); it('is empty initially', () => { expect(introspector.lastResult).toEqual(null); expect(introspector.results).toEqual([]); }); describe('with the simplest tree possible', () => { beforeEach(() => { bTree = new BehaviorTree_1.default({ tree: 'simpleTask', blackboard }); }); it('puts in the result of the last run', () => { bTree.step({ introspector }); const resultFirstRun = { name: 'The Task', result: constants_1.SUCCESS }; expect(introspector.lastResult).toEqual(resultFirstRun); expect(introspector.results).toEqual([resultFirstRun]); blackboard.result = constants_1.FAILURE; bTree.step({ introspector }); const resultSecondRun = { name: 'The Task', result: constants_1.FAILURE }; expect(introspector.lastResult).toEqual(resultSecondRun); expect(introspector.results).toEqual([resultFirstRun, resultSecondRun]); blackboard.result = constants_1.RUNNING; bTree.step({ introspector }); const resultThirdRun = { name: 'The Task', result: constants_1.RUNNING }; expect(introspector.lastResult).toEqual(resultThirdRun); expect(introspector.results).toEqual([resultFirstRun, resultSecondRun, resultThirdRun]); }); }); describe('with nameless tasks', () => { beforeEach(() => { blackboard = { start: 0, run: 0, end: 0, result: constants_1.SUCCESS }; bTree = new BehaviorTree_1.default({ tree: new Task_1.default({ run: () => constants_1.RUNNING }), blackboard }); }); it('does not print a name', () => { bTree.step({ introspector }); const resultFirstRun = { result: constants_1.RUNNING }; expect(introspector.lastResult).toEqual(resultFirstRun); expect(introspector.results).toEqual([resultFirstRun]); }); }); describe('with nameless branching nodes', () => { beforeEach(() => { blackboard = { start: 0, run: 0, end: 0, result: constants_1.SUCCESS }; bTree = new BehaviorTree_1.default({ tree: new Sequence_1.default({ nodes: ['simpleTask'] }), blackboard }); }); it('does not print a name', () => { bTree.step({ introspector }); const resultFirstRun = { children: [ { name: 'The Task', result: constants_1.SUCCESS } ], result: constants_1.SUCCESS }; expect(introspector.lastResult).toEqual(resultFirstRun); expect(introspector.results).toEqual([resultFirstRun]); }); }); describe('with a decorator', () => { beforeEach(() => { blackboard = { start: 0, run: 0, end: 0, result: constants_1.SUCCESS }; const tree = new InvertDecorator_1.default({ name: 'inverter', node: 'simpleTask' }); bTree = new BehaviorTree_1.default({ tree, blackboard }); }); it('shows Task and Decorator', () => { bTree.step({ introspector }); const result = { name: 'inverter', result: constants_1.FAILURE, children: [ { name: 'The Task', result: constants_1.SUCCESS } ] }; expect(introspector.lastResult).toEqual(result); expect(introspector.results).toEqual([result]); }); }); describe('with a selector', () => { beforeEach(() => { blackboard = { start: 0, run: 0, end: 0, result: constants_1.SUCCESS }; }); it('does not show task that did not run', () => { const tree = new Selector_1.default({ name: 'select', nodes: ['simpleTask', 'failingTask'] }); bTree = new BehaviorTree_1.default({ tree, blackboard }); bTree.step({ introspector }); const result = { name: 'select', result: constants_1.SUCCESS, children: [ { name: 'The Task', result: constants_1.SUCCESS } ] }; expect(introspector.lastResult).toEqual(result); expect(introspector.results).toEqual([result]); }); it('show all tasks if all did run', () => { const tree = new Selector_1.default({ name: 'select', nodes: ['failingTask', 'simpleTask'] }); bTree = new BehaviorTree_1.default({ tree, blackboard }); bTree.step({ introspector }); const result = { name: 'select', result: constants_1.SUCCESS, children: [ { name: 'Bumm', result: constants_1.FAILURE }, { name: 'The Task', result: constants_1.SUCCESS } ] }; expect(introspector.lastResult).toEqual(result); expect(introspector.results).toEqual([result]); }); it('does not show more then was running', () => { const tree = new Selector_1.default({ name: 'select', nodes: ['runningTask', 'simpleTask'] }); bTree = new BehaviorTree_1.default({ tree, blackboard }); bTree.step({ introspector }); const result = { name: 'select', result: constants_1.RUNNING, children: [ { name: 'Forest', result: constants_1.RUNNING } ] }; expect(introspector.lastResult).toEqual(result); expect(introspector.results).toEqual([result]); }); }); describe('with a parallel node', () => { beforeEach(() => { blackboard = { start: 0, run: 0, end: 0, result: constants_1.SUCCESS }; }); it('shows all tasks that did and did not run', () => { const tree = new Parallel_1.default({ name: 'parallel', nodes: ['runningTask', 'simpleTask'] }); bTree = new BehaviorTree_1.default({ tree, blackboard }); bTree.step({ introspector }); const result = { name: 'parallel', result: constants_1.RUNNING, children: [ { name: 'Forest', result: constants_1.RUNNING }, { name: 'The Task', result: constants_1.SUCCESS } ] }; expect(introspector.lastResult).toEqual(result); expect(introspector.results).toEqual([result]); }); }); describe('a full scale tree', () => { beforeEach(() => { blackboard = { start: 0, run: 0, end: 0, result: constants_1.SUCCESS }; }); it('shows all that did run', () => { const invertedSimple = new InvertDecorator_1.default({ node: 'simpleTask' }); const selector1 = new Selector_1.default({ name: 'select1', nodes: ['failingTask', 'simpleTask'] }); const selector2 = new Selector_1.default({ name: 'select2', nodes: [invertedSimple, 'simpleTask', 'failingTask'] }); const tree = new Sequence_1.default({ name: 'sequence', nodes: [selector1, selector2] }); bTree = new BehaviorTree_1.default({ tree, blackboard }); bTree.step({ introspector }); const result = { name: 'sequence', result: constants_1.SUCCESS, children: [ { name: 'select1', result: constants_1.SUCCESS, children: [ { name: 'Bumm', result: constants_1.FAILURE }, { name: 'The Task', result: constants_1.SUCCESS } ] }, { name: 'select2', result: constants_1.SUCCESS, children: [ { result: constants_1.FAILURE, children: [ { name: 'The Task', result: constants_1.SUCCESS } ] }, { name: 'The Task', result: constants_1.SUCCESS } ] } ] }; expect(introspector.lastResult).toEqual(result); expect(introspector.results).toEqual([result]); }); }); describe('with a Random node', () => { beforeEach(() => { blackboard = { start: 0, run: 0, end: 0, result: constants_1.SUCCESS }; bTree = new BehaviorTree_1.default({ tree: new Random_1.default({ nodes: ['simpleTask', 'failingTask'] }), blackboard }); }); it('cleans the results', () => { for (let i = 10; i--;) { bTree.step({ introspector }); } expect(introspector.lastResult).not.toEqual(null); expect(introspector.results.length).toEqual(10); expect(introspector.lastResult).toEqual(expect.objectContaining({ children: [{ name: expect.any(String), result: expect.any(Boolean) }], result: expect.any(Boolean) })); }); }); describe('.reset method', () => { it('cleans the results', () => { bTree = new BehaviorTree_1.default({ tree: 'simpleTask', blackboard: {} }); bTree.step({ introspector }); expect(introspector.lastResult).not.toEqual(null); expect(introspector.results).not.toEqual([]); introspector.reset(); expect(introspector.lastResult).toEqual(null); expect(introspector.results).toEqual([]); }); }); describe('having a custom introspector module', () => { class BlackboardChangesIntrospector extends Introspector_1.default { start(tree) { this.blackboardSnap = JSON.stringify(tree.blackboard); super.start(tree); } // eslint-disable-next-line @typescript-eslint/no-explicit-any _toResult(node, result, blackboard) { const newSnap = JSON.stringify(blackboard); const blackboardChanged = newSnap !== this.blackboardSnap; this.blackboardSnap = newSnap; return { ...(node.name ? { name: node.name } : {}), result, blackboardChanged }; } } beforeEach(() => { introspector = new BlackboardChangesIntrospector(); }); it('also has the blackboard available', () => { bTree = new BehaviorTree_1.default({ tree: new Sequence_1.default({ nodes: ['simpleTask', 'failingTask'] }), blackboard }); bTree.step({ introspector }); // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((introspector.lastResult?.children || []).map((x) => x.blackboardChanged)).toEqual([true, false]); }); }); });