@woosh/meep-engine 2.43.40 → 2.43.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/core/binary/BitSet.js +4 -18
  2. package/core/model/node-graph/Connection.js +41 -0
  3. package/core/model/node-graph/Connection.spec.js +21 -0
  4. package/core/model/node-graph/DataType.js +23 -1
  5. package/core/model/node-graph/DataType.spec.js +28 -0
  6. package/core/model/node-graph/NodeGraph.js +16 -7
  7. package/core/model/node-graph/node/NodeDescription.js +12 -5
  8. package/core/model/node-graph/node/NodeInstance.js +9 -3
  9. package/core/model/node-graph/node/NodeInstancePortReference.js +18 -0
  10. package/core/model/node-graph/node/NodeRegistry.js +36 -10
  11. package/core/model/node-graph/node/NodeRegistry.spec.js +25 -0
  12. package/core/model/node-graph/node/Port.js +12 -1
  13. package/core/model/node-graph/node/PortDirection.js +1 -1
  14. package/engine/graphics/render/frame_graph/RenderGraph.js +5 -1
  15. package/engine/intelligence/behavior/SelectorBehavior.js +3 -1
  16. package/engine/intelligence/behavior/behavior_to_dot.js +251 -0
  17. package/engine/intelligence/behavior/behavior_to_dot.prototype.js +55 -0
  18. package/engine/intelligence/behavior/composite/CompositeBehavior.js +6 -0
  19. package/engine/intelligence/behavior/composite/ParallelBehavior.js +10 -0
  20. package/engine/intelligence/behavior/composite/SequenceBehavior.js +6 -50
  21. package/engine/intelligence/behavior/composite/SequenceBehaviorSerializationAdapter.js +52 -0
  22. package/engine/intelligence/blackboard/Blackboard.js +26 -14
  23. package/engine/intelligence/blackboard/Blackboard.spec.js +62 -0
  24. package/engine/intelligence/blackboard/{Blacboard.spec.js → BlackboardSerializationAdapter.spec.js} +1 -1
  25. package/engine/intelligence/mcts/README.md +7 -0
  26. package/engine/intelligence/optimization/RandomOptimizer.js +2 -1
  27. package/engine/intelligence/resource/ResourceAllocationBid.js +3 -3
  28. package/engine/intelligence/resource/ResourceAllocationSolver.js +1 -1
  29. package/engine/intelligence/resource/StrategicResourceAllocator.js +6 -4
  30. package/engine/intelligence/resource/TacticalModule.js +7 -0
  31. package/engine/knowledge/database/StaticKnowledgeDataTableDescriptor.js +5 -5
  32. package/engine/navigation/grid/GridField.js +1 -0
  33. package/engine/notify/NotificationLog.js +2 -2
  34. package/package.json +1 -1
