@civ-clone/core-strategy 0.1.0

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 (47) hide show
  1. package/.deepsource.toml +21 -0
  2. package/.gitattributes +2 -0
  3. package/.github/workflows/build-on-push.yml +15 -0
  4. package/.prettierrc +4 -0
  5. package/LICENSE +21 -0
  6. package/README.md +123 -0
  7. package/Routine.d.ts +15 -0
  8. package/Routine.js +38 -0
  9. package/Routine.js.map +1 -0
  10. package/Routine.ts +39 -0
  11. package/Rules/Priority.d.ts +6 -0
  12. package/Rules/Priority.js +9 -0
  13. package/Rules/Priority.js.map +1 -0
  14. package/Rules/Priority.ts +8 -0
  15. package/Strategy.d.ts +18 -0
  16. package/Strategy.js +32 -0
  17. package/Strategy.js.map +1 -0
  18. package/Strategy.ts +41 -0
  19. package/StrategyNote.d.ts +17 -0
  20. package/StrategyNote.js +42 -0
  21. package/StrategyNote.js.map +1 -0
  22. package/StrategyNote.ts +43 -0
  23. package/StrategyNoteRegistry.d.ts +21 -0
  24. package/StrategyNoteRegistry.js +40 -0
  25. package/StrategyNoteRegistry.js.map +1 -0
  26. package/StrategyNoteRegistry.ts +62 -0
  27. package/StrategyRegistry.d.ts +13 -0
  28. package/StrategyRegistry.js +25 -0
  29. package/StrategyRegistry.js.map +1 -0
  30. package/StrategyRegistry.ts +28 -0
  31. package/package.json +40 -0
  32. package/tests/Routine.test.ts +93 -0
  33. package/tests/Strategy.test.ts +80 -0
  34. package/tests/StrategyRegistry.test.ts +77 -0
  35. package/tests/lib/PlayerActions.d.ts +2 -0
  36. package/tests/lib/PlayerActions.js +8 -0
  37. package/tests/lib/PlayerActions.js.map +1 -0
  38. package/tests/lib/PlayerActions.ts +3 -0
  39. package/tests/lib/Routines.d.ts +9 -0
  40. package/tests/lib/Routines.js +23 -0
  41. package/tests/lib/Routines.js.map +1 -0
  42. package/tests/lib/Routines.ts +17 -0
  43. package/tests/lib/Traits.d.ts +12 -0
  44. package/tests/lib/Traits.js +26 -0
  45. package/tests/lib/Traits.js.map +1 -0
  46. package/tests/lib/Traits.ts +22 -0
  47. package/tsconfig.json +16 -0
