@civ-clone/core-strategy 0.1.2 → 0.1.4

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.
@@ -2,7 +2,8 @@ import EntityRegistry from '@civ-clone/core-registry/EntityRegistry';
2
2
  import PlayerAction from '@civ-clone/core-player/PlayerAction';
3
3
  import Strategy from './Strategy';
4
4
  export declare class StrategyRegistry extends EntityRegistry<Strategy> {
5
- constructor();
5
+ private _randomNumberGenerator;
6
+ constructor(randomNumberGenerator?: () => number);
6
7
  /**
7
8
  * Iterates all `Strategy`s ordered first by `Priority`, then by a random number so that alternative strategies are
8
9
  * tried.
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.instance = exports.StrategyRegistry = void 0;
4
4
  const EntityRegistry_1 = require("@civ-clone/core-registry/EntityRegistry");
5
5
  const Strategy_1 = require("./Strategy");
6
+ const core_random_1 = require("@civ-clone/core-random");
6
7
  class StrategyRegistry extends EntityRegistry_1.default {
7
- constructor() {
8
+ constructor(randomNumberGenerator = core_random_1.instance) {
8
9
  super(Strategy_1.default);
10
+ this._randomNumberGenerator = randomNumberGenerator;
9
11
  }
10
12
  /**
11
13
  * Iterates all `Strategy`s ordered first by `Priority`, then by a random number so that alternative strategies are
@@ -14,7 +16,7 @@ class StrategyRegistry extends EntityRegistry_1.default {
14
16
  attempt(action) {
15
17
  return this.entries()
16
18
  .sort((a, b) => a.priority(action).value() - b.priority(action).value() ||
17
- Math.floor(Math.random() * 3 - 1))
19
+ Math.floor(this._randomNumberGenerator() * 3 - 1))
18
20
  .some((strategy) => strategy.attempt(action));
19
21
  }
20
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"StrategyRegistry.js","sourceRoot":"","sources":["StrategyRegistry.ts"],"names":[],"mappings":";;;AAAA,4EAAqE;AAErE,yCAAkC;AAElC,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,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE;YACvD,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;AAlBD,4CAkBC;AAEY,QAAA,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;AAE/C,kBAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"StrategyRegistry.js","sourceRoot":"","sources":["StrategyRegistry.ts"],"names":[],"mappings":";;;AAAA,4EAAqE;AAErE,yCAAkC;AAClC,wDAAiE;AAEjE,MAAa,gBAAiB,SAAQ,wBAAwB;IAG5D,YAAY,wBAAsC,sBAAW;QAC3D,KAAK,CAAC,kBAAQ,CAAC,CAAC;QAEhB,IAAI,CAAC,sBAAsB,GAAG,qBAAqB,CAAC;IACtD,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,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE;YACvD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CACpD;aACA,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC;CACF;AAtBD,4CAsBC;AAEY,QAAA,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;AAE/C,kBAAe,gBAAgB,CAAC"}
@@ -1,10 +1,15 @@
1
1
  import EntityRegistry from '@civ-clone/core-registry/EntityRegistry';
2
2
  import PlayerAction from '@civ-clone/core-player/PlayerAction';
3
3
  import Strategy from './Strategy';
4
+ import { instance as rngInstance } from '@civ-clone/core-random';
4
5
 
5
6
  export class StrategyRegistry extends EntityRegistry<Strategy> {
6
- constructor() {
7
+ private _randomNumberGenerator: () => number;
8
+
9
+ constructor(randomNumberGenerator: () => number = rngInstance) {
7
10
  super(Strategy);
11
+
12
+ this._randomNumberGenerator = randomNumberGenerator;
8
13
  }
9
14
 
10
15
  /**
@@ -16,7 +21,7 @@ export class StrategyRegistry extends EntityRegistry<Strategy> {
16
21
  .sort(
17
22
  (a, b) =>
18
23
  a.priority(action).value() - b.priority(action).value() ||
19
- Math.floor(Math.random() * 3 - 1)
24
+ Math.floor(this._randomNumberGenerator() * 3 - 1)
20
25
  )
21
26
  .some((strategy) => strategy.attempt(action));
22
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civ-clone/core-strategy",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "repository": "git@github.com:civ-clone/core-strategy.git",
5
5
  "keywords": [
6
6
  "typescript",
@@ -34,6 +34,7 @@
34
34
  "@civ-clone/core-civilization": "^0.1.0",
35
35
  "@civ-clone/core-data-object": "^0.1.0",
36
36
  "@civ-clone/core-player": "^0.1.2",
37
+ "@civ-clone/core-random": "^0.1.0",
37
38
  "@civ-clone/core-registry": "^0.1.0",
38
39
  "@civ-clone/core-rule": "^0.1.1"
39
40
  }
@@ -1,7 +1,12 @@
1
1
  import { StrategyFalse, StrategyTrue } from './lib/Strategies';
2
2
  import { expect, spy, use } from 'chai';
3
+ import Criterion from '@civ-clone/core-rule/Criterion';
4
+ import Effect from '@civ-clone/core-rule/Effect';
3
5
  import Player from '@civ-clone/core-player/Player';
4
6
  import { PlayerAction } from './lib/PlayerActions';
7
+ import PriorityRule from '../Rules/Priority';
8
+ import PriorityValue from '@civ-clone/core-rule/Priority';
9
+ import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
5
10
  import Strategy from '../Strategy';
6
11
  import StrategyRegistry from '../StrategyRegistry';
7
12
  import * as spies from 'chai-spies';
@@ -11,49 +16,63 @@ use(spies);
11
16
  describe('StrategyRegistry', () => {
12
17
  const testPlayer = new Player();
13
18
 
14
- it('should filter inactive `Strategy`s', async () => {
15
- const strategyA = new StrategyTrue(),
16
- strategyB = new Strategy(),
17
- strategyRegistry = new StrategyRegistry(),
18
- spyA = spy.on(strategyA, 'attempt'),
19
- spyB = spy.on(strategyB, 'attempt');
20
-
21
- strategyRegistry.register(strategyA, strategyB);
19
+ it('should stop calling `Strategy`s after the first successful `attempt()`', async () => {
20
+ // `A` is given the higher priority so that it definitely runs first.
21
+ // Equal priorities are ordered by a random draw, which would make "the
22
+ // second one was not reached" true only some of the time.
23
+ const ruleRegistry = new RuleRegistry(),
24
+ strategyA = new StrategyTrue(ruleRegistry),
25
+ strategyB = new StrategyTrue(ruleRegistry),
26
+ strategyRegistry = new StrategyRegistry();
22
27
 
23
- expect(await strategyRegistry.attempt(new PlayerAction(testPlayer, null)))
24
- .true;
25
- expect(spyA).not.called;
26
- expect(spyB).called;
27
- });
28
+ ruleRegistry.register(
29
+ new PriorityRule(
30
+ new Criterion(
31
+ (action: PlayerAction, strategy: Strategy): boolean =>
32
+ strategy === strategyA
33
+ ),
34
+ new Effect((): PriorityValue => new PriorityValue(1))
35
+ )
36
+ );
28
37
 
29
- it('should stop calling `Strategy`s after the first successful `attempt()`', async () => {
30
- const strategyA = new StrategyTrue(),
31
- strategyB = new StrategyFalse(),
32
- strategyRegistry = new StrategyRegistry(),
33
- spyA = spy.on(strategyA, 'attempt'),
38
+ const spyA = spy.on(strategyA, 'attempt'),
34
39
  spyB = spy.on(strategyB, 'attempt');
35
40
 
36
41
  strategyRegistry.register(strategyA, strategyB);
37
42
 
38
- expect(await strategyRegistry.attempt(new PlayerAction(testPlayer, null)))
39
- .true;
40
- expect(spyA).called;
41
- expect(spyB).not.called;
43
+ expect(strategyRegistry.attempt(new PlayerAction(testPlayer, null))).true;
44
+ expect(spyA).to.have.been.called();
45
+ expect(spyB).to.not.have.been.called();
42
46
  });
43
47
 
44
48
  it('should respect `Strategy` `Priority`s', async () => {
45
- const strategyA = new StrategyTrue(),
46
- strategyB = new StrategyTrue(),
47
- strategyRegistry = new StrategyRegistry(),
48
- spyA = spy.on(strategyA, 'attempt'),
49
+ const ruleRegistry = new RuleRegistry(),
50
+ strategyA = new StrategyTrue(ruleRegistry),
51
+ strategyB = new StrategyTrue(ruleRegistry),
52
+ strategyRegistry = new StrategyRegistry();
53
+
54
+ // Without this the two are tied and the order is a coin flip. Giving `B`
55
+ // the lower value — which is the higher priority — is the whole point of
56
+ // the test, and it is what makes the assertion below meaningful rather
57
+ // than true three times in four.
58
+ ruleRegistry.register(
59
+ new PriorityRule(
60
+ new Criterion(
61
+ (action: PlayerAction, strategy: Strategy): boolean =>
62
+ strategy === strategyB
63
+ ),
64
+ new Effect((): PriorityValue => new PriorityValue(1))
65
+ )
66
+ );
67
+
68
+ const spyA = spy.on(strategyA, 'attempt'),
49
69
  spyB = spy.on(strategyB, 'attempt');
50
70
 
51
71
  strategyRegistry.register(strategyA, strategyB);
52
72
 
53
- expect(await strategyRegistry.attempt(new PlayerAction(testPlayer, null)))
54
- .true;
55
- expect(spyA).not.called;
56
- expect(spyB).called;
73
+ expect(strategyRegistry.attempt(new PlayerAction(testPlayer, null))).true;
74
+ expect(spyB).to.have.been.called();
75
+ expect(spyA).to.not.have.been.called();
57
76
  });
58
77
 
59
78
  it('should return false if there are no successfully executed `Strategy`s', async () => {
@@ -67,10 +86,9 @@ describe('StrategyRegistry', () => {
67
86
 
68
87
  strategyRegistry.register(strategyA, strategyB, strategyC);
69
88
 
70
- expect(await strategyRegistry.attempt(new PlayerAction(testPlayer, null)))
71
- .false;
72
- expect(spyA).called;
73
- expect(spyB).called;
74
- expect(spyC).called;
89
+ expect(strategyRegistry.attempt(new PlayerAction(testPlayer, null))).false;
90
+ expect(spyA).to.have.been.called();
91
+ expect(spyB).to.have.been.called();
92
+ expect(spyC).to.have.been.called();
75
93
  });
76
94
  });