@@ -0,0 +1,251 @@
1
+ import LineBuilder from "../../../core/codegen/LineBuilder.js";
2
+ import { Behavior } from "./Behavior.js";
3
+
4
+ /**
5
+ *
6
+ * @param {Behavior} behavior
7
+ * @return {string}
8
+ */
9
+ function compute_behavior_name(behavior) {
10
+
11
+ // try to get name from the constructor field
12
+ const typeName = behavior.constructor.typeName;
13
+ if (typeof typeName === "string") {
14
+ return typeName;
15
+ }
16
+
17
+ const constructor_name = behavior.constructor.name;
18
+ if (typeof constructor_name === "string") {
19
+ return constructor_name;
20
+ }
21
+
22
+ return '$Behavior';
23
+ }
24
+
25
+
26
+ /**
27
+ *
28
+ * @param {Behavior|SequenceBehavior|ParallelBehavior} behavior
29
+ * @return {Object}
30
+ */
31
+ function behavior_node_attributes(behavior) {
32
+ const result = {};
33
+
34
+ if (behavior.isCompositeBehavior === true) {
35
+
36
+ result.fillcolor = "orange";
37
+
38
+ if (behavior.isSequenceBehavior === true) {
39
+ result.fillcolor = "paleturquoise";
40
+ } else if (behavior.isParallelBehavior === true) {
41
+ result.fillcolor = "plum";
42
+ }
43
+ } else {
44
+
45
+ }
46
+
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ *
52
+ * @param {Object} attributes
53
+ * @return {string}
54
+ */
55
+ function attributes_to_dot(attributes) {
56
+ const key_names = Object.keys(attributes);
57
+
58
+ return key_names.map(key => {
59
+
60
+ return `${key}=${attributes[key]}`;
61
+
62
+ }).join(', ');
63
+ }
64
+
65
+ /**
66
+ *
67
+ * @param {string} prop
68
+ * @param {Object} object
69
+ */
70
+ function should_property_be_included(prop, object) {
71
+ if (prop.startsWith('_')) {
72
+ // private field
73
+ return false;
74
+ }
75
+
76
+ const value = object[prop];
77
+
78
+ if (object.constructor.prototype[prop] === value) {
79
+ return false;
80
+ }
81
+
82
+ const typeof_value = typeof value;
83
+
84
+ if (value === null || typeof_value === "undefined" || typeof_value === "symbol") {
85
+ return false;
86
+ }
87
+
88
+ if (typeof_value === "object" && String(value) === '[object Object]') {
89
+ return false;
90
+ }
91
+
92
+ return true;
93
+ }
94
+
95
+ function property_to_string(value, max_length = 200) {
96
+
97
+
98
+ let string_value;
99
+
100
+ if (typeof value !== "string") {
101
+ string_value = String(value);
102
+ } else {
103
+ string_value = `\"${value}\"`;
104
+ }
105
+
106
+ const sanitized = string_value.replace(/\n/, "\n");
107
+
108
+ if (sanitized.length > max_length) {
109
+ return '[...]';
110
+ }
111
+
112
+ return sanitized;
113
+ }
114
+
115
+ /**
116
+ *
117
+ * @param {Behavior} behavior
118
+ * @param {number} max_props
119
+ */
120
+ function build_node_properties_string(behavior, max_props = 10) {
121
+
122
+ const properties = {};
123
+
124
+ let qualified_property_count = 0;
125
+
126
+ for (const behaviorKey in behavior) {
127
+
128
+ if (should_property_be_included(behaviorKey, behavior)) {
129
+ if (qualified_property_count < max_props) {
130
+ properties[behaviorKey] = property_to_string(behavior[behaviorKey]);
131
+ }
132
+
133
+ qualified_property_count++;
134
+ }
135
+
136
+ }
137
+
138
+ // stringify and concatenate
139
+ return Object.keys(properties).map(key => `${key} = ${properties[key]}`).join('<BR/>');
140
+
141
+ }
142
+
143
+ /**
144
+ *
145
+ * @param {Behavior} behavior
146
+ * @return {boolean}
147
+ */
148
+ function should_behavior_have_properties_string(behavior) {
149
+ if (behavior.isCompositeBehavior === true) {
150
+ return false;
151
+ }
152
+
153
+ return true;
154
+ }
155
+
156
+ function build_node_label(behavior) {
157
+ const node_name = compute_behavior_name(behavior);
158
+
159
+ const label_pieces = [];
160
+
161
+ label_pieces.push(`<B>${node_name}</B>`);
162
+
163
+ if (should_behavior_have_properties_string(behavior)) {
164
+ const properties = build_node_properties_string(behavior);
165
+
166
+ if (properties.trim().length > 0) {
167
+
168
+ label_pieces.push(properties);
169
+
170
+ }
171
+ }
172
+
173
+ return `<{ {${label_pieces.join('<BR/>')} } }>`;
174
+ }
175
+
176
+ /**
177
+ *
178
+ * @param {LineBuilder} out
179
+ * @param {Behavior|CompositeBehavior} behavior
180
+ * @param {{id_counter:number, node_ids:Map<Behavior,string>}} context
181
+ * @returns {string}
182
+ */
183
+ function parse_behavior(out, behavior, context) {
184
+ const id = context.id_counter++;
185
+ const node_id = `Node_${id}`;
186
+
187
+ context.node_ids.set(behavior, node_id);
188
+
189
+ const attributes = {
190
+ label: build_node_label(behavior),
191
+ style: "\"rounded,filled\""
192
+ };
193
+
194
+ Object.assign(attributes, behavior_node_attributes(behavior));
195
+
196
+ out.add(`${node_id} [${attributes_to_dot(attributes)}]`);
197
+
198
+ if (behavior.isCompositeBehavior === true) {
199
+ // is a composite behavior, which means it includes other behaviors
200
+
201
+ const children = behavior.getChildren();
202
+
203
+ const child_count = children.length;
204
+
205
+ for (let i = 0; i < child_count; i++) {
206
+ const child = children[i];
207
+
208
+ const child_id = parse_behavior(out, child, context);
209
+
210
+ // create edge
211
+ out.add(`${node_id} -> ${child_id} [label="№${i + 1}"];`);
212
+ }
213
+
214
+ }
215
+
216
+ return node_id;
217
+ }
218
+
219
+ /**
220
+ * Produces a diagram of behavior tree in DOT format, useful for debugging and exploration of complex trees
221
+ *
222
+ * @see https://en.wikipedia.org/wiki/DOT_(graph_description_language)
223
+ * @param {Behavior} behavior
224
+ * @returns {string}
225
+ */
226
+ export function behavior_to_dot(behavior) {
227
+ const out = new LineBuilder();
228
+
229
+ const context = {
230
+ id_counter: 0,
231
+ node_ids: new Map()
232
+ };
233
+
234
+ const font = {
235
+ name: "helvetica",
236
+ size: 10
237
+ };
238
+
239
+ out.add("digraph BehaviorTree {");
240
+ out.indent();
241
+ out.add("graph [style=invis, rankdir=\"TB\" ordering=out, splines=spline]");
242
+ out.add(`node [shape=record, fontname=\"${font.name}\", fontsize=${font.size}, margin=\"0.2,0.03\", fillcolor=ivory]`);
243
+ out.add(`edge [labelfontname=\"${font.name}\", labelfontsize=${font.size}, fontname=\"${font.name}\", fontsize=${font.size}, fontcolor=grey]`);
244
+
245
+ parse_behavior(out, behavior, context);
246
+
247
+ out.dedent();
248
+ out.add("}");
249
+
250
+ return out.build();
251
+ }
@@ -0,0 +1,55 @@
1
+ import { SequenceBehavior } from "./composite/SequenceBehavior.js";
2
+ import { SucceedingBehavior } from "./primitive/SucceedingBehavior.js";
3
+ import { Behavior } from "./Behavior.js";
4
+ import { ParallelBehavior } from "./composite/ParallelBehavior.js";
5
+ import { behavior_to_dot } from "./behavior_to_dot.js";
6
+ import { DelayBehavior } from "./util/DelayBehavior.js";
7
+
8
+
9
+ class CenterCameraOnObject extends Behavior{
10
+ object = "whiteboard"
11
+ }
12
+
13
+ class UserControlsDisable extends Behavior{
14
+
15
+ }
16
+
17
+ class PlayVideoInPopup extends Behavior{
18
+ url = "url/how_to_use_whiteboard.mp4"
19
+ }
20
+
21
+ class DrawCurvesOnWhiteBoard extends Behavior{
22
+ curve = "hello"
23
+
24
+ }
25
+
26
+ class UserControlsEnable extends Behavior{
27
+
28
+ }
29
+
30
+ const complex = SequenceBehavior.from([
31
+ new SucceedingBehavior(),
32
+ SequenceBehavior.from([
33
+ new Behavior()
34
+ ]),
35
+ ParallelBehavior.from([
36
+ new SucceedingBehavior(),
37
+ DelayBehavior.from(17)
38
+ ])
39
+ ]);
40
+
41
+ const tutorial = SequenceBehavior.from([ // will execute all members one after another
42
+
43
+ ParallelBehavior.from([ // will execute all members at the same time, completes when last member completes
44
+
45
+ new CenterCameraOnObject( ), // center camera on whiteboard
46
+ new UserControlsDisable(),
47
+ new PlayVideoInPopup() // plays a video in a popup, behavior finishes when video does
48
+
49
+ ]),
50
+ new DrawCurvesOnWhiteBoard(),
51
+ new UserControlsEnable()
52
+
53
+ ])
54
+
55
+ console.log(behavior_to_dot(tutorial));
@@ -54,3 +54,9 @@ export class CompositeBehavior extends Behavior {
54
54
  this.__children = [];
55
55
  }
56
56
  }
