@civ-clone/base-city-lifecycle 0.1.0 → 0.1.1

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.
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.releaseSpecialists = exports.releaseWorkedTiles = exports.destroyUnits = void 0;
4
+ /**
5
+ * Destroys each of `units` (a city's own, say) that isn't destroyed already: when the city was its player's last, the
6
+ * first unit to go defeats the player, which destroys the rest (civ-clone/web-renderer#600).
7
+ */
8
+ const destroyUnits = (units) => units.forEach((unit) => {
9
+ if (unit.destroyed()) {
10
+ return;
11
+ }
12
+ unit.destroy();
13
+ });
14
+ exports.destroyUnits = destroyUnits;
15
+ /**
16
+ * The tiles a city that's gone worked are free for another to work.
17
+ */
18
+ const releaseWorkedTiles = (city, workedTileRegistry) => workedTileRegistry
19
+ .getByCity(city)
20
+ .forEach((workedTile) => workedTileRegistry.unregister(workedTile));
21
+ exports.releaseWorkedTiles = releaseWorkedTiles;
22
+ /**
23
+ * A city that's gone has no specialists.
24
+ */
25
+ const releaseSpecialists = (city, specialistRegistry) => specialistRegistry
26
+ .getByCity(city)
27
+ .forEach((specialist) => specialistRegistry.unregister(specialist));
28
+ exports.releaseSpecialists = releaseSpecialists;
29
+ //# sourceMappingURL=destroyed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destroyed.js","sourceRoot":"","sources":["destroyed.ts"],"names":[],"mappings":";;;AAKA;;;GAGG;AACI,MAAM,YAAY,GAAG,CAAC,KAAa,EAAQ,EAAE,CAClD,KAAK,CAAC,OAAO,CAAC,CAAC,IAAU,EAAQ,EAAE;IACjC,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE;QACpB,OAAO;KACR;IAED,IAAI,CAAC,OAAO,EAAE,CAAC;AACjB,CAAC,CAAC,CAAC;AAPQ,QAAA,YAAY,gBAOpB;AAEL;;GAEG;AACI,MAAM,kBAAkB,GAAG,CAChC,IAAU,EACV,kBAAsC,EAChC,EAAE,CACR,kBAAkB;KACf,SAAS,CAAC,IAAI,CAAC;KACf,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;AAN3D,QAAA,kBAAkB,sBAMyC;AAExE;;GAEG;AACI,MAAM,kBAAkB,GAAG,CAChC,IAAU,EACV,kBAAsC,EAChC,EAAE,CACR,kBAAkB;KACf,SAAS,CAAC,IAAI,CAAC;KACf,OAAO,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;AAN3D,QAAA,kBAAkB,sBAMyC"}
@@ -0,0 +1,39 @@
1
+ import { SpecialistRegistry } from '@civ-clone/core-city/SpecialistRegistry';
2
+ import City from '@civ-clone/core-city/City';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+ import { WorkedTileRegistry } from '@civ-clone/core-city/WorkedTileRegistry';
5
+
6
+ /**
7
+ * Destroys each of `units` (a city's own, say) that isn't destroyed already: when the city was its player's last, the
8
+ * first unit to go defeats the player, which destroys the rest (civ-clone/web-renderer#600).
9
+ */
10
+ export const destroyUnits = (units: Unit[]): void =>
11
+ units.forEach((unit: Unit): void => {
12
+ if (unit.destroyed()) {
13
+ return;
14
+ }
15
+
16
+ unit.destroy();
17
+ });
18
+
19
+ /**
20
+ * The tiles a city that's gone worked are free for another to work.
21
+ */
22
+ export const releaseWorkedTiles = (
23
+ city: City,
24
+ workedTileRegistry: WorkedTileRegistry
25
+ ): void =>
26
+ workedTileRegistry
27
+ .getByCity(city)
28
+ .forEach((workedTile) => workedTileRegistry.unregister(workedTile));
29
+
30
+ /**
31
+ * A city that's gone has no specialists.
32
+ */
33
+ export const releaseSpecialists = (
34
+ city: City,
35
+ specialistRegistry: SpecialistRegistry
36
+ ): void =>
37
+ specialistRegistry
38
+ .getByCity(city)
39
+ .forEach((specialist) => specialistRegistry.unregister(specialist));
package/lib/events.d.ts CHANGED
@@ -29,3 +29,11 @@ export declare const emitBuildingHeld: (
29
29
  held: (string | null)[],
30
30
  engine: Engine
31
31
  ) => void;
