behaviortree
Version:
A JavaScript implementation of Behavior Trees. They are useful for implementing AIs. For Browsers and NodeJS.
226 lines (225 loc) • 9.83 kB
JavaScript
"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 */
/* eslint-disable @typescript-eslint/no-explicit-any */
const constants_1 = require("./constants");
const Parallel_1 = __importDefault(require("./Parallel"));
const Task_1 = __importDefault(require("./Task"));
describe('Parallel', () => {
let countSuccess = 0;
const successTask = new Task_1.default({
run: function () {
++countSuccess;
return constants_1.SUCCESS;
}
});
let countFail = 0;
const failTask = new Task_1.default({
run: function () {
++countFail;
return constants_1.FAILURE;
}
});
let countRunning = 0;
const runningTask = new Task_1.default({
run: function () {
++countRunning;
return constants_1.RUNNING;
}
});
const switchTask = new Task_1.default({
run: function (blackboard) {
++blackboard.switchCounter;
return blackboard.switchResult;
}
});
const switchTask2 = new Task_1.default({
run: function (blackboard) {
++blackboard.switchCounter2;
return blackboard.switchResult2;
}
});
const switchTask3 = new Task_1.default({
run: function (blackboard) {
++blackboard.switchCounter3;
return blackboard.switchResult3;
}
});
beforeEach(() => {
countSuccess = 0;
countFail = 0;
countRunning = 0;
});
it('runs all child nodes and returns running child index as long as one node is running and none are failing', () => {
const parallel = new Parallel_1.default({
nodes: [successTask, runningTask, successTask]
});
const result = parallel.run();
expect(countRunning).toEqual(1);
expect(countSuccess).toEqual(2);
expect(result).toEqual({ total: constants_1.RUNNING, state: [constants_1.SUCCESS, constants_1.RUNNING, constants_1.SUCCESS] });
});
it('returns failure if one task is failing', () => {
const parallel = new Parallel_1.default({
nodes: [successTask, runningTask, failTask]
});
const result = parallel.run();
expect(countSuccess).toEqual(1);
expect(countRunning).toEqual(1);
expect(countFail).toEqual(1);
expect(result).toEqual(constants_1.FAILURE);
});
it('returns success if all tasks are success', () => {
const parallel = new Parallel_1.default({
nodes: [successTask, successTask]
});
const result = parallel.run();
expect(countSuccess).toEqual(2);
expect(result).toEqual(constants_1.SUCCESS);
});
describe('running tasks', () => {
const parallel = new Parallel_1.default({
nodes: [switchTask, switchTask2, switchTask3]
});
it('resumes tasks that where running and stops as soon as one task returns failure', () => {
const blackboard = {
switchCounter: 0,
switchResult: constants_1.RUNNING,
switchCounter2: 0,
switchResult2: constants_1.RUNNING,
switchCounter3: 0,
switchResult3: constants_1.RUNNING
};
let result = parallel.run(blackboard);
expect(blackboard.switchCounter).toEqual(1);
expect(blackboard.switchCounter2).toEqual(1);
expect(blackboard.switchCounter3).toEqual(1);
expect(result).toEqual({ total: constants_1.RUNNING, state: [constants_1.RUNNING, constants_1.RUNNING, constants_1.RUNNING] });
blackboard.switchResult2 = constants_1.SUCCESS;
result = parallel.run(blackboard, { lastRun: result });
expect(blackboard.switchCounter).toEqual(2);
expect(blackboard.switchCounter2).toEqual(2);
expect(blackboard.switchCounter3).toEqual(2);
expect(result).toEqual({ total: constants_1.RUNNING, state: [constants_1.RUNNING, constants_1.SUCCESS, constants_1.RUNNING] });
blackboard.switchResult3 = constants_1.FAILURE;
result = parallel.run(blackboard, { lastRun: result });
// counter 2 did not run anymore
expect(blackboard.switchCounter).toEqual(3);
expect(blackboard.switchCounter2).toEqual(2);
expect(blackboard.switchCounter3).toEqual(3);
expect(result).toEqual(constants_1.FAILURE);
});
});
describe('deeper nesting', () => {
it('works as with shallow nodes', () => {
const innerParallel = new Parallel_1.default({
nodes: [switchTask, switchTask2]
});
const parallel = new Parallel_1.default({
nodes: [innerParallel, switchTask3]
});
const blackboard = {
switchCounter: 0,
switchResult: constants_1.RUNNING,
switchCounter2: 0,
switchResult2: constants_1.RUNNING,
switchCounter3: 0,
switchResult3: constants_1.RUNNING
};
let result = parallel.run(blackboard);
expect(blackboard.switchCounter).toEqual(1);
expect(blackboard.switchCounter2).toEqual(1);
expect(blackboard.switchCounter3).toEqual(1);
expect(result).toEqual({ total: constants_1.RUNNING, state: [{ total: constants_1.RUNNING, state: [constants_1.RUNNING, constants_1.RUNNING] }, constants_1.RUNNING] });
blackboard.switchResult2 = constants_1.SUCCESS;
result = parallel.run(blackboard, { lastRun: result });
expect(blackboard.switchCounter).toEqual(2);
expect(blackboard.switchCounter2).toEqual(2);
expect(blackboard.switchCounter3).toEqual(2);
expect(result).toEqual({ total: constants_1.RUNNING, state: [{ total: constants_1.RUNNING, state: [constants_1.RUNNING, constants_1.SUCCESS] }, constants_1.RUNNING] });
blackboard.switchResult = constants_1.FAILURE;
result = parallel.run(blackboard, { lastRun: result });
expect(blackboard.switchCounter).toEqual(3);
expect(blackboard.switchCounter2).toEqual(2);
expect(blackboard.switchCounter3).toEqual(3);
expect(result).toEqual(constants_1.FAILURE);
});
it('works when only one node is running', () => {
const innerParallel = new Parallel_1.default({
nodes: [switchTask, switchTask2]
});
const parallel = new Parallel_1.default({
nodes: [innerParallel, switchTask3]
});
const blackboard = {
switchCounter: 0,
switchResult: constants_1.RUNNING,
switchCounter2: 0,
switchResult2: constants_1.SUCCESS,
switchCounter3: 0,
switchResult3: constants_1.SUCCESS
};
let result = parallel.run(blackboard);
expect(blackboard.switchCounter).toEqual(1);
expect(blackboard.switchCounter2).toEqual(1);
expect(blackboard.switchCounter3).toEqual(1);
expect(result).toEqual({ total: constants_1.RUNNING, state: [{ total: constants_1.RUNNING, state: [constants_1.RUNNING, constants_1.SUCCESS] }, constants_1.SUCCESS] });
blackboard.switchResult = constants_1.SUCCESS;
result = parallel.run(blackboard, { lastRun: result });
expect(blackboard.switchCounter).toEqual(2);
expect(blackboard.switchCounter2).toEqual(1);
expect(blackboard.switchCounter3).toEqual(1);
expect(result).toEqual(constants_1.SUCCESS);
});
});
describe('start and end callbacks', () => {
const aTask = new Task_1.default({
run: function () {
return constants_1.SUCCESS;
}
});
const switchTask = new Task_1.default({
start: function (blackboard) {
++blackboard.start;
},
run: function (blackboard) {
++blackboard.run;
return blackboard.switchResult;
},
end: function (blackboard) {
++blackboard.end;
}
});
it('start is not called again on further running node', () => {
const parallel = new Parallel_1.default({
nodes: [aTask, aTask, switchTask, aTask]
});
const blackboard = {
switchResult: constants_1.RUNNING,
start: 0,
run: 0,
end: 0
};
const result = parallel.run(blackboard);
expect(blackboard.start).toEqual(1);
expect(blackboard.run).toEqual(1);
expect(blackboard.end).toEqual(0);
const result2 = parallel.run(blackboard, { lastRun: result, rerun: true });
expect(blackboard.start).toEqual(1);
expect(blackboard.run).toEqual(2);
expect(blackboard.end).toEqual(0);
blackboard.switchResult = constants_1.SUCCESS;
parallel.run(blackboard, { lastRun: result2, rerun: true });
expect(blackboard.start).toEqual(1);
expect(blackboard.run).toEqual(3);
expect(blackboard.end).toEqual(1);
parallel.run(blackboard);
expect(blackboard.start).toEqual(2);
expect(blackboard.run).toEqual(4);
expect(blackboard.end).toEqual(2);
});
});
});