57
+
58
+ /**
59
+ *
60
+ * @type {boolean}
61
+ */
62
+ CompositeBehavior.prototype.isCompositeBehavior = true;
@@ -226,5 +226,15 @@ export class ParallelBehavior extends CompositeBehavior {
226
226
  }
227
227
  }
228
228
 
229
+ /**
230
+ * @readonly
231
+ * @type {boolean}
232
+ */
233
+ ParallelBehavior.prototype.isParallelBehavior = true;
234
+
235
+ /**
236
+ * @readonly
237
+ * @type {string}
238
+ */
229
239
  ParallelBehavior.typeName = "ParallelBehavior";
230
240
 
@@ -1,9 +1,6 @@
1
1
  import { Behavior } from "../Behavior.js";
2
2
  import { CompositeBehavior } from "./CompositeBehavior.js";
3
3
  import { BehaviorStatus } from "../BehaviorStatus.js";
4
- import {
5
- ObjectBasedClassSerializationAdapter
6
- } from "../../../ecs/storage/binary/object/ObjectBasedClassSerializationAdapter.js";
7
4
  import { assert } from "../../../../core/assert.js";
8
5
 
9
6
  export class SequenceBehavior extends CompositeBehavior {
@@ -115,52 +112,11 @@ export class SequenceBehavior extends CompositeBehavior {
115
112
  }
116
113
  }
