@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.
- package/README.md +14 -3
- package/lib/assignWorkers.d.ts +114 -0
- package/lib/assignWorkers.js +184 -0
- package/lib/assignWorkers.js.map +1 -0
- package/lib/assignWorkers.ts +372 -0
- package/lib/captured.d.ts +12 -0
- package/lib/captured.js +30 -0
- package/lib/captured.js.map +1 -0
- package/lib/captured.ts +31 -0
- package/lib/created.d.ts +41 -0
- package/lib/created.js +52 -0
- package/lib/created.js.map +1 -0
- package/lib/created.ts +110 -0
- package/lib/destroyed.d.ts +23 -0
- package/lib/destroyed.js +29 -0
- package/lib/destroyed.js.map +1 -0
- package/lib/destroyed.ts +39 -0
- package/lib/events.d.ts +8 -0
- package/lib/events.js +8 -1
- package/lib/events.js.map +1 -1
- package/lib/events.ts +11 -0
- package/lib/occupiedTiles.d.ts +14 -0
- package/lib/occupiedTiles.js +16 -1
- package/lib/occupiedTiles.js.map +1 -1
- package/lib/occupiedTiles.ts +32 -0
- package/package.json +6 -1
- package/tests/r3b.test.ts +341 -0
package/lib/destroyed.js
ADDED
|
@@ -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"}
|
package/lib/destroyed.ts
ADDED
|
@@ -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
|
+
};
|
package/lib/occupiedTiles.d.ts
CHANGED
|
@@ -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;
|
package/lib/occupiedTiles.js
CHANGED
|
@@ -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
|
package/lib/occupiedTiles.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"occupiedTiles.js","sourceRoot":"","sources":["occupiedTiles.ts"],"names":[],"mappings":";;;
|
|
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"}
|
package/lib/occupiedTiles.ts
CHANGED
|
@@ -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.
|
|
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
|
+
});
|