32
+ /**
33
+ * Tells clients that `cityBuild` has built `built`.
34
+ */
35
+ export declare const emitBuildingComplete: (
36
+ cityBuild: CityBuild,
37
+ built: unknown,
38
+ engine: Engine
39
+ ) => void;
package/lib/events.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.emitBuildingHeld = exports.emitTileReleased = exports.emitTileWorked = void 0;
3
+ exports.emitBuildingComplete = exports.emitBuildingHeld = exports.emitTileReleased = exports.emitTileWorked = void 0;
4
4
  /**
5
5
  * Tells clients that `city` now works `tile`, so they can redraw it and show who works it.
6
6
  */
@@ -23,4 +23,11 @@ const emitBuildingHeld = (cityBuild, item, held, engine) => {
23
23
  engine.emit('city:building-held', cityBuild, item, held);
24
24
  };
25
25
  exports.emitBuildingHeld = emitBuildingHeld;
26
+ /**
27
+ * Tells clients that `cityBuild` has built `built`.
28
+ */
29
+ const emitBuildingComplete = (cityBuild, built, engine) => {
30
+ engine.emit('city:building-complete', cityBuild, built);
31
+ };
32
+ exports.emitBuildingComplete = emitBuildingComplete;
26
33
  //# sourceMappingURL=events.js.map
package/lib/events.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["events.ts"],"names":[],"mappings":";;;AAMA;;GAEG;AACI,MAAM,cAAc,GAAG,CAC5B,IAAU,EACV,IAAU,EACV,MAAc,EACR,EAAE;IACR,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC,CAAC;AANW,QAAA,cAAc,kBAMzB;AAEF;;GAEG;AACI,MAAM,gBAAgB,GAAG,CAC9B,IAAU,EACV,IAAU,EACV,MAAc,EACR,EAAE;IACR,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAChD,CAAC,CAAC;AANW,QAAA,gBAAgB,oBAM3B;AAEF;;;GAGG;AACI,MAAM,gBAAgB,GAAG,CAC9B,SAAoB,EACpB,IAAe,EACf,IAAuB,EACvB,MAAc,EACR,EAAE;IACR,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC3D,CAAC,CAAC;AAPW,QAAA,gBAAgB,oBAO3B"}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["events.ts"],"names":[],"mappings":";;;AAMA;;GAEG;AACI,MAAM,cAAc,GAAG,CAC5B,IAAU,EACV,IAAU,EACV,MAAc,EACR,EAAE;IACR,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC,CAAC;AANW,QAAA,cAAc,kBAMzB;AAEF;;GAEG;AACI,MAAM,gBAAgB,GAAG,CAC9B,IAAU,EACV,IAAU,EACV,MAAc,EACR,EAAE;IACR,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAChD,CAAC,CAAC;AANW,QAAA,gBAAgB,oBAM3B;AAEF;;;GAGG;AACI,MAAM,gBAAgB,GAAG,CAC9B,SAAoB,EACpB,IAAe,EACf,IAAuB,EACvB,MAAc,EACR,EAAE;IACR,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC3D,CAAC,CAAC;AAPW,QAAA,gBAAgB,oBAO3B;AAEF;;GAEG;AACI,MAAM,oBAAoB,GAAG,CAClC,SAAoB,EACpB,KAAc,EACd,MAAc,EACR,EAAE;IACR,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AAC1D,CAAC,CAAC;AANW,QAAA,oBAAoB,wBAM/B"}
package/lib/events.ts CHANGED
@@ -38,3 +38,14 @@ export const emitBuildingHeld = (
38
38
  ): void => {
39
39
  engine.emit('city:building-held', cityBuild, item, held);
40
40
  };
41
+
42
+ /**
43
+ * Tells clients that `cityBuild` has built `built`.
44
+ */
45
+ export const emitBuildingComplete = (
46
+ cityBuild: CityBuild,
47
+ built: unknown,
48
+ engine: Engine
49
+ ): void => {
50
+ engine.emit('city:building-complete', cityBuild, built);
51
+ };
@@ -1,6 +1,9 @@
1
+ import { CityRegistry } from '@civ-clone/core-city/CityRegistry';
2
+ import Player from '@civ-clone/core-player/Player';
1
3
  import { RuleRegistry } from '@civ-clone/core-rule/RuleRegistry';