117
114
 
118
- SequenceBehavior.typeName = "SequenceBehavior";
119
-
120
- export class SequenceBehaviorSerializationAdapter extends ObjectBasedClassSerializationAdapter {
121
- constructor() {
122
- super();
123
-
124
- this.klass = SequenceBehavior;
125
- this.version = 0;
126
- }
127
-
128
- /**
129
- *
130
- * @param {BinaryBuffer} buffer
131
- * @param {SequenceBehavior} value
132
- */
133
- serialize(buffer, value) {
134
- const children = value.getChildren();
135
-
136
- const n = children.length;
137
- buffer.writeUintVar(n);
138
-
139
- for (let i = 0; i < n; i++) {
140
- const behavior = children[i];
115
+ /**
116
+ * @readonly
117
+ * @type {boolean}
118
+ */
119
+ SequenceBehavior.prototype.isSequenceBehavior = true;
141
120
 
142
- this.objectAdapter.serialize(buffer, behavior);
143
- }
144
- }
145
-
146
- /**
147
- *
148
- * @param {BinaryBuffer} buffer
149
- * @param {SequenceBehavior} value
150
- */
151
- deserialize(buffer, value) {
152
- const n = buffer.readUintVar();
153
-
154
- value.clearChildren();
155
-
156
- for (let i = 0; i < n; i++) {
157
- /**
158
- *
159
- * @type {Behavior}
160
- */
161
- const behavior = this.objectAdapter.deserialize(buffer);
121
+ SequenceBehavior.typeName = "SequenceBehavior";
162
122
 
163
- value.addChild(behavior);
164
- }
165
- }
166
- }
@@ -0,0 +1,52 @@
1
+ import {
2
+ ObjectBasedClassSerializationAdapter
3
+ } from "../../../ecs/storage/binary/object/ObjectBasedClassSerializationAdapter.js";
4
+ import { SequenceBehavior } from "./SequenceBehavior.js";
5
+
6
+ export class SequenceBehaviorSerializationAdapter extends ObjectBasedClassSerializationAdapter {
7
+ constructor() {
8
+ super();
9
+
10
+ this.klass = SequenceBehavior;
11
+ this.version = 0;
12
+ }
13
+
14
+ /**
15
+ *
16
+ * @param {BinaryBuffer} buffer
17
+ * @param {SequenceBehavior} value
18
+ */
19
+ serialize(buffer, value) {
20
+ const children = value.getChildren();
21
+
22
+ const n = children.length;
23
+ buffer.writeUintVar(n);
24
+
25
+ for (let i = 0; i < n; i++) {
26
+ const behavior = children[i];
27
+
28
+ this.objectAdapter.serialize(buffer, behavior);
29
+ }
30
+ }
31
+
32
+ /**
33
+ *
34
+ * @param {BinaryBuffer} buffer
35
+ * @param {SequenceBehavior} value
36
+ */
37
+ deserialize(buffer, value) {
38
+ const n = buffer.readUintVar();
39
+
40
+ value.clearChildren();
41
+
42
+ for (let i = 0; i < n; i++) {
43
+ /**
44
+ *
45
+ * @type {Behavior}
46
+ */
47
+ const behavior = this.objectAdapter.deserialize(buffer);
48
+
49
+ value.addChild(behavior);
50
+ }
51
+ }
52
+ }
@@ -18,7 +18,7 @@ export class Blackboard extends AbstractBlackboard {
18
18
 
19
19
  /**
20
20
  *
21
- * @type {Object<string,BlackboardValue>}
21
+ * @type {Object<BlackboardValue>}
22
22
  */
23
23
  this.data = {};
24
24
 
@@ -76,7 +76,16 @@ export class Blackboard extends AbstractBlackboard {
76
76
  return this.proxy;
77
77
  }
78
78
 
79
+ /**
80
+ *
81
+ * @param {string} name
82
+ * @param {DataType} type
83
+ * @return {boolean} true only if entry exists and matches type, or if supplied parameter type is set to "Any"
84
+ */
79
85
  contains(name, type) {
86
+ assert.isString(name, 'name');
87
+ assert.enum(type, DataType, 'type');
88
+
80
89
  const datum = this.data[name];
81
90
 
82
91
  if (datum === undefined) {
@@ -115,7 +124,7 @@ export class Blackboard extends AbstractBlackboard {
115
124
  * @param {RegExp} pattern
116
125
  */
117
126
  traverseWithPattern(pattern, visitor) {
118
- assert.notEqual(pattern, undefined, 'pattern is undefined');
127
+ assert.defined(pattern, 'pattern');
119
128
  assert.ok(pattern instanceof RegExp, 'pattern is not a RegExp');
120
129
 
121
130
  this.traverse(function (name, value, type) {
@@ -133,33 +142,36 @@ export class Blackboard extends AbstractBlackboard {
133
142
  * @returns {T}
134
143
  */
135
144
  acquire(name, type, initialValue) {
136
- assert.typeOf(name, 'string', 'name');
145
+ assert.isString(name, 'name');
146
+
147
+ let datum;
137
148
 
138
149
  if (this.data.hasOwnProperty(name)) {
139
150
  // property exists
140
- const datum = this.data[name];
151
+ datum = this.data[name];
141
152
 
142
153
  if (type !== DataType.Any && datum.type !== type) {
143
154
  throw new TypeError(`Value '${name}' exists, but is type(='${datum.type}'), expected type '${type}'`);
144
155
  }
145
156
 
146
- datum.referenceCount++;
147
-
148
- return datum.value;
149
157
  } else {
150
158
  //doesn't exist - create it
151
- const blackboardValue = new BlackboardValue(type);
159
+ datum = new BlackboardValue(type);
160
+
161
+ const concrete_value = datum.value;
152
162
 
153
163
  if (initialValue !== undefined) {
154
- blackboardValue.value.set(initialValue);
164
+ concrete_value.set(initialValue);
155
165
  }
156
166
 
157
- this.data[name] = blackboardValue;
158
-
159
- this.on.added.send4(name, blackboardValue.value.getValue(), type, this);
167
+ this.data[name] = datum;
160
168
 
161
- return blackboardValue.value;
169
+ this.on.added.send4(name, concrete_value.getValue(), type, this);
162
170
  }
171
+
172
+ datum.referenceCount++;
173
+
174
+ return datum.value;
163
175
  }
164
176
 
165
177
  /**
@@ -230,7 +242,7 @@ export class Blackboard extends AbstractBlackboard {
230
242
  if (value_type === 'number') {
231
243
  this.acquireNumber(propName, value).set(value);
232
244
  } else if (value_type === 'boolean') {
233
- this.acquireNumber(propName, value).set(value);
245
+ this.acquireBoolean(propName, value).set(value);
234
246
  } else if (value_type === 'string') {
235
247
  this.acquireString(propName, value).set(value);
236
248
  }
@@ -0,0 +1,62 @@
1
+ import { Blackboard } from "./Blackboard.js";
2
+ import { DataType } from "../../../core/parser/simple/DataType.js";
3
+
4
+ test('"contains" method', () => {
5
+ const bb = new Blackboard();
6
+
7
+ expect(bb.contains('a', DataType.Any)).toBe(false);
8
+
9
+ bb.acquireNumber('a', 0);
10
+
11
+ expect(bb.contains('a', DataType.Number)).toBe(true);
12
+ expect(bb.contains('a', DataType.Any)).toBe(true);
13
+ expect(bb.contains('a', DataType.Boolean)).toBe(false);
14
+
15
+ expect(bb.contains('b', DataType.Number)).toBe(false);
16
+ });
17
+
18
+
19
+ test("entry is deleted after reset", () => {
20
+ const bb = new Blackboard();
21
+
22
+ bb.acquireNumber('a');
23
+
24
+ bb.reset();
25
+
26
+ expect(bb.contains('a', DataType.Number)).toBe(false);
27
+ });
28
+
29
+ test("entry is copied across when blackboard is copied", () => {
30
+ const a = new Blackboard();
31
+
32
+ a.acquireNumber('test', 7);
33
+
34
+ const b = new Blackboard();
35
+
36
+ b.copy(a);
37
+
38
+ expect(b.contains('test', DataType.Number)).toBe(true);
39
+ expect(b.acquireNumber('test').getValue()).toBe(7);
40
+ });
41
+
42
+ test("to/from JSON serialization consistency", () => {
43
+
44
+ const source = new Blackboard();
45
+
46
+ source.acquireNumber('count', 7.1);
47
+ source.acquireBoolean('flag', true);
48
+ source.acquireString('label', 'hello');
49
+
50
+ const target = new Blackboard();
51
+
52
+ target.fromJSON(source.toJSON());
53
+
54
+ expect(target.contains('count', DataType.Number)).toBe(true);
55
+ expect(target.acquireNumber('count').getValue()).toBe(7.1);
56
+
57
+ expect(target.contains('flag', DataType.Boolean)).toBe(true);
58
+ expect(target.acquireBoolean('flag').getValue()).toBe(true);
59
+
60
+ expect(target.contains('label', DataType.String)).toBe(true);
61
+ expect(target.acquireString('label').getValue()).toBe('hello');
62
+ });
@@ -1,4 +1,4 @@
1
- import { Blackboard} from "./Blackboard.js";
1
+ import { Blackboard } from "./Blackboard.js";
2
2
  import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
3
3
  import { BlackboardSerializationAdapter } from "./BlackboardSerializationAdapter.js";
4
4
 
@@ -0,0 +1,7 @@
1
+ Monte-Carlo Tree Search simulation package
2
+
3
+ Intended for development of AI features
4
+
5
+
6
+ References:
7
+ * https://en.wikipedia.org/wiki/Monte_Carlo_tree_search
@@ -2,7 +2,8 @@ import { seededRandom } from "../../../core/math/random/seededRandom.js";
2
2
  import { randomFromArray } from "../../../core/math/random/randomFromArray.js";
3
3
 
4
4
  /**
5
- * Hill climbing optimizer based on random moves
5
+ * Hill climbing optimizer based on testing random moves one move at a time, if overall fitness improves - that move is committed, otherwise it's rejected
6
+ * Very dumb, but quite effective. Requires very little code to write a competent optimizer, especially when state space is relatively small
6
7
  * @template S
7
8
  * @class
8
9
  */
@@ -12,10 +12,10 @@ export class ResourceAllocationBid {
12
12
  * @param {number} value
13
13
  */
14
14
  constructor(allocation, value) {
15
- assert.notEqual(allocation, undefined, 'allocation is undefined');
16
- assert.notEqual(allocation, null, 'allocation is null');
15
+ assert.defined(allocation, 'allocation');
16
+ assert.notNull(allocation, 'allocation');
17
17
 
18
- assert.typeOf(value, 'number', 'value');
18
+ assert.isNumber(value, 'value');
19
19
 
20
20
  /**
21
21
  *
@@ -1,7 +1,7 @@
1
1
  import { assert } from "../../../core/assert.js";
2
2
 
3
3
  /**
4
- *
4
+ * Sorting comparator
5
5
  * @param {ResourceAllocationBid} a
6
6
  * @param {ResourceAllocationBid} b
7
7
  * @returns {number}
@@ -31,6 +31,8 @@ export class StrategicResourceAllocator {
31
31
  */
32
32
  addTacticalModule(module) {
33
33
  assert.defined(module, 'module');
34
+ assert.notNull(module, 'module');
35
+ assert.equal(module.isTacticalModule, true, 'module.TacticalModule !== true');
34
36
 
35
37
  this.modules.push(module);
36
38
  }
@@ -53,13 +55,13 @@ export class StrategicResourceAllocator {
53
55
  const moduleResults = this.modules.map(m => {
54
56
  const promise = m.collectBids(resources);
55
57
 
56
- assert.notEqual(promise, undefined, 'promise is undefined');
57
- assert.notEqual(promise, null, 'promise is null');
58
- assert.typeOf(promise.then, 'function', "promise.then");
58
+ assert.defined(promise, 'promise');
59
+ assert.notNull(promise, 'promise');
60
+ assert.isFunction(promise.then, "promise.then");
59
61
 
60
62
  promise.then(moduleBids => {
61
63
 
62
- assert.ok(Array.isArray(moduleBids), `moduleBids expected to be an array, was something else (typeof='${typeof moduleBids}')`);
64
+ assert.isArray(moduleBids, 'moduleBids');
63
65
 
64
66
  moduleBids.forEach(b => bids.set(b, m));
65
67