@@ -0,0 +1,21 @@
1
+ version = 1
2
+
3
+ test_patterns = ["test/**/*.test.ts"]
4
+
5
+ exclude_patterns = ["**/*.js"]
6
+
7
+ [[analyzers]]
8
+ name = "javascript"
9
+ enabled = true
10
+
11
+ [analyzers.meta]
12
+ dialect = "typescript"
13
+ environment = [
14
+ "nodejs",
15
+ "mocha"
16
+ ]
17
+
18
+ [[analyzers]]
19
+ name = "test-coverage"
20
+ enabled = true
21
+
package/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ **/*.js linguist-generated=true
2
+
@@ -0,0 +1,15 @@
1
+ name: Build and test application on push to remote
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v1
10
+ - uses: actions/setup-node@v1
11
+ with:
12
+ node-version: 12
13
+ - run: npm install
14
+ - run: npm run prettier:check
15
+ - run: npm test
package/.prettierrc ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true
4
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Dom Hastings
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # core-strategy
2
+
3
+ A framework for building modular AI. The idea being that `Strategy`s are built of many `Routine`s for specific
4
+ `PlayerAction`s and it should be easy enough to register new `Strategy`s to handle newly added `PlayerAction`s.
5
+
6
+ The consumer `AIClient`, `SimpleAIStrategyClient`, is available at
7
+ [civ-clone/simple-ai-strategy-client](https://github.com/civ-clone/simple-ai-strategy-client).
8
+
9
+ Another aim for this set of classes is to be able to automate simple tasks (explore, improve terrain, go-to, etc.).
10
+
11
+ Lastly, there's the possibility for primitive Barbarian behaviour, without having a "ghost" player (like Civ1).
12
+
13
+ ## Current state
14
+
15
+ ```ts
16
+ import Strategy from '@civ-clone/core-strategy/Strategy';
17
+
18
+ // Export your base `Strategy`:
19
+ export class MyStrategy extends Strategy {
20
+ constructor(
21
+ dependencyForRoutine: SpecificThing = defaultInstanceOfSpecificThing
22
+ ) {
23
+ super(
24
+ new MyRoutine(dependencyForRoutine),
25
+ new AnotherRoutine(dependencyForRoutine)
26
+ );
27
+ }
28
+ }
29
+
30
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
31
+ import Routine from '@civ-clone/core-strategy/Strategy';
32
+ import { instance as strategyNoteInstance } from '@civ-clone/core-strategy/StrategyNoteRegistry';
33
+
34
+ // which is comprised of `Routine`s like the following:
35
+ export class MyRoutine extends Routine {
36
+ // Inject dependencies (e.g. `Registry`s) into the `constructor` as usual
37
+
38
+ attempt(action: PlayerAction<SpecificItemClass>): boolean {
39
+ // Check if your `Routine` can handle this type of action first, fail early as lots of routines will be even
40
+ // more expensive to check otherwise.
41
+ if (!(action instanceof MyHandleableAction)) {
42
+ return false;
43
+ }
44
+
45
+ // In here you can use any existing code, for example you could trigger existing `Action`s for `Unit`s...
46
+
47
+ // If you need to share data across many `Strategy`s or `Routine`s you can use the `StrategyNoteRegistry` (with
48
+ // an optional custom `generateKey` method to ensure you always use the expected key).
49
+ const existingNote = strategyNoteRegistryInstance.getByKey(
50
+ myCustomKeyGenerator(action.value())
51
+ );
52
+
53
+ if (!existingNote) {
54
+ return false;
55
+ }
56
+
57
+ const data = existingNote.value(),
58
+ newAction = new DoSomething(data.x(), data.y(), data.thing());
59
+
60
+ newAction.perform(action);
61
+
62
+ // When you have handled the action, ensure you return `true` to prevent any more actions from triggering.
63
+ return true;
64
+ }
65
+ }
66
+
67
+ // The core `generateKey` method exists in `StrategyNote`, but having a more specific function associated to your
68
+ // `Strategy` can help ensure you are passing the expected entities and get consistent keys.
69
+ import { generateKey } from '@civ-clone/core-strategy/StrategyNote';
70
+
71
+ export const myCustomKeyGenerator = (item: SpecificItemClass) =>
72
+ generateKey(item, MyRoutine.name);
73
+
74
+ // To control the priority of your `Routine`s you need to use `Priority` `Rule`s and you can even take the `Leader`s
75
+ // `Trait`s into consideration if you wish:
76
+ import { High, Normal } from '@civ-clone/core-rule/Priorities';
77
+ import {
78
+ TraitRegistry,
79
+ instance as traitRegistryInstance,
80
+ } from '@civ-clone/core-civilization/TraitRegistry';
81
+ import Expansionist from '@civ-clone/base-leader-trait-development/Development/Expansionist';
82
+ import Player from '@civ-clone/core-player/Player';
83
+ import Priority from '@civ-clone/core-strategy/Rules/Priority';
84
+ import Routine from '@civ-clone/core-strategy/Strategy';
85
+ import Trait from '@civ-clone/core-civilization/Trait';
86
+
87
+ export const getRules = (
88
+ traitRegistry: TraitRegistry = traitRegistryInstance
89
+ ): Priority[] => [
90
+ new Priority(
91
+ new Criterion(
92
+ (player: Player, routine: Routine) => routine instanceof MyRoutine
93
+ ),
94
+ new Effect((player: Player) => {
95
+ const civilization = player.civilization(),
96
+ leader = civilization.leader();
97
+
98
+ if (
99
+ leader &&
100
+ traitRegistry.some(
101
+ (trait: Trait) =>
102
+ leader instanceof trait.leader() && trait instanceof Expansionist
103
+ )
104
+ ) {
105
+ // Could be any arbitrary `Priority` (from `core-rule`) to give more fine-grained control.
106
+ return new High();
107
+ }
108
+
109
+ return new Normal();
110
+ })
111
+ ),
112
+ ];
113
+
114
+ // In the main entrypoint make sure you register the `Rule`s and the `Strategy`:
115
+ import { instance as ruleRegistryInstance } from '@civ-clone/core-rule/RuleRegistry';
116
+ import { instance as strategyRegistryInstance } from '@civ-clone/core-strategy/StrategyRegistry';
117
+
118
+ ruleRegistryInstance.register(...getRules());
119
+ strategyRegistryInstance.register(new MyStrategy());
120
+
121
+ // `StrategyNote`s can be written from anywhere, for example when first making contact with another `Player` or when a
122
+ // `Tile` is discovered, which can then be acted upon.
123
+ ```
package/Routine.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { RuleRegistry } from '@civ-clone/core-rule/RuleRegistry';
2
+ import Player from '@civ-clone/core-player/Player';
3
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
4
+ import Priority from '@civ-clone/core-rule/Priority';
5
+ export interface IRoutine {
6
+ attempt(action: PlayerAction): boolean;
7
+ priority(player: Player): Priority;
8
+ }
9
+ export declare class Routine implements IRoutine {
10
+ #private;
11
+ constructor(ruleRegistry?: RuleRegistry);
12
+ attempt(action: PlayerAction): boolean;
13
+ priority(player: Player): Priority;
14
+ }
15
+ export default Routine;
package/Routine.js ADDED
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _Routine_ruleRegistry;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.Routine = void 0;
16
+ const RuleRegistry_1 = require("@civ-clone/core-rule/RuleRegistry");
17
+ const Priority_1 = require("@civ-clone/core-rule/Priority");
18
+ const Priority_2 = require("./Rules/Priority");
19
+ class Routine {
20
+ constructor(ruleRegistry = RuleRegistry_1.instance) {
21
+ _Routine_ruleRegistry.set(this, void 0);
22
+ __classPrivateFieldSet(this, _Routine_ruleRegistry, ruleRegistry, "f");
23
+ }
24
+ attempt(action) {
25
+ throw new TypeError('`attempt` must be overridden.');
26
+ }
27
+ priority(player) {
28
+ return new Priority_1.default(
29
+ // This takes the highest priority (lowest value) from all the applicable `PriorityRule`s
30
+ Math.min(...__classPrivateFieldGet(this, _Routine_ruleRegistry, "f")
31
+ .process(Priority_2.default, player, this)
32
+ .map((priority) => priority.value()), Infinity));
33
+ }
34
+ }
35
+ exports.Routine = Routine;
36
+ _Routine_ruleRegistry = new WeakMap();
37
+ exports.default = Routine;
38
+ //# sourceMappingURL=Routine.js.map
package/Routine.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Routine.js","sourceRoot":"","sources":["Routine.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,oEAG2C;AAG3C,4DAAqD;AACrD,+CAA4C;AAO5C,MAAa,OAAO;IAGlB,YAAY,eAA6B,uBAAoB;QAF7D,wCAA4B;QAG1B,uBAAA,IAAI,yBAAiB,YAAY,MAAA,CAAC;IACpC,CAAC;IAED,OAAO,CAAC,MAAoB;QAC1B,MAAM,IAAI,SAAS,CAAC,+BAA+B,CAAC,CAAC;IACvD,CAAC;IAED,QAAQ,CAAC,MAAc;QACrB,OAAO,IAAI,kBAAQ;QACjB,yFAAyF;QACzF,IAAI,CAAC,GAAG,CACN,GAAG,uBAAA,IAAI,6BAAc;aAClB,OAAO,CAAC,kBAAY,EAAE,MAAM,EAAE,IAAI,CAAC;aACnC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,EACtC,QAAQ,CACT,CACF,CAAC;IACJ,CAAC;CACF;AAtBD,0BAsBC;;AAED,kBAAe,OAAO,CAAC"}
package/Routine.ts ADDED
@@ -0,0 +1,39 @@
1
+ import {
2
+ RuleRegistry,
3
+ instance as ruleRegistryInstance,
4
+ } from '@civ-clone/core-rule/RuleRegistry';
5
+ import Player from '@civ-clone/core-player/Player';
6
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
7
+ import Priority from '@civ-clone/core-rule/Priority';
8
+ import PriorityRule from './Rules/Priority';
9
+
10
+ export interface IRoutine {
11
+ attempt(action: PlayerAction): boolean;
12
+ priority(player: Player): Priority;
13
+ }
14
+
15
+ export class Routine implements IRoutine {
16
+ #ruleRegistry: RuleRegistry;
17
+
18
+ constructor(ruleRegistry: RuleRegistry = ruleRegistryInstance) {
19
+ this.#ruleRegistry = ruleRegistry;
20
+ }
21
+
22
+ attempt(action: PlayerAction): boolean {
23
+ throw new TypeError('`attempt` must be overridden.');
24
+ }
25
+
26
+ priority(player: Player): Priority {
27
+ return new Priority(
28
+ // This takes the highest priority (lowest value) from all the applicable `PriorityRule`s
29
+ Math.min(
30
+ ...this.#ruleRegistry
31
+ .process(PriorityRule, player, this)
32
+ .map((priority) => priority.value()),
33
+ Infinity
34
+ )
35
+ );
36
+ }
37
+ }
38
+
39
+ export default Routine;
@@ -0,0 +1,6 @@
1
+ import Player from '@civ-clone/core-player/Player';
2
+ import PriorityValue from '@civ-clone/core-rule/Priority';
3
+ import Routine from '../Routine';
4
+ import Rule from '@civ-clone/core-rule/Rule';
5
+ export declare class Priority extends Rule<[Player, Routine], PriorityValue> {}
6
+ export default Priority;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Priority = void 0;
4
+ const Rule_1 = require("@civ-clone/core-rule/Rule");
5
+ class Priority extends Rule_1.default {
6
+ }
7
+ exports.Priority = Priority;
8
+ exports.default = Priority;
9
+ //# sourceMappingURL=Priority.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Priority.js","sourceRoot":"","sources":["Priority.ts"],"names":[],"mappings":";;;AAGA,oDAA6C;AAE7C,MAAa,QAAS,SAAQ,cAAsC;CAAG;AAAvE,4BAAuE;AAEvE,kBAAe,QAAQ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import Player from '@civ-clone/core-player/Player';
2
+ import PriorityValue from '@civ-clone/core-rule/Priority';
3
+ import Routine from '../Routine';
4
+ import Rule from '@civ-clone/core-rule/Rule';
5
+
6
+ export class Priority extends Rule<[Player, Routine], PriorityValue> {}
7
+
8
+ export default Priority;
package/Strategy.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import Player from '@civ-clone/core-player/Player';
2
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
3
+ import Priority from '@civ-clone/core-rule/Priority';
4
+ import Routine from './Routine';
5
+ export interface IStrategy {
6
+ attempt(action: PlayerAction): boolean;
7
+ priority(player: Player): Priority;
8
+ }
9
+ export declare class Strategy implements IStrategy {
10
+ #private;
11
+ constructor(...items: Routine[]);
12
+ /**
13
+ * Checks to see if the `action` can be handled, returns `true` if it is, `false` otherwise.
14
+ */
15
+ attempt(action: PlayerAction): boolean;
16
+ priority(player: Player): Priority;
17
+ }
18
+ export default Strategy;
package/Strategy.js ADDED
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
+ };
7
+ var _Strategy_routines;
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.Strategy = void 0;
10
+ const Priority_1 = require("@civ-clone/core-rule/Priority");
11
+ class Strategy {
12
+ constructor(...items) {
13
+ _Strategy_routines.set(this, []);
14
+ items.forEach((item) => __classPrivateFieldGet(this, _Strategy_routines, "f").push(item));
15
+ }
16
+ /**
17
+ * Checks to see if the `action` can be handled, returns `true` if it is, `false` otherwise.
18
+ */
19
+ attempt(action) {
20
+ return __classPrivateFieldGet(this, _Strategy_routines, "f")
21
+ .sort((a, b) => a.priority(action.player()).value() -
22
+ b.priority(action.player()).value())
23
+ .some((routine) => routine.attempt(action));
24
+ }
25
+ priority(player) {
26
+ return new Priority_1.default(Math.min(...__classPrivateFieldGet(this, _Strategy_routines, "f").map((routine) => routine.priority(player).value()), Infinity));
27
+ }
28
+ }
29
+ exports.Strategy = Strategy;
30
+ _Strategy_routines = new WeakMap();
31
+ exports.default = Strategy;
32
+ //# sourceMappingURL=Strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Strategy.js","sourceRoot":"","sources":["Strategy.ts"],"names":[],"mappings":";;;;;;;;;AAEA,4DAAqD;AAQrD,MAAa,QAAQ;IAGnB,YAAY,GAAG,KAAgB;QAF/B,6BAAuB,EAAE,EAAC;QAGxB,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,uBAAA,IAAI,0BAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,CAAC;IAED;;OAEG;IACH,OAAO,CAAC,MAAoB;QAC1B,OAAO,uBAAA,IAAI,0BAAU;aAClB,IAAI,CACH,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE;YACnC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,CACtC;aACA,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,QAAQ,CAAC,MAAc;QACrB,OAAO,IAAI,kBAAQ,CACjB,IAAI,CAAC,GAAG,CACN,GAAG,uBAAA,IAAI,0BAAU,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,EACpE,QAAQ,CACT,CACF,CAAC;IACJ,CAAC;CACF;AA5BD,4BA4BC;;AAED,kBAAe,QAAQ,CAAC"}
package/Strategy.ts ADDED
@@ -0,0 +1,41 @@
1
+ import Player from '@civ-clone/core-player/Player';
2
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
3
+ import Priority from '@civ-clone/core-rule/Priority';
4
+ import Routine from './Routine';
5
+
6
+ export interface IStrategy {
7
+ attempt(action: PlayerAction): boolean;
8
+ priority(player: Player): Priority;
9
+ }
10
+
11
+ export class Strategy implements IStrategy {
12
+ #routines: Routine[] = [];
13
+
14
+ constructor(...items: Routine[]) {
15
+ items.forEach((item) => this.#routines.push(item));
16
+ }
17
+
18
+ /**
19
+ * Checks to see if the `action` can be handled, returns `true` if it is, `false` otherwise.
20
+ */
21
+ attempt(action: PlayerAction): boolean {
22
+ return this.#routines
23
+ .sort(
24
+ (a, b) =>
25
+ a.priority(action.player()).value() -
26
+ b.priority(action.player()).value()
27
+ )
28
+ .some((routine) => routine.attempt(action));
29
+ }
30
+
31
+ priority(player: Player): Priority {
32
+ return new Priority(
33
+ Math.min(
34
+ ...this.#routines.map((routine) => routine.priority(player).value()),
35
+ Infinity
36
+ )
37
+ );
38
+ }
39
+ }
40
+
41
+ export default Strategy;
@@ -0,0 +1,17 @@
1
+ declare type IDataObject = {
2
+ id(): string;
3
+ };
4
+ export interface IStrategyNote<Value = any> {
5
+ key(): string;
6
+ value(): Value;
7
+ }
8
+ export declare class StrategyNote<Value = any> implements IStrategyNote<Value> {
9
+ #private;
10
+ constructor(key: string, value: Value);
11
+ key(): string;
12
+ value(): Value;
13
+ }
14
+ export declare const generateKey: (
15
+ ...items: (IDataObject | string)[]
16
+ ) => string;
17
+ export default StrategyNote;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _StrategyNote_key, _StrategyNote_value;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.generateKey = exports.StrategyNote = void 0;
16
+ const DataObject_1 = require("@civ-clone/core-data-object/DataObject");
17
+ class StrategyNote {
18
+ constructor(key, value) {
19
+ _StrategyNote_key.set(this, void 0);
20
+ _StrategyNote_value.set(this, void 0);
21
+ __classPrivateFieldSet(this, _StrategyNote_key, key, "f");
22
+ __classPrivateFieldSet(this, _StrategyNote_value, value, "f");
23
+ }
24
+ key() {
25
+ return __classPrivateFieldGet(this, _StrategyNote_key, "f");
26
+ }
27
+ value() {
28
+ return __classPrivateFieldGet(this, _StrategyNote_value, "f");
29
+ }
30
+ }
31
+ exports.StrategyNote = StrategyNote;
32
+ _StrategyNote_key = new WeakMap(), _StrategyNote_value = new WeakMap();
33
+ const generateKey = (...items) => items
34
+ .map((item) => item instanceof DataObject_1.default
35
+ ? item.id()
36
+ : typeof item === 'string'
37
+ ? item
38
+ : item.toString())
39
+ .join('-');
40
+ exports.generateKey = generateKey;
41
+ exports.default = StrategyNote;
42
+ //# sourceMappingURL=StrategyNote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StrategyNote.js","sourceRoot":"","sources":["StrategyNote.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,uEAAgE;AAWhE,MAAa,YAAY;IAIvB,YAAY,GAAW,EAAE,KAAY;QAHrC,oCAAa;QACb,sCAAc;QAGZ,uBAAA,IAAI,qBAAQ,GAAG,MAAA,CAAC;QAChB,uBAAA,IAAI,uBAAU,KAAK,MAAA,CAAC;IACtB,CAAC;IAEM,GAAG;QACR,OAAO,uBAAA,IAAI,yBAAK,CAAC;IACnB,CAAC;IAEM,KAAK;QACV,OAAO,uBAAA,IAAI,2BAAO,CAAC;IACrB,CAAC;CACF;AAhBD,oCAgBC;;AAEM,MAAM,WAAW,GAAmD,CACzE,GAAG,KAA+B,EAClC,EAAE,CACF,KAAK;KACF,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,IAAI,YAAY,oBAAU;IACxB,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE;IACX,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ;QAC1B,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CACpB;KACA,IAAI,CAAC,GAAG,CAAC,CAAC;AAXF,QAAA,WAAW,eAWT;AAEf,kBAAe,YAAY,CAAC"}
@@ -0,0 +1,43 @@
1
+ import DataObject from '@civ-clone/core-data-object/DataObject';
2
+
3
+ type IDataObject = {
4
+ id(): string;
5
+ };
6
+
7
+ export interface IStrategyNote<Value = any> {
8
+ key(): string;
9
+ value(): Value;
10
+ }
11
+
12
+ export class StrategyNote<Value = any> implements IStrategyNote<Value> {
13
+ #key: string;
14
+ #value: Value;
15
+
16
+ constructor(key: string, value: Value) {
17
+ this.#key = key;
18
+ this.#value = value;
19
+ }
20
+
21
+ public key(): string {
22
+ return this.#key;
23
+ }
24
+
25
+ public value(): Value {
26
+ return this.#value;
27
+ }
28
+ }
29
+
30
+ export const generateKey: (...items: (IDataObject | string)[]) => string = (
31
+ ...items: (IDataObject | string)[]
32
+ ) =>
33
+ items
34
+ .map((item) =>
35
+ item instanceof DataObject
36
+ ? item.id()
37
+ : typeof item === 'string'
38
+ ? item
39
+ : item.toString()
40
+ )
41
+ .join('-');
42
+
43
+ export default StrategyNote;
@@ -0,0 +1,21 @@
1
+ import {
2
+ EntityRegistry,
3
+ IEntityRegistry,
4
+ } from '@civ-clone/core-registry/EntityRegistry';
5
+ import StrategyNote from './StrategyNote';
6
+ export interface IStrategyNoteRegistry extends IEntityRegistry<StrategyNote> {
7
+ getByKey<Value = any>(key: string): StrategyNote<Value> | undefined;
8
+ getOrCreateByKey<Value>(key: string, value: Value): StrategyNote<Value>;
9
+ replace(...entities: StrategyNote[]): void;
10
+ }
11
+ export declare class StrategyNoteRegistry
12
+ extends EntityRegistry<StrategyNote>
13
+ implements IStrategyNoteRegistry
14
+ {
15
+ getByKey<Value = any>(key: string): StrategyNote<Value> | undefined;
16
+ getOrCreateByKey<Value>(key: string, value: Value): StrategyNote<Value>;
17
+ register(...entities: StrategyNote[]): void;
18
+ replace(...entities: StrategyNote[]): void;
19
+ }
20
+ export declare const instance: StrategyNoteRegistry;
21
+ export default StrategyNoteRegistry;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instance = exports.StrategyNoteRegistry = void 0;
4
+ const EntityRegistry_1 = require("@civ-clone/core-registry/EntityRegistry");
5
+ const StrategyNote_1 = require("./StrategyNote");
6
+ class StrategyNoteRegistry extends EntityRegistry_1.EntityRegistry {
7
+ getByKey(key) {
8
+ return this.getBy('key', key)[0];
9
+ }
10
+ getOrCreateByKey(key, value) {
11
+ const existing = this.getByKey(key);
12
+ if (existing) {
13
+ return existing;
14
+ }
15
+ const note = new StrategyNote_1.default(key, value);
16
+ this.register(note);
17
+ return note;
18
+ }
19
+ register(...entities) {
20
+ entities.forEach((entity) => {
21
+ if (this.getByKey(entity.key())) {
22
+ throw new TypeError(`Entity with key '${entity.key()}' already exists.`);
23
+ }
24
+ super.register(entity);
25
+ });
26
+ }
27
+ replace(...entities) {
28
+ entities.forEach((entity) => {
29
+ const existing = this.getByKey(entity.key());
30
+ if (existing) {
31
+ this.unregister(existing);
32
+ }
33
+ this.register(entity);
34
+ });
35
+ }
36
+ }
37
+ exports.StrategyNoteRegistry = StrategyNoteRegistry;
38
+ exports.instance = new StrategyNoteRegistry();
39
+ exports.default = StrategyNoteRegistry;
40
+ //# sourceMappingURL=StrategyNoteRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StrategyNoteRegistry.js","sourceRoot":"","sources":["StrategyNoteRegistry.ts"],"names":[],"mappings":";;;AAAA,4EAGiD;AACjD,iDAA0C;AAQ1C,MAAa,oBACX,SAAQ,+BAA4B;IAGpC,QAAQ,CAAc,GAAW;QAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IAED,gBAAgB,CAAQ,GAAW,EAAE,KAAY;QAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAEpC,IAAI,QAAQ,EAAE;YACZ,OAAO,QAAQ,CAAC;SACjB;QAED,MAAM,IAAI,GAAG,IAAI,sBAAY,CAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;QAEjD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ,CAAC,GAAG,QAAwB;QAClC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;YAC1B,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE;gBAC/B,MAAM,IAAI,SAAS,CACjB,oBAAoB,MAAM,CAAC,GAAG,EAAE,mBAAmB,CACpD,CAAC;aACH;YAED,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,GAAG,QAAwB;QACjC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;YAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;YAE7C,IAAI,QAAQ,EAAE;gBACZ,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;aAC3B;YAED,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AA7CD,oDA6CC;AAEY,QAAA,QAAQ,GAAyB,IAAI,oBAAoB,EAAE,CAAC;AAEzE,kBAAe,oBAAoB,CAAC"}
@@ -0,0 +1,62 @@
1
+ import {
2
+ EntityRegistry,
3
+ IEntityRegistry,
4
+ } from '@civ-clone/core-registry/EntityRegistry';
5
+ import StrategyNote from './StrategyNote';
6
+
7
+ export interface IStrategyNoteRegistry extends IEntityRegistry<StrategyNote> {
8
+ getByKey<Value = any>(key: string): StrategyNote<Value> | undefined;
9
+ getOrCreateByKey<Value>(key: string, value: Value): StrategyNote<Value>;
10
+ replace(...entities: StrategyNote[]): void;
11
+ }
12
+
13
+ export class StrategyNoteRegistry
14
+ extends EntityRegistry<StrategyNote>
15
+ implements IStrategyNoteRegistry
16
+ {
17
+ getByKey<Value = any>(key: string): StrategyNote<Value> | undefined {
18
+ return this.getBy('key', key)[0];
19
+ }
20
+
21
+ getOrCreateByKey<Value>(key: string, value: Value): StrategyNote<Value> {
22
+ const existing = this.getByKey(key);
23
+
24
+ if (existing) {
25
+ return existing;
26
+ }
27
+
28
+ const note = new StrategyNote<Value>(key, value);
29
+
30
+ this.register(note);
31
+
32
+ return note;
33
+ }
34
+
35
+ register(...entities: StrategyNote[]): void {
36
+ entities.forEach((entity) => {
37
+ if (this.getByKey(entity.key())) {
38
+ throw new TypeError(
39
+ `Entity with key '${entity.key()}' already exists.`
40
+ );
41
+ }
42
+
43
+ super.register(entity);
44
+ });
45
+ }
46
+
47
+ replace(...entities: StrategyNote[]): void {
48
+ entities.forEach((entity) => {
49
+ const existing = this.getByKey(entity.key());
50
+
51
+ if (existing) {
52
+ this.unregister(existing);
53
+ }
54
+
55
+ this.register(entity);
56
+ });
57
+ }
58
+ }
59
+
60
+ export const instance: StrategyNoteRegistry = new StrategyNoteRegistry();
61
+
62
+ export default StrategyNoteRegistry;
@@ -0,0 +1,13 @@
1
+ import EntityRegistry from '@civ-clone/core-registry/EntityRegistry';
2
+ import Strategy from './Strategy';
3
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
4
+ export declare class StrategyRegistry extends EntityRegistry<Strategy> {
5
+ constructor();
6
+ /**
7
+ * Iterates all `Strategy`s ordered first by `Priority`, then by a random number so that alternative strategies are
8
+ * tried.
9
+ */
10
+ attempt(action: PlayerAction): boolean;
11
+ }
12
+ export declare const instance: StrategyRegistry;
13
+ export default StrategyRegistry;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instance = exports.StrategyRegistry = void 0;
4
+ const EntityRegistry_1 = require("@civ-clone/core-registry/EntityRegistry");
5
+ const Strategy_1 = require("./Strategy");
6
+ class StrategyRegistry extends EntityRegistry_1.default {
7
+ constructor() {
8
+ super(Strategy_1.default);
9
+ }
10
+ /**
11
+ * Iterates all `Strategy`s ordered first by `Priority`, then by a random number so that alternative strategies are
12
+ * tried.
13
+ */
14
+ attempt(action) {
15
+ return this.entries()
16
+ .sort((a, b) => a.priority(action.player()).value() -
17
+ b.priority(action.player()).value() ||
18
+ Math.floor(Math.random() * 3 - 1))
19
+ .some((strategy) => strategy.attempt(action));
20
+ }
21
+ }
22
+ exports.StrategyRegistry = StrategyRegistry;
23
+ exports.instance = new StrategyRegistry();
24
+ exports.default = StrategyRegistry;
25
+ //# sourceMappingURL=StrategyRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StrategyRegistry.js","sourceRoot":"","sources":["StrategyRegistry.ts"],"names":[],"mappings":";;;AAAA,4EAAqE;AACrE,yCAAkC;AAGlC,MAAa,gBAAiB,SAAQ,wBAAwB;IAC5D;QACE,KAAK,CAAC,kBAAQ,CAAC,CAAC;IAClB,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,MAAoB;QAC1B,OAAO,IAAI,CAAC,OAAO,EAAE;aAClB,IAAI,CACH,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE;YACjC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE;YACrC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CACpC;aACA,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC;CACF;AAnBD,4CAmBC;AAEY,QAAA,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;AAE/C,kBAAe,gBAAgB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import EntityRegistry from '@civ-clone/core-registry/EntityRegistry';
2
+ import Strategy from './Strategy';
3
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
4
+
5
+ export class StrategyRegistry extends EntityRegistry<Strategy> {
6
+ constructor() {
7
+ super(Strategy);
8
+ }
9
+
10
+ /**
11
+ * Iterates all `Strategy`s ordered first by `Priority`, then by a random number so that alternative strategies are
12
+ * tried.
13
+ */
14
+ attempt(action: PlayerAction): boolean {
15
+ return this.entries()
16
+ .sort(
17
+ (a, b) =>
18
+ a.priority(action.player()).value() -
19
+ b.priority(action.player()).value() ||
20
+ Math.floor(Math.random() * 3 - 1)
21
+ )
22
+ .some((strategy) => strategy.attempt(action));
23
+ }
24
+ }
25
+
26
+ export const instance = new StrategyRegistry();
27
+
28
+ export default StrategyRegistry;
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@civ-clone/core-strategy",
3
+ "version": "0.1.0",
4
+ "repository": "git@github.com:civ-clone/core-strategy.git",
5
+ "keywords": [
6
+ "typescript",
7
+ "civilization"
8
+ ],
9
+ "author": "dom111 <dom111@users.noreply.github.com>",
10
+ "license": "MIT",
11
+ "private": false,
12
+ "scripts": {
13
+ "build": "npm run ts:compile && npm run prettier:format",
14
+ "prettier:check": "prettier --config .prettierrc '**/*.ts'",
15
+ "prettier:format": "prettier --config .prettierrc '**/*.ts' --write",
16
+ "test": "ts-mocha ./tests/*.test.ts",
17
+ "test:coverage": "c8 ts-mocha ./tests/*.test.ts",
18
+ "ts:compile": "tsc --build tsconfig.json"
19
+ },
20
+ "devDependencies": {
21
+ "@types/chai": "^4.0",
22
+ "@types/chai-spies": "^1.0",
23
+ "@types/mocha": "^9.0",
24
+ "@types/node": "^14.0",
25
+ "c8": "^7.0",
26
+ "chai": "^4.0",
27
+ "chai-spies": "^1.0",
28
+ "mocha": "^9.0",
29
+ "prettier": "^2.0",
30
+ "ts-mocha": "^9.0",
31
+ "typescript": "^4.0"
32
+ },
33
+ "dependencies": {
34
+ "@civ-clone/core-civilization": "^0.1.0",
35
+ "@civ-clone/core-data-object": "^0.1.0",
36
+ "@civ-clone/core-player": "^0.1.2",
37
+ "@civ-clone/core-registry": "^0.1.0",
38
+ "@civ-clone/core-rule": "^0.1.1"
39
+ }
40
+ }
@@ -0,0 +1,93 @@
1
+ import { High, Low, Normal } from '@civ-clone/core-rule/Priorities';
2
+ import { RoutineA, RoutineB } from './lib/Routines';
3
+ import { TraitFull, TraitHalf, TraitNone } from './lib/Traits';
4
+ import Civilization from '@civ-clone/core-civilization/Civilization';
5
+ import Criterion from '@civ-clone/core-rule/Criterion';
6
+ import Effect from '@civ-clone/core-rule/Effect';
7
+ import Leader from '@civ-clone/core-civilization/Leader';
8
+ import Player from '@civ-clone/core-player/Player';
9
+ import Priority from '@civ-clone/core-rule/Priority';
10
+ import PriorityRule from '../Rules/Priority';
11
+ import Routine from '../Routine';
12
+ import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
13
+ import Trait from '@civ-clone/core-civilization/Trait';
14
+ import TraitRegistry from '@civ-clone/core-civilization/TraitRegistry';
15
+ import { expect } from 'chai';
16
+
17
+ describe('Routine', () => {
18
+ const testPlayer = new Player();
19
+
20
+ it('should default to `Normal` `Priority`', () => {
21
+ const routine = new Routine();
22
+
23
+ expect(routine.priority(testPlayer).value()).equal(Infinity);
24
+ });
25
+
26
+ it('should be possible to prioritise based on `Priority` `Rule`s', async () => {
27
+ const traitRegistry = new TraitRegistry(),
28
+ ruleRegistry = new RuleRegistry(),
29
+ civilization = new Civilization(),
30
+ traitNone = new TraitNone(Leader),
31
+ traitHalf = new TraitHalf(Leader),
32
+ traitFull = new TraitFull(Leader),
33
+ routine = new RoutineA(ruleRegistry),
34
+ inverseRoutine = new RoutineB(ruleRegistry);
35
+
36
+ ruleRegistry.register(
37
+ ...(
38
+ [
39
+ [RoutineA, TraitFull, High],
40
+ [RoutineA, TraitHalf, Normal],
41
+ [RoutineA, TraitNone, Low],
42
+ [RoutineB, TraitFull, Low],
43
+ [RoutineB, TraitHalf, Normal],
44
+ [RoutineB, TraitNone, High],
45
+ ] as [typeof Routine, typeof Trait, typeof Priority][]
46
+ ).map(
47
+ ([RoutineType, TraitType, PriorityType]) =>
48
+ new PriorityRule(
49
+ new Criterion(
50
+ (player: Player, routine: Routine) =>
51
+ routine instanceof RoutineType
52
+ ),
53
+ new Criterion(
54
+ (player: Player) => player.civilization().leader() !== null
55
+ ),
56
+ new Criterion((player: Player) =>
57
+ traitRegistry
58
+ .getByLeader(
59
+ player.civilization().leader()!.sourceClass() as typeof Leader
60
+ )
61
+ .some((trait) => trait instanceof TraitType)
62
+ ),
63
+ new Effect(() => new PriorityType())
64
+ )
65
+ )
66
+ );
67
+
68
+ testPlayer.setCivilization(civilization);
69
+ civilization.setLeader(new Leader());
70
+ traitRegistry.register(traitFull);
71
+
72
+ expect(routine.priority(testPlayer).value()).equal(new High().value());
73
+ expect(inverseRoutine.priority(testPlayer).value()).equal(
74
+ new Low().value()
75
+ );
76
+
77
+ traitRegistry.unregister(traitFull);
78
+ traitRegistry.register(traitHalf);
79
+
80
+ expect(routine.priority(testPlayer).value()).equal(new Normal().value());
81
+ expect(inverseRoutine.priority(testPlayer).value()).equal(
82
+ new Normal().value()
83
+ );
84
+
85
+ traitRegistry.unregister(traitHalf);
86
+ traitRegistry.register(traitNone);
87
+
88
+ expect(routine.priority(testPlayer).value()).equal(new Low().value());
89
+ expect(inverseRoutine.priority(testPlayer).value()).equal(
90
+ new High().value()
91
+ );
92
+ });
93
+ });
@@ -0,0 +1,80 @@
1
+ import { High, Normal } from '@civ-clone/core-rule/Priorities';
2
+ import { RoutineA, RoutineB, RoutineFalse, RoutineTrue } from './lib/Routines';
3
+ import { expect, spy } from 'chai';
4
+ import Criterion from '@civ-clone/core-rule/Criterion';
5
+ import Effect from '@civ-clone/core-rule/Effect';
6
+ import Player from '@civ-clone/core-player/Player';
7
+ import { PlayerAction } from './lib/PlayerActions';
8
+ import Priority from '@civ-clone/core-rule/Priority';
9
+ import PriorityRule from '../Rules/Priority';
10
+ import Routine from '../Routine';
11
+ import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
12
+ import Strategy from '../Strategy';
13
+
14
+ describe('Strategy', () => {
15
+ const testPlayer = new Player();
16
+
17
+ it('should default to `Priority(Infinity)`', () =>
18
+ expect(new Strategy().priority(testPlayer).value()).eq(Infinity));
19
+
20
+ it('should stop calling `Routine`s after the first successful `attempt()`', async () => {
21
+ const routineA = new RoutineFalse(),
22
+ routineB = new RoutineTrue(),
23
+ routineC = new RoutineTrue(),
24
+ strategy = new Strategy(routineA, routineB, routineC),
25
+ spyA = spy.on(routineA, 'attempt'),
26
+ spyB = spy.on(routineB, 'attempt'),
27
+ spyC = spy.on(routineC, 'attempt');
28
+
29
+ expect(await strategy.attempt(new PlayerAction(testPlayer, null))).true;
30
+ expect(spyA).called();
31
+ expect(spyB).called();
32
+ expect(spyC).not.called();
33
+ });
34
+
35
+ it('should respect `Routine` `Priority`s', async () => {
36
+ const ruleRegistry = new RuleRegistry(),
37
+ routineA = new RoutineA(ruleRegistry),
38
+ routineB = new RoutineB(ruleRegistry),
39
+ strategy = new Strategy(routineA, routineB),
40
+ spyA = spy.on(routineA, 'attempt'),
41
+ spyB = spy.on(routineB, 'attempt');
42
+
43
+ ruleRegistry.register(
44
+ ...(
45
+ [
46
+ [RoutineA, High],
47
+ [RoutineB, Normal],
48
+ ] as [typeof Routine, typeof Priority][]
49
+ ).map(
50
+ ([RoutineType, PriorityType]) =>
51
+ new PriorityRule(
52
+ new Criterion(
53
+ (player: Player, routine: Routine) =>
54
+ routine instanceof RoutineType
55
+ ),
56
+ new Effect(() => new PriorityType())
57
+ )
58
+ )
59
+ );
60
+
61
+ expect(await strategy.attempt(new PlayerAction(testPlayer, null))).true;
62
+ expect(spyA).called();
63
+ expect(spyB).not.called();
64
+ });
65
+
66
+ it('should return false if there are no successfully executed `Routine`s', async () => {
67
+ const routineA = new RoutineFalse(),
68
+ routineB = new RoutineFalse(),
69
+ routineC = new RoutineFalse(),
70
+ strategy = new Strategy(routineA, routineB, routineC),
71
+ spyA = spy.on(routineA, 'attempt'),
72
+ spyB = spy.on(routineB, 'attempt'),
73
+ spyC = spy.on(routineC, 'attempt');
74
+
75
+ expect(await strategy.attempt(new PlayerAction(testPlayer, null))).false;
76
+ expect(spyA).called();
77
+ expect(spyB).called();
78
+ expect(spyC).called();
79
+ });
80
+ });
@@ -0,0 +1,77 @@
1
+ import { RoutineFalse, RoutineTrue } from './lib/Routines';
2
+ import { expect, spy, use } from 'chai';
3
+ import Player from '@civ-clone/core-player/Player';
4
+ import { PlayerAction } from './lib/PlayerActions';
5
+ import Strategy from '../Strategy';
6
+ import StrategyRegistry from '../StrategyRegistry';
7
+ import * as spies from 'chai-spies';
8
+
9
+ use(spies);
10
+
11
+ describe('StrategyRegistry', () => {
12
+ const testPlayer = new Player();
13
+
14
+ it('should filter inactive `Strategy`s', async () => {
15
+ const strategyA = new Strategy(new RoutineTrue()),
16
+ strategyB = new Strategy(new RoutineTrue()),
17
+ strategyRegistry = new StrategyRegistry(),
18
+ spyA = spy.on(strategyA, 'attempt'),
19
+ spyB = spy.on(strategyB, 'attempt');
20
+
21
+ strategyRegistry.register(strategyA, strategyB);
22
+
23
+ expect(await strategyRegistry.attempt(new PlayerAction(testPlayer, null)))
24
+ .true;
25
+ expect(spyA).not.called;
26
+ expect(spyB).called;
27
+ });
28
+
29
+ it('should stop calling `Strategy`s after the first successful `attempt()`', async () => {
30
+ const strategyA = new Strategy(new RoutineTrue()),
31
+ strategyB = new Strategy(new RoutineFalse()),
32
+ strategyRegistry = new StrategyRegistry(),
33
+ spyA = spy.on(strategyA, 'attempt'),
34
+ spyB = spy.on(strategyB, 'attempt');
35
+
36
+ strategyRegistry.register(strategyA, strategyB);
37
+
38
+ expect(await strategyRegistry.attempt(new PlayerAction(testPlayer, null)))
39
+ .true;
40
+ expect(spyA).called;
41
+ expect(spyB).not.called;
42
+ });
43
+
44
+ it('should respect `Strategy` `Priority`s', async () => {
45
+ const routine = new RoutineTrue(),
46
+ strategyA = new Strategy(routine),
47
+ strategyB = new Strategy(routine),
48
+ strategyRegistry = new StrategyRegistry(),
49
+ spyA = spy.on(strategyA, 'attempt'),
50
+ spyB = spy.on(strategyB, 'attempt');
51
+
52
+ strategyRegistry.register(strategyA, strategyB);
53
+
54
+ expect(await strategyRegistry.attempt(new PlayerAction(testPlayer, null)))
55
+ .true;
56
+ expect(spyA).not.called;
57
+ expect(spyB).called;
58
+ });
59
+
60
+ it('should return false if there are no successfully executed `Strategy`s', async () => {
61
+ const strategyA = new Strategy(new RoutineFalse()),
62
+ strategyB = new Strategy(new RoutineFalse()),
63
+ strategyC = new Strategy(new RoutineFalse()),
64
+ strategyRegistry = new StrategyRegistry(),
65
+ spyA = spy.on(strategyA, 'attempt'),
66
+ spyB = spy.on(strategyB, 'attempt'),
67
+ spyC = spy.on(strategyC, 'attempt');
68
+
69
+ strategyRegistry.register(strategyA, strategyB, strategyC);
70
+
71
+ expect(await strategyRegistry.attempt(new PlayerAction(testPlayer, null)))
72
+ .false;
73
+ expect(spyA).called;
74
+ expect(spyB).called;
75
+ expect(spyC).called;
76
+ });
77
+ });
@@ -0,0 +1,2 @@
1
+ import PlayerActionBase from '@civ-clone/core-player/PlayerAction';
2
+ export declare class PlayerAction<T = null> extends PlayerActionBase<T> {}
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PlayerAction = void 0;
4
+ const PlayerAction_1 = require("@civ-clone/core-player/PlayerAction");
5
+ class PlayerAction extends PlayerAction_1.default {
6
+ }
7
+ exports.PlayerAction = PlayerAction;
8
+ //# sourceMappingURL=PlayerActions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlayerActions.js","sourceRoot":"","sources":["PlayerActions.ts"],"names":[],"mappings":";;;AAAA,sEAAmE;AAEnE,MAAa,YAAuB,SAAQ,sBAAmB;CAAG;AAAlE,oCAAkE"}
@@ -0,0 +1,3 @@
1
+ import PlayerActionBase from '@civ-clone/core-player/PlayerAction';
2
+
3
+ export class PlayerAction<T = null> extends PlayerActionBase<T> {}
@@ -0,0 +1,9 @@
1
+ import Routine from '../../Routine';
2
+ export declare class RoutineTrue extends Routine {
3
+ attempt(): boolean;
4
+ }
5
+ export declare class RoutineFalse extends Routine {
6
+ attempt(): boolean;
7
+ }
8
+ export declare class RoutineA extends RoutineTrue {}
9
+ export declare class RoutineB extends RoutineTrue {}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RoutineB = exports.RoutineA = exports.RoutineFalse = exports.RoutineTrue = void 0;
4
+ const Routine_1 = require("../../Routine");
5
+ class RoutineTrue extends Routine_1.default {
6
+ attempt() {
7
+ return true;
8
+ }
9
+ }
10
+ exports.RoutineTrue = RoutineTrue;
11
+ class RoutineFalse extends Routine_1.default {
12
+ attempt() {
13
+ return false;
14
+ }
15
+ }
16
+ exports.RoutineFalse = RoutineFalse;
17
+ class RoutineA extends RoutineTrue {
18
+ }
19
+ exports.RoutineA = RoutineA;
20
+ class RoutineB extends RoutineTrue {
21
+ }
22
+ exports.RoutineB = RoutineB;
23
+ //# sourceMappingURL=Routines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Routines.js","sourceRoot":"","sources":["Routines.ts"],"names":[],"mappings":";;;AAAA,2CAAoC;AAEpC,MAAa,WAAY,SAAQ,iBAAO;IAC/B,OAAO;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAJD,kCAIC;AAED,MAAa,YAAa,SAAQ,iBAAO;IAChC,OAAO;QACZ,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAJD,oCAIC;AAED,MAAa,QAAS,SAAQ,WAAW;CAAG;AAA5C,4BAA4C;AAE5C,MAAa,QAAS,SAAQ,WAAW;CAAG;AAA5C,4BAA4C"}
@@ -0,0 +1,17 @@
1
+ import Routine from '../../Routine';
2
+
3
+ export class RoutineTrue extends Routine {
4
+ public attempt(): boolean {
5
+ return true;
6
+ }
7
+ }
8
+
9
+ export class RoutineFalse extends Routine {
10
+ public attempt(): boolean {
11
+ return false;
12
+ }
13
+ }
14
+
15
+ export class RoutineA extends RoutineTrue {}
16
+
17
+ export class RoutineB extends RoutineTrue {}
@@ -0,0 +1,12 @@
1
+ import Leader from '@civ-clone/core-civilization/Leader';
2
+ import Trait from '@civ-clone/core-civilization/Trait';
3
+ export declare class TraitType extends Trait {}
4
+ export declare class TraitFull extends TraitType {
5
+ constructor(leader: typeof Leader);
6
+ }
7
+ export declare class TraitHalf extends TraitType {
8
+ constructor(leader: typeof Leader);
9
+ }
10
+ export declare class TraitNone extends TraitType {
11
+ constructor(leader: typeof Leader);
12
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TraitNone = exports.TraitHalf = exports.TraitFull = exports.TraitType = void 0;
4
+ const Trait_1 = require("@civ-clone/core-civilization/Trait");
5
+ class TraitType extends Trait_1.default {
6
+ }
7
+ exports.TraitType = TraitType;
8
+ class TraitFull extends TraitType {
9
+ constructor(leader) {
10
+ super(leader, 1);
11
+ }
12
+ }
13
+ exports.TraitFull = TraitFull;
14
+ class TraitHalf extends TraitType {
15
+ constructor(leader) {
16
+ super(leader, 0.5);
17
+ }
18
+ }
19
+ exports.TraitHalf = TraitHalf;
20
+ class TraitNone extends TraitType {
21
+ constructor(leader) {
22
+ super(leader, 0);
23
+ }
24
+ }
25
+ exports.TraitNone = TraitNone;
26
+ //# sourceMappingURL=Traits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Traits.js","sourceRoot":"","sources":["Traits.ts"],"names":[],"mappings":";;;AACA,8DAAuD;AAEvD,MAAa,SAAU,SAAQ,eAAK;CAAG;AAAvC,8BAAuC;AAEvC,MAAa,SAAU,SAAQ,SAAS;IACtC,YAAY,MAAqB;QAC/B,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACnB,CAAC;CACF;AAJD,8BAIC;AAED,MAAa,SAAU,SAAQ,SAAS;IACtC,YAAY,MAAqB;QAC/B,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACrB,CAAC;CACF;AAJD,8BAIC;AAED,MAAa,SAAU,SAAQ,SAAS;IACtC,YAAY,MAAqB;QAC/B,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACnB,CAAC;CACF;AAJD,8BAIC"}
@@ -0,0 +1,22 @@
1
+ import Leader from '@civ-clone/core-civilization/Leader';
2
+ import Trait from '@civ-clone/core-civilization/Trait';
3
+
4
+ export class TraitType extends Trait {}
5
+
6
+ export class TraitFull extends TraitType {
7
+ constructor(leader: typeof Leader) {
8
+ super(leader, 1);
9
+ }
10
+ }
11
+
12
+ export class TraitHalf extends TraitType {
13
+ constructor(leader: typeof Leader) {
14
+ super(leader, 0.5);
15
+ }
16
+ }
17
+
18
+ export class TraitNone extends TraitType {
19
+ constructor(leader: typeof Leader) {
20
+ super(leader, 0);
21
+ }
22
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "declaration": true,
4
+ "lib": [
5
+ "es2019"
6
+ ],
7
+ "module": "commonjs",
8
+ "sourceMap": true,
9
+ "strict": true,
10
+ "target": "ES2018"
11
+ },
12
+ "exclude": [
13
+ "node_modules",
14
+ "tests/*.test.ts"
15
+ ]
16
+ }