2
4
  import Tile from '@civ-clone/core-world/Tile';
3
5
  import Unit from '@civ-clone/core-unit/Unit';
6
+ import { UnitRegistry } from '@civ-clone/core-unit/UnitRegistry';
4
7
  import WorkedTile from '@civ-clone/core-city/WorkedTile';
5
8
  import { WorkedTileRegistry } from '@civ-clone/core-city/WorkedTileRegistry';
6
9
  /**
@@ -23,3 +26,14 @@ export declare const releaseEnemyWorkedTile: (
23
26
  workedTileRegistry: WorkedTileRegistry,
24
27
  ruleRegistry: RuleRegistry
25
28
  ) => void;
29
+ /**
30
+ * At the start of `player`'s turn, each of its cities stops working any tile (other than its centre) that another
31
+ * player's unit stands on, and its `TileReassigned` rules choose what it works instead.
32
+ */
33
+ export declare const releaseOccupiedWorkedTiles: (
34
+ player: Player,
35
+ cityRegistry: CityRegistry,
36
+ unitRegistry: UnitRegistry,
37
+ workedTileRegistry: WorkedTileRegistry,
38
+ ruleRegistry: RuleRegistry
39
+ ) => void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.releaseEnemyWorkedTile = exports.enemyWorkedTile = void 0;
3
+ exports.releaseOccupiedWorkedTiles = exports.releaseEnemyWorkedTile = exports.enemyWorkedTile = void 0;
4
4
  const TileReassigned_1 = require("@civ-clone/core-city/Rules/TileReassigned");
5
5
  /**
6
6
  * The `WorkedTile` for `tile` if a city of a player other than `unit`'s works it, other than as its centre (a city always
@@ -32,4 +32,19 @@ const releaseEnemyWorkedTile = (unit, tile, workedTileRegistry, ruleRegistry) =>
32
32
  ruleRegistry.process(TileReassigned_1.default, workedTile.city(), tile);
33
33
  };
34
34
  exports.releaseEnemyWorkedTile = releaseEnemyWorkedTile;
35
+ /**
36
+ * At the start of `player`'s turn, each of its cities stops working any tile (other than its centre) that another
37
+ * player's unit stands on, and its `TileReassigned` rules choose what it works instead.
38
+ */
39
+ const releaseOccupiedWorkedTiles = (player, cityRegistry, unitRegistry, workedTileRegistry, ruleRegistry) => cityRegistry.getByPlayer(player).forEach((city) => workedTileRegistry
40
+ .getByCity(city)
41
+ .filter((workedTile) => workedTile.tile() !== city.tile() &&
42
+ unitRegistry
43
+ .getByTile(workedTile.tile())
44
+ .some((unit) => unit.player() !== player))
45
+ .forEach((workedTile) => {
46
+ workedTileRegistry.unregister(workedTile);
47
+ ruleRegistry.process(TileReassigned_1.default, city, workedTile.tile());
48
+ }));
49
+ exports.releaseOccupiedWorkedTiles = releaseOccupiedWorkedTiles;
35
50
  //# sourceMappingURL=occupiedTiles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"occupiedTiles.js","sourceRoot":"","sources":["occupiedTiles.ts"],"names":[],"mappings":";;;AAEA,8EAAuE;AAKvE;;;GAGG;AACI,MAAM,eAAe,GAAG,CAC7B,IAAU,EACV,IAAU,EACV,kBAAsC,EACnB,EAAE;IACrB,MAAM,UAAU,GAAG,kBAAkB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAEtD,IAAI,UAAU,KAAK,IAAI,EAAE;QACvB,OAAO,IAAI,CAAC;KACb;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAE/B,IAAI,UAAU,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;QACxE,OAAO,IAAI,CAAC;KACb;IAED,OAAO,UAAU,CAAC;AACpB,CAAC,CAAC;AAlBW,QAAA,eAAe,mBAkB1B;AAEF;;;;GAIG;AACI,MAAM,sBAAsB,GAAG,CACpC,IAAU,EACV,IAAU,EACV,kBAAsC,EACtC,YAA0B,EACpB,EAAE;IACR,MAAM,UAAU,GAAG,IAAA,uBAAe,EAAC,IAAI,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC;IAEnE,IAAI,UAAU,KAAK,IAAI,EAAE;QACvB,OAAO;KACR;IAED,kBAAkB,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAE1C,YAAY,CAAC,OAAO,CAAC,wBAAc,EAAE,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;AAChE,CAAC,CAAC;AAfW,QAAA,sBAAsB,0BAejC"}
