behaviortree
Version:
A JavaScript implementation of Behavior Trees. They are useful for implementing AIs. For Browsers and NodeJS.
224 lines (223 loc) • 10 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 */
const constants_1 = require("./constants");
const Selector_1 = __importDefault(require("./Selector"));
const Task_1 = __importDefault(require("./Task"));
describe('Selector', () => {
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;
}
});
beforeEach(() => {
countSuccess = 0;
countFail = 0;
countRunning = 0;
});
it('stops immediately at a successfull node', () => {
const selector = new Selector_1.default({
nodes: [successTask, failTask]
});
selector.run();
expect(countSuccess).toEqual(1);
expect(countFail).toEqual(0);
});
it('stops at a successfull node', () => {
const selector = new Selector_1.default({
nodes: [failTask, successTask, successTask, successTask, failTask]
});
selector.run();
expect(countSuccess).toEqual(1);
expect(countFail).toEqual(1);
});
it('calls all tasks if all fail', () => {
const selector = new Selector_1.default({
nodes: [failTask, failTask, failTask, failTask]
});
selector.run();
expect(countSuccess).toEqual(0);
expect(countFail).toEqual(4);
});
it('does not call tasks after running task', () => {
const selector = new Selector_1.default({
nodes: [failTask, failTask, runningTask, failTask]
});
selector.run();
expect(countFail).toEqual(2);
expect(countRunning).toEqual(1);
});
describe('result values', () => {
it('returns SUCCESS if one task succeeds', () => {
const selector = new Selector_1.default({
nodes: [failTask, successTask, failTask]
});
expect(selector.run()).toEqual(constants_1.SUCCESS);
});
it('returns FAILURE if no task succeeds', () => {
const selector = new Selector_1.default({
nodes: [failTask, failTask, failTask, failTask]
});
expect(selector.run()).toEqual(constants_1.FAILURE);
});
it('returns the index of still running task as array of running lastRun', () => {
const selector = new Selector_1.default({
nodes: [failTask, failTask, runningTask, failTask]
});
expect(selector.run()).toEqual({ total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.FAILURE, constants_1.RUNNING] });
});
});
describe('blackboard', () => {
const aTask = new Task_1.default({
run: function (blackboard) {
++blackboard.counter;
return constants_1.FAILURE;
}
});
const bTask = new Task_1.default({
run: function (blackboard) {
blackboard.counter += 10;
return constants_1.FAILURE;
}
});
it('can be passed to a task, and stores data between different tasks', () => {
const selector = new Selector_1.default({
nodes: [aTask, bTask]
});
const blackboard = { counter: 0 };
selector.run(blackboard);
expect(blackboard.counter).toEqual(11);
});
});
describe('nested selectors', () => {
const aTask = new Task_1.default({
run: function (blackboard) {
++blackboard.aCounter;
return constants_1.FAILURE;
}
});
const bTask = new Task_1.default({
run: function (blackboard) {
++blackboard.bCounter;
return constants_1.FAILURE;
}
});
it('calls all nested tasks if all are failing', () => {
const selector = new Selector_1.default({
nodes: [
aTask,
aTask,
new Selector_1.default({
nodes: [bTask, bTask, bTask]
})
]
});
const blackboard = { aCounter: 0, bCounter: 0 };
selector.run(blackboard);
expect(blackboard.aCounter).toEqual(2);
expect(blackboard.bCounter).toEqual(3);
});
describe('running tasks', () => {
const switchTask = new Task_1.default({
run: function (blackboard) {
++blackboard.switchCounter;
return blackboard.switchResult;
}
});
let selector = new Selector_1.default({
nodes: [
aTask,
aTask,
new Selector_1.default({
nodes: [bTask, switchTask, bTask]
}),
aTask
]
});
it('returns lastRun if tasks are running', () => {
const blackboard = { aCounter: 0, bCounter: 0, switchCounter: 0, switchResult: constants_1.RUNNING };
const result = selector.run(blackboard);
expect(blackboard.aCounter).toEqual(2);
expect(blackboard.bCounter).toEqual(1);
expect(result).toEqual({ total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.FAILURE, { total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.RUNNING] }] });
});
it('resumes tasks where we left off', () => {
const blackboard = { aCounter: 0, bCounter: 0, switchCounter: 0, switchResult: constants_1.RUNNING };
let result = selector.run(blackboard);
expect(blackboard.switchCounter).toEqual(1);
expect(result).toEqual({ total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.FAILURE, { total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.RUNNING] }] });
result = selector.run(blackboard, { lastRun: result });
expect(blackboard.switchCounter).toEqual(2);
expect(blackboard.aCounter).toEqual(2);
expect(blackboard.bCounter).toEqual(1);
expect(result).toEqual({ total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.FAILURE, { total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.RUNNING] }] });
});
it('after resuming in can progress, if tasks allow', () => {
const blackboard = { aCounter: 0, bCounter: 0, switchCounter: 0, switchResult: constants_1.RUNNING };
let result = selector.run(blackboard);
expect(blackboard.switchCounter).toEqual(1);
expect(result).toEqual({ total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.FAILURE, { total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.RUNNING] }] });
blackboard.switchResult = constants_1.FAILURE;
result = selector.run(blackboard, { lastRun: result });
expect(blackboard.switchCounter).toEqual(2);
expect(blackboard.aCounter).toEqual(3);
expect(blackboard.bCounter).toEqual(2);
expect(result).toEqual(constants_1.FAILURE);
});
it('after resuming in can progress, if tasks allow', () => {
const blackboard = { aCounter: 0, bCounter: 0, switchCounter: 0, switchResult: constants_1.RUNNING };
let result = selector.run(blackboard);
expect(blackboard.switchCounter).toEqual(1);
expect(result).toEqual({ total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.FAILURE, { total: constants_1.RUNNING, state: [constants_1.FAILURE, constants_1.RUNNING] }] });
blackboard.switchResult = constants_1.SUCCESS;
result = selector.run(blackboard, { lastRun: result });
expect(blackboard.switchCounter).toEqual(2);
// No count increments because of success
expect(blackboard.aCounter).toEqual(2);
expect(blackboard.bCounter).toEqual(1);
expect(result).toEqual(constants_1.SUCCESS);
});
it('does not call start on rerunning running task', () => {
const blackboard = { start: 0, end: 0, switchResult: constants_1.RUNNING };
selector = new Selector_1.default({
start: function (blackboard) {
++blackboard.start;
},
end: function (blackboard) {
++blackboard.end;
},
nodes: [aTask, switchTask]
});
selector.run(blackboard);
expect(blackboard.start).toEqual(1);
expect(blackboard.end).toEqual(0);
selector.run(blackboard, { rerun: true });
expect(blackboard.start).toEqual(1);
expect(blackboard.end).toEqual(0);
blackboard.switchResult = constants_1.FAILURE;
selector.run(blackboard, { rerun: true });
expect(blackboard.start).toEqual(1);
expect(blackboard.end).toEqual(1);
});
});
});
});