1
+ {"version":3,"file":"occupiedTiles.js","sourceRoot":"","sources":["occupiedTiles.ts"],"names":[],"mappings":";;;AAKA,8EAAuE;AAMvE;;;GAGG;AACI,MAAM,eAAe,GAAG,CAC7B,IAAU,EACV,IAAU,EACV,kBAAsC,EACnB,EAAE;IACrB,MAAM,UAAU,GAAG,kBAAkB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAEtD,IAAI,UAAU,KAAK,IAAI,EAAE;QACvB,OAAO,IAAI,CAAC;KACb;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAE/B,IAAI,UAAU,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE;QACxE,OAAO,IAAI,CAAC;KACb;IAED,OAAO,UAAU,CAAC;AACpB,CAAC,CAAC;AAlBW,QAAA,eAAe,mBAkB1B;AAEF;;;;GAIG;AACI,MAAM,sBAAsB,GAAG,CACpC,IAAU,EACV,IAAU,EACV,kBAAsC,EACtC,YAA0B,EACpB,EAAE;IACR,MAAM,UAAU,GAAG,IAAA,uBAAe,EAAC,IAAI,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC;IAEnE,IAAI,UAAU,KAAK,IAAI,EAAE;QACvB,OAAO;KACR;IAED,kBAAkB,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAE1C,YAAY,CAAC,OAAO,CAAC,wBAAc,EAAE,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;AAChE,CAAC,CAAC;AAfW,QAAA,sBAAsB,0BAejC;AAEF;;;GAGG;AACI,MAAM,0BAA0B,GAAG,CACxC,MAAc,EACd,YAA0B,EAC1B,YAA0B,EAC1B,kBAAsC,EACtC,YAA0B,EACpB,EAAE,CACR,YAAY,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,IAAU,EAAQ,EAAE,CAC5D,kBAAkB;KACf,SAAS,CAAC,IAAI,CAAC;KACf,MAAM,CACL,CAAC,UAAsB,EAAW,EAAE,CAClC,UAAU,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE;IACjC,YAAY;SACT,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;SAC5B,IAAI,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,MAAM,CAAC,CAC7D;KACA,OAAO,CAAC,CAAC,UAAsB,EAAQ,EAAE;IACxC,kBAAkB,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAE1C,YAAY,CAAC,OAAO,CAAC,wBAAc,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC,CAAC,CACL,CAAC;AAtBS,QAAA,0BAA0B,8BAsBnC"}
@@ -1,7 +1,11 @@
1
+ import City from '@civ-clone/core-city/City';
2
+ import { CityRegistry } from '@civ-clone/core-city/CityRegistry';
3
+ import Player from '@civ-clone/core-player/Player';
1
4
  import { RuleRegistry } from '@civ-clone/core-rule/RuleRegistry';
2
5
  import Tile from '@civ-clone/core-world/Tile';
3
6
  import TileReassigned from '@civ-clone/core-city/Rules/TileReassigned';
4
7
  import Unit from '@civ-clone/core-unit/Unit';
8
+ import { UnitRegistry } from '@civ-clone/core-unit/UnitRegistry';
5
9
  import WorkedTile from '@civ-clone/core-city/WorkedTile';
6
10
  import { WorkedTileRegistry } from '@civ-clone/core-city/WorkedTileRegistry';
7
11
 
@@ -50,3 +54,31 @@ export const releaseEnemyWorkedTile = (
50
54
 
51
55
  ruleRegistry.process(TileReassigned, workedTile.city(), tile);
52
56
  };
57
+
58
+ /**
59
+ * At the start of `player`'s turn, each of its cities stops working any tile (other than its centre) that another
60
+ * player's unit stands on, and its `TileReassigned` rules choose what it works instead.
61
+ */
62
+ export const releaseOccupiedWorkedTiles = (
63
+ player: Player,
64
+ cityRegistry: CityRegistry,
65
+ unitRegistry: UnitRegistry,
66
+ workedTileRegistry: WorkedTileRegistry,
67
+ ruleRegistry: RuleRegistry
68
+ ): void =>
69
+ cityRegistry.getByPlayer(player).forEach((city: City): void =>
70
+ workedTileRegistry
71
+ .getByCity(city)
72
+ .filter(
73
+ (workedTile: WorkedTile): boolean =>
74
+ workedTile.tile() !== city.tile() &&
75
+ unitRegistry
76
+ .getByTile(workedTile.tile())
77
+ .some((unit: Unit): boolean => unit.player() !== player)
78
+ )
79
+ .forEach((workedTile: WorkedTile): void => {
80
+ workedTileRegistry.unregister(workedTile);
81
+
82
+ ruleRegistry.process(TileReassigned, city, workedTile.tile());
83
+ })
84
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civ-clone/base-city-lifecycle",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A city's turn: its food stored and its production built, growing or running out of food, the squares it can work, its units unsupported, and the city actions offered to its player.",
5
5
  "repository": "git@github.com:civ-clone/base-city-lifecycle.git",
6
6
  "keywords": [
@@ -18,6 +18,7 @@
18
18
  "ts:compile": "tsc --build tsconfig.json"
19
19
  },
20
20
  "devDependencies": {
21
+ "@civ-clone/library-city": "0.1.4",
21
22
  "@types/chai": "^4.3.20",
22
23
  "@types/mocha": "^10.0.10",
23
24
  "@types/node": "^14.18.63",
@@ -28,12 +29,16 @@
28
29
  "typescript": "^4.9.5"
29
30
  },
30
31
  "dependencies": {
32
+ "@civ-clone/base-terrain-yield-food": "^0.1.0",
33
+ "@civ-clone/base-terrain-yield-production": "^0.1.0",
34
+ "@civ-clone/base-terrain-yield-trade": "^0.1.0",
31
35
  "@civ-clone/core-city": "^0.1.24",
32
36
  "@civ-clone/core-city-build": "^0.1.22",
33
37
  "@civ-clone/core-city-growth": "^0.1.6",
34
38
  "@civ-clone/core-city-happiness": "^0.1.2",
35
39
  "@civ-clone/core-engine": "^0.1.4",
36
40
  "@civ-clone/core-player": "^0.1.8",
41
+ "@civ-clone/core-player-world": "^0.1.14",
37
42
  "@civ-clone/core-rule": "^0.1.5",
38
43
  "@civ-clone/core-unit": "^0.1.31",
39
44
  "@civ-clone/core-world": "^0.1.19",
@@ -0,0 +1,341 @@
1
+ import * as base from '../lib/assignWorkers';
2
+ import * as library from '@civ-clone/library-city/lib/assignWorkers';
3
+ import { describe, it } from 'mocha';
4
+ import {
5
+ destroyUnits,
6
+ releaseSpecialists,
7
+ releaseWorkedTiles,
8
+ } from '../lib/destroyed';
9
+ import {
10
+ registerCityBuild,
11
+ registerCityGrowth,
12
+ workCityTile,
13
+ } from '../lib/created';
14
+ import AvailableCityBuildItemsRegistry from '@civ-clone/core-city-build/AvailableCityBuildItemsRegistry';
15
+ import AvailableSpecialistRegistry from '@civ-clone/core-city/AvailableSpecialistRegistry';
16
+ import CityBuildRegistry from '@civ-clone/core-city-build/CityBuildRegistry';
17
+ import CityGrowthRegistry from '@civ-clone/core-city-growth/CityGrowthRegistry';
18
+ import Effect from '@civ-clone/core-rule/Effect';
19
+ import PlayerWorldRegistry from '@civ-clone/core-player-world/PlayerWorldRegistry';
20
+ import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
21
+ import Specialist from '@civ-clone/core-city/Specialist';
22
+ import SpecialistRegistry from '@civ-clone/core-city/SpecialistRegistry';
23
+ import TileReassigned from '@civ-clone/core-city/Rules/TileReassigned';
24
+ import WorkedTile from '@civ-clone/core-city/WorkedTile';
25
+ import WorkedTileRegistry from '@civ-clone/core-city/WorkedTileRegistry';
26
+ import { clearCapturedBuild, keepCapturedBuild } from '../lib/captured';
27
+ import { emitBuildingComplete } from '../lib/events';
28
+ import { expect } from 'chai';
29
+ import { readFileSync } from 'fs';
30
+ import { releaseOccupiedWorkedTiles } from '../lib/occupiedTiles';
31
+
32
+ class Entertainer extends Specialist {}
33
+ class Scientist extends Specialist {}
34
+
35
+ const named = (name: string, extra: object = {}) => ({ name, ...extra } as any),
36
+ // A city with no tiles of its own to work, but its centre.
37
+ standInCity = (name: string, centre: unknown, player: unknown) =>
38
+ named(name, {
39
+ tile: () => centre,
40
+ tiles: () => ({ entries: () => [], includes: () => false }),
41
+ tilesWorked: () => ({ yields: () => [], entries: () => [] }),
42
+ player: () => player,
43
+ }),
44
+ growthOf = (size: number) =>
45
+ ({ getByCity: () => ({ size: () => size }) } as any);
46
+
47
+ describe('assignWorkers', (): void => {
48
+ // The compiled functions, with the yields named the same way: library-city's come from its `Yields` module.
49
+ const source = (fn: unknown): string =>
50
+ String(fn)
51
+ .replace(/Yields_1\.(Food|Production|Trade)/g, '$1')
52
+ .replace(/(Food|Production|Trade)_1\.default/g, '$1');
53
+
54
+ // The whole compiled module, private helpers (`removeSpecialist`) included, with only where the yields come from and
55
+ // the lifting note taken out of each.
56
+ it("is library-city 0.1.4's file, unchanged", (): void => {
57
+ const module = (path: string): string =>
58
+ readFileSync(path, 'utf8')
59
+ .split('\n')
60
+ .filter(
61
+ (line) =>
62
+ !line.startsWith('//') &&
63
+ !/^const (Yields|Food|Production|Trade)_1 = require/.test(line)
64
+ )
65
+ .join('\n')
66
+ .replace(/Yields_1\.(Food|Production|Trade)/g, '$1')
67
+ .replace(/(Food|Production|Trade)_1\.default/g, '$1');
68
+
69
+ expect(
70
+ module(require.resolve('../lib/assignWorkers.js')) ===
71
+ module(require.resolve('@civ-clone/library-city/lib/assignWorkers.js'))
72
+ ).true;
73
+ });
74
+
75
+ it("exports library-city's functions, each unchanged", (): void => {
76
+ const names = Object.keys(library).filter(
77
+ (name) => typeof (library as any)[name] === 'function'
78
+ );
79
+
80
+ expect(names).to.not.be.empty;
81
+ expect(
82
+ Object.keys(base).filter(
83
+ (name) => typeof (base as any)[name] === 'function'
84
+ )
85
+ ).to.have.members(names);
86
+
87
+ names.forEach((name: string): void => {
88
+ expect(source((base as any)[name]), name).to.equal(
89
+ source((library as any)[name])
90
+ );
91
+ });
92
+ });
93
+
94
+ it('places citizens as specialists of the first kind offered, and turns one into the next kind', (): void => {
95
+ const specialistRegistry = new SpecialistRegistry(),
96
+ available = new AvailableSpecialistRegistry(),
97
+ city = standInCity('city', named('centre'), named('player'));
98
+
99
+ available.register(Entertainer, Scientist);
100
+
101
+ const specialist = base.addSpecialist(city, specialistRegistry, available)!;
102
+
103
+ expect(specialist).to.instanceOf(Entertainer);
104
+ expect(
105
+ base.citizenCount(city, new WorkedTileRegistry(), specialistRegistry)
106
+ ).to.equal(1);
107
+
108
+ const changed = base.changeSpecialist(
109
+ specialist,
110
+ specialistRegistry,
111
+ available
112
+ );
113
+
114
+ expect(changed).to.instanceOf(Scientist);
115
+ expect(specialistRegistry.getByCity(city)).to.eql([changed]);
116
+ expect(
117
+ base.addSpecialist(
118
+ city,
119
+ specialistRegistry,
120
+ new AvailableSpecialistRegistry()
121
+ )
122
+ ).to.null;
123
+ });
124
+ });
125
+
126
+ describe('created', (): void => {
127
+ it('gives a new city its build, asked for as a new one, and its growth', (): void => {
128
+ const city = named('city'),
129
+ ruleRegistry = new RuleRegistry(),
130
+ cityBuildRegistry = new CityBuildRegistry(),
131
+ cityGrowthRegistry = new CityGrowthRegistry(),
132
+ cityBuild = registerCityBuild(
133
+ city,
134
+ cityBuildRegistry,
135
+ new AvailableCityBuildItemsRegistry(),
136
+ ruleRegistry
137
+ ),
138
+ cityGrowth = registerCityGrowth(city, cityGrowthRegistry, ruleRegistry);
139
+
140
+ expect(cityBuildRegistry.getByCity(city)).to.equal(cityBuild);
141
+ expect(cityBuild.cleared()).to.eql({ reason: 'created', item: null });
142
+ expect(cityGrowthRegistry.getByCity(city)).to.equal(cityGrowth);
143
+ expect(cityGrowth.size()).to.equal(1);
144
+ });
145
+
146
+ it('works its own centre, and lets the city that worked it choose again first', (): void => {
147
+ const centre = named('centre'),
148
+ player = named('player'),
149
+ previous = standInCity('previous', named('elsewhere'), player),
150
+ city = standInCity('city', centre, player),
151
+ workedTileRegistry = new WorkedTileRegistry(),
152
+ ruleRegistry = new RuleRegistry(),
153
+ log: unknown[][] = [];
154
+
155
+ workedTileRegistry.register(new WorkedTile(centre as any, previous));
156
+
157
+ ruleRegistry.register(
158
+ new TileReassigned(
159
+ new Effect((...args: unknown[]) => {
160
+ log.push([
161
+ ...args,
162
+ workedTileRegistry.getByTile(centre as any)!.city(),
163
+ ]);
164
+ })
165
+ )
166
+ );
167
+
168
+ workCityTile(
169
+ city,
170
+ ruleRegistry,
171
+ new PlayerWorldRegistry(),
172
+ growthOf(0),
173
+ workedTileRegistry,
174
+ new SpecialistRegistry(),
175
+ new AvailableSpecialistRegistry()
176
+ );
177
+
178
+ expect(workedTileRegistry.getByTile(centre as any)!.city()).to.equal(city);
179
+ expect(log).to.eql([[previous, centre, city]]);
180
+ });
181
+
182
+ it('places a citizen with no tile to work as a specialist', (): void => {
183
+ const city = standInCity('city', named('centre'), named('player')),
184
+ specialistRegistry = new SpecialistRegistry(),
185
+ available = new AvailableSpecialistRegistry();
186
+
187
+ available.register(Entertainer);
188
+
189
+ workCityTile(
190
+ city,
191
+ new RuleRegistry(),
192
+ new PlayerWorldRegistry(),
193
+ growthOf(1),
194
+ new WorkedTileRegistry(),
195
+ specialistRegistry,
196
+ available
197
+ );
198
+
199
+ expect(specialistRegistry.getByCity(city)).to.have.length(1);
200
+ });
201
+ });
202
+
203
+ describe('destroyed', (): void => {
204
+ it('destroys only the units not destroyed already', (): void => {
205
+ const log: string[] = [],
206
+ unit = (name: string, destroyed: boolean) =>
207
+ named(name, {
208
+ destroyed: () => destroyed,
209
+ destroy: () => log.push(name),
210
+ });
211
+
212
+ destroyUnits([unit('a', false), unit('b', true), unit('c', false)]);
213
+
214
+ expect(log).to.eql(['a', 'c']);
215
+ });
216
+
217
+ it("frees the city's tiles and its specialists, and no other city's", (): void => {
218
+ const city = named('city'),
219
+ other = named('other'),
220
+ workedTileRegistry = new WorkedTileRegistry(),
221
+ specialistRegistry = new SpecialistRegistry(),
222
+ kept = new WorkedTile(named('kept') as any, other),
223
+ keptSpecialist = new Entertainer(other);
224
+
225
+ workedTileRegistry.register(
226
+ new WorkedTile(named('a') as any, city),
227
+ kept,
228
+ new WorkedTile(named('b') as any, city)
229
+ );
230
+ specialistRegistry.register(
231
+ new Entertainer(city),
232
+ keptSpecialist,
233
+ new Scientist(city)
234
+ );
235
+
236
+ releaseWorkedTiles(city, workedTileRegistry);
237
+ releaseSpecialists(city, specialistRegistry);
238
+
239
+ expect(workedTileRegistry.entries()).to.eql([kept]);
240
+ expect(specialistRegistry.entries()).to.eql([keptSpecialist]);
241
+ });
242
+ });
243
+
244
+ describe('releaseOccupiedWorkedTiles', (): void => {
245
+ it("releases the tiles another player's unit stands on, never the centre, and hands them to TileReassigned", (): void => {
246
+ const player = named('player'),
247
+ enemy = named('enemy'),
248
+ centre = named('centre'),
249
+ occupied = named('occupied'),
250
+ own = named('own'),
251
+ free = named('free'),
252
+ city = named('city', { tile: () => centre }),
253
+ workedTileRegistry = new WorkedTileRegistry(),
254
+ ruleRegistry = new RuleRegistry(),
255
+ log: unknown[][] = [],
256
+ unitsOn: Map<unknown, unknown[]> = new Map([
257
+ [centre, [named('enemyOnCentre', { player: () => enemy })]],
258
+ [occupied, [named('enemyUnit', { player: () => enemy })]],
259
+ [own, [named('ownUnit', { player: () => player })]],
260
+ ]),
261
+ centreWorked = new WorkedTile(centre, city),
262
+ ownWorked = new WorkedTile(own, city),
263
+ freeWorked = new WorkedTile(free, city);
264
+
265
+ workedTileRegistry.register(
266
+ centreWorked,
267
+ new WorkedTile(occupied, city),
268
+ ownWorked,
269
+ freeWorked
270
+ );
271
+
272
+ ruleRegistry.register(
273
+ new TileReassigned(new Effect((...args: unknown[]) => log.push(args)))
274
+ );
275
+
276
+ releaseOccupiedWorkedTiles(
277
+ player,
278
+ { getByPlayer: () => [city] } as any,
279
+ { getByTile: (tile: unknown) => unitsOn.get(tile) ?? [] } as any,
280
+ workedTileRegistry,
281
+ ruleRegistry
282
+ );
283
+
284
+ expect(workedTileRegistry.entries()).to.eql([
285
+ centreWorked,
286
+ ownWorked,
287
+ freeWorked,
288
+ ]);
289
+ expect(log).to.eql([[city, occupied]]);
290
+ });
291
+ });
292
+
293
+ describe('emitBuildingComplete', (): void => {
294
+ it('tells clients of the build and what was built', (): void => {
295
+ const emitted: unknown[][] = [],
296
+ cityBuild = named('cityBuild'),
297
+ built = named('built');
298
+
299
+ emitBuildingComplete(cityBuild, built, {
300
+ emit: (...args: unknown[]) => emitted.push(args),
301
+ } as any);
302
+
303
+ expect(emitted).to.eql([['city:building-complete', cityBuild, built]]);
304
+ });
305
+ });
306
+
307
+ describe('captured', (): void => {
308
+ const standInBuild = (building: unknown, log: unknown[][]) =>
309
+ ({
310
+ building: () => building,
311
+ setCleared: (...args: unknown[]) => log.push(['setCleared', ...args]),
312
+ cancel: (...args: unknown[]) => log.push(['cancel', ...args]),
313
+ clearQueue: (...args: unknown[]) => log.push(['clearQueue', ...args]),
314
+ reprice: (...args: unknown[]) => log.push(['reprice', ...args]),
315
+ } as any);
316
+
317
+ it('clears the build, or asks for one when there was none', (): void => {
318
+ const log: unknown[][] = [];
319
+
320
+ clearCapturedBuild(standInBuild(named('item'), log));
321
+ clearCapturedBuild(standInBuild(null, log));
322
+
323
+ expect(log).to.eql([
324
+ ['cancel', 'captured'],
325
+ ['setCleared', 'captured'],
326
+ ]);
327
+ });
328
+
329
+ it('keeps the item at the new price without the queue, or asks for one when there was none', (): void => {
330
+ const log: unknown[][] = [];
331
+
332
+ keepCapturedBuild(standInBuild(named('item'), log));
333
+ keepCapturedBuild(standInBuild(null, log));
334
+
335
+ expect(log).to.eql([
336
+ ['clearQueue'],
337
+ ['reprice'],
338
+ ['setCleared', 'captured'],
339
+ ]);
340
+ });
341
+ });