@civ-clone/base-unit-lifecycle 0.0.0-stage → 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 (56) hide show
  1. package/.gitattributes +4 -0
  2. package/.github/workflows/build-on-push.yml +39 -0
  3. package/.prettierrc +4 -0
  4. package/LICENSE +21 -0
  5. package/README.md +35 -2
  6. package/lib/activate.d.ts +15 -0
  7. package/lib/activate.js +27 -0
  8. package/lib/activate.js.map +1 -0
  9. package/lib/activate.ts +47 -0
  10. package/lib/canStow.d.ts +8 -0
  11. package/lib/canStow.js +17 -0
  12. package/lib/canStow.js.map +1 -0
  13. package/lib/canStow.ts +25 -0
  14. package/lib/created.d.ts +9 -0
  15. package/lib/created.js +19 -0
  16. package/lib/created.js.map +1 -0
  17. package/lib/created.ts +24 -0
  18. package/lib/defeated.d.ts +25 -0
  19. package/lib/defeated.js +33 -0
  20. package/lib/defeated.js.map +1 -0
  21. package/lib/defeated.ts +55 -0
  22. package/lib/destroyed.d.ts +37 -0
  23. package/lib/destroyed.js +74 -0
  24. package/lib/destroyed.js.map +1 -0
  25. package/lib/destroyed.ts +106 -0
  26. package/lib/halted.d.ts +6 -0
  27. package/lib/halted.js +10 -0
  28. package/lib/halted.js.map +1 -0
  29. package/lib/halted.ts +11 -0
  30. package/lib/impassable.d.ts +8 -0
  31. package/lib/impassable.js +15 -0
  32. package/lib/impassable.js.map +1 -0
  33. package/lib/impassable.ts +19 -0
  34. package/lib/lostAtSea.d.ts +12 -0
  35. package/lib/lostAtSea.js +26 -0
  36. package/lib/lostAtSea.js.map +1 -0
  37. package/lib/lostAtSea.ts +37 -0
  38. package/lib/playerAction.d.ts +22 -0
  39. package/lib/playerAction.js +28 -0
  40. package/lib/playerAction.js.map +1 -0
  41. package/lib/playerAction.ts +64 -0
  42. package/lib/stowed.d.ts +2 -0
  43. package/lib/stowed.js +8 -0
  44. package/lib/stowed.js.map +1 -0
  45. package/lib/stowed.ts +6 -0
  46. package/lib/unsupported.d.ts +6 -0
  47. package/lib/unsupported.js +9 -0
  48. package/lib/unsupported.js.map +1 -0
  49. package/lib/unsupported.ts +10 -0
  50. package/lib/validateMove.d.ts +11 -0
  51. package/lib/validateMove.js +26 -0
  52. package/lib/validateMove.js.map +1 -0
  53. package/lib/validateMove.ts +36 -0
  54. package/package.json +43 -4
  55. package/tests/lifecycle.test.ts +657 -0
  56. package/tsconfig.json +16 -0
@@ -0,0 +1,106 @@
1
+ import Engine from '@civ-clone/core-engine/Engine';
2
+ import { ITransport } from '@civ-clone/core-unit-transport/Transport';
3
+ import Player from '@civ-clone/core-player/Player';
4
+ import Stowed from '@civ-clone/base-unit-action-embark/Busy/Stowed';
5
+ import TransportManifest from '@civ-clone/core-unit-transport/TransportManifest';
6
+ import TransportRegistry from '@civ-clone/core-unit-transport/TransportRegistry';
7
+ import Unit from '@civ-clone/core-unit/Unit';
8
+ import UnitImprovementRegistry from '@civ-clone/core-unit-improvement/UnitImprovementRegistry';
9
+ import UnitLossRegistry from '@civ-clone/core-unit/UnitLossRegistry';
10
+ import { Water } from '@civ-clone/core-terrain/Types';
11
+
12
+ // What a ruleset's `Destroyed` rules ask and do when `unit` is destroyed. `player` destroyed it, or is `null` when no
13
+ // one did (disbanding, founding or joining a city, a spent unit...).
14
+
15
+ // Emits `unit:destroyed` with the unit and the player who destroyed it.
16
+ export const emitDestroyed =
17
+ (engine: Engine) =>
18
+ (unit: Unit, player: Player | null): void => {
19
+ engine.emit('unit:destroyed', unit, player);
20
+ };
21
+
22
+ // Another player destroyed the unit.
23
+ export const destroyedByAnother = (
24
+ unit: Unit,
25
+ player: Player | null
26
+ ): boolean => player !== null && player !== unit.player();
27
+
28
+ // The unit isn't marked destroyed yet: a rule that should act once per unit asks it before `deactivate` runs.
29
+ export const notYetDestroyed = (unit: Unit): boolean => !unit.destroyed();
30
+
31
+ // Records the unit's type as a loss of its player's in `unitLossRegistry`.
32
+ export const countLoss =
33
+ (unitLossRegistry: UnitLossRegistry) =>
34
+ (unit: Unit): void => {
35
+ unitLossRegistry.record(unit.player(), unit.sourceClass() as typeof Unit);
36
+ };
37
+
38
+ // Makes the unit inactive and marks it destroyed.
39
+ export const deactivate = (unit: Unit): void => {
40
+ unit.setActive(false);
41
+ unit.setDestroyed();
42
+ };
43
+
44
+ // Removes the unit's improvements (veteran status, fortification...).
45
+ export const removeImprovements =
46
+ (unitImprovementRegistry: UnitImprovementRegistry) =>
47
+ (unit: Unit): void =>
48
+ unitImprovementRegistry
49
+ .getByUnit(unit)
50
+ .forEach((unitImprovement) =>
51
+ unitImprovementRegistry.unregister(unitImprovement)
52
+ );
53
+
54
+ // Takes a unit off its transport. Nothing clears `Stowed` on its own (its criterion is `() => false`), so a unit taken
55
+ // off a transport, destroyed or not, would otherwise stay stowed aboard nothing.
56
+ export const leaveTransport =
57
+ (transportRegistry: TransportRegistry) =>
58
+ (manifest: TransportManifest): void => {
59
+ const cargo = manifest.unit();
60
+
61
+ transportRegistry.unregister(manifest);
62
+
63
+ if (cargo.busy() instanceof Stowed) {
64
+ cargo.setBusy();
65
+ }
66
+ };
67
+
68
+ // The unit is a transport with something aboard.
69
+ export const carriesCargo =
70
+ (transportRegistry: TransportRegistry) =>
71
+ (unit: Unit): boolean =>
72
+ transportRegistry.getByTransport(unit as unknown as ITransport).length > 0;
73
+
74
+ // Takes everything off the transport (`leaveTransport`). At sea (on `Water`) the cargo goes down with it, destroyed by
75
+ // `player`; in a city it stays in the city.
76
+ export const loseCargo = (transportRegistry: TransportRegistry) => {
77
+ const leave = leaveTransport(transportRegistry);
78
+
79
+ return (unit: Unit, player: Player | null): void =>
80
+ transportRegistry
81
+ .getByTransport(unit as unknown as ITransport)
82
+ // A copy: unregistering changes the list.
83
+ .slice()
84
+ .forEach((manifest: TransportManifest): void => {
85
+ const cargo = manifest.unit();
86
+
87
+ leave(manifest);
88
+
89
+ if (unit.tile().terrain() instanceof Water && !cargo.destroyed()) {
90
+ cargo.destroy(player);
91
+ }
92
+ });
93
+ };
94
+
95
+ // The unit is aboard a transport.
96
+ export const isAboard =
97
+ (transportRegistry: TransportRegistry) =>
98
+ (unit: Unit): boolean =>
99
+ transportRegistry.hasUnit(unit);
100
+
101
+ // Takes the unit off the transport it's aboard (`leaveTransport`).
102
+ export const leaveOwnTransport = (transportRegistry: TransportRegistry) => {
103
+ const leave = leaveTransport(transportRegistry);
104
+
105
+ return (unit: Unit): void => leave(transportRegistry.getByUnit(unit));
106
+ };
@@ -0,0 +1,6 @@
1
+ import Engine from '@civ-clone/core-engine/Engine';
2
+ import Tile from '@civ-clone/core-world/Tile';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+ export declare const emitHalted: (
5
+ engine: Engine
6
+ ) => (unit: Unit, target: Tile) => void;
package/lib/halted.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emitHalted = void 0;
4
+ // Emits `unit:halted` with the unit and its destination: a GoTo stopped next to it, for the player to choose what to
5
+ // do there.
6
+ const emitHalted = (engine) => (unit, target) => {
7
+ engine.emit('unit:halted', unit, target);
8
+ };
9
+ exports.emitHalted = emitHalted;
10
+ //# sourceMappingURL=halted.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"halted.js","sourceRoot":"","sources":["halted.ts"],"names":[],"mappings":";;;AAIA,qHAAqH;AACrH,aAAa;AACN,MAAM,UAAU,GACrB,CAAC,MAAc,EAAE,EAAE,CACnB,CAAC,IAAU,EAAE,MAAY,EAAQ,EAAE;IACjC,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC,CAAC;AAJS,QAAA,UAAU,cAInB"}
package/lib/halted.ts ADDED
@@ -0,0 +1,11 @@
1
+ import Engine from '@civ-clone/core-engine/Engine';
2
+ import Tile from '@civ-clone/core-world/Tile';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+
5
+ // Emits `unit:halted` with the unit and its destination: a GoTo stopped next to it, for the player to choose what to
6
+ // do there.
7
+ export const emitHalted =
8
+ (engine: Engine) =>
9
+ (unit: Unit, target: Tile): void => {
10
+ engine.emit('unit:halted', unit, target);
11
+ };
@@ -0,0 +1,8 @@
1
+ import { PartnerOf } from '@civ-clone/base-unit-action-rules/lib/criteria/partners';
2
+ import Tile from '@civ-clone/core-world/Tile';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+ export declare const fromLand: (unit: Unit, to: Tile, from: Tile) => boolean;
5
+ export declare const toPartnersLand: (
6
+ partnerOf: PartnerOf
7
+ ) => (unit: Unit, to: Tile) => boolean;
8
+ export declare const impassable: () => boolean;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.impassable = exports.toPartnersLand = exports.fromLand = void 0;
4
+ // What a ruleset's `Impassable` rules ask, so that routes (a GoTo, a computer player's route) go round a treaty
5
+ // partner's land and following one never breaks a treaty by accident.
6
+ // The step starts on land.
7
+ const fromLand = (unit, to, from) => from.isLand();
8
+ exports.fromLand = fromLand;
9
+ // `to` is a treaty partner's land: `partnerOf` is the ruleset's territory model.
10
+ const toPartnersLand = (partnerOf) => (unit, to) => partnerOf(unit, to) !== null;
11
+ exports.toPartnersLand = toPartnersLand;
12
+ // The tile is impassable.
13
+ const impassable = () => true;
14
+ exports.impassable = impassable;
15
+ //# sourceMappingURL=impassable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"impassable.js","sourceRoot":"","sources":["impassable.ts"],"names":[],"mappings":";;;AAIA,gHAAgH;AAChH,uEAAuE;AAEvE,2BAA2B;AACpB,MAAM,QAAQ,GAAG,CAAC,IAAU,EAAE,EAAQ,EAAE,IAAU,EAAW,EAAE,CACpE,IAAI,CAAC,MAAM,EAAE,CAAC;AADH,QAAA,QAAQ,YACL;AAEhB,iFAAiF;AAC1E,MAAM,cAAc,GACzB,CAAC,SAAoB,EAAE,EAAE,CACzB,CAAC,IAAU,EAAE,EAAQ,EAAW,EAAE,CAChC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC;AAHpB,QAAA,cAAc,kBAGM;AAEjC,0BAA0B;AACnB,MAAM,UAAU,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC;AAAjC,QAAA,UAAU,cAAuB"}
@@ -0,0 +1,19 @@
1
+ import { PartnerOf } from '@civ-clone/base-unit-action-rules/lib/criteria/partners';
2
+ import Tile from '@civ-clone/core-world/Tile';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+
5
+ // What a ruleset's `Impassable` rules ask, so that routes (a GoTo, a computer player's route) go round a treaty
6
+ // partner's land and following one never breaks a treaty by accident.
7
+
8
+ // The step starts on land.
9
+ export const fromLand = (unit: Unit, to: Tile, from: Tile): boolean =>
10
+ from.isLand();
11
+
12
+ // `to` is a treaty partner's land: `partnerOf` is the ruleset's territory model.
13
+ export const toPartnersLand =
14
+ (partnerOf: PartnerOf) =>
15
+ (unit: Unit, to: Tile): boolean =>
16
+ partnerOf(unit, to) !== null;
17
+
18
+ // The tile is impassable.
19
+ export const impassable = (): boolean => true;
@@ -0,0 +1,12 @@
1
+ import Engine from '@civ-clone/core-engine/Engine';
2
+ import { ITransport } from '@civ-clone/core-unit-transport/Transport';
3
+ import TransportRegistry from '@civ-clone/core-unit-transport/TransportRegistry';
4
+ import UnitLossRegistry from '@civ-clone/core-unit/UnitLossRegistry';
5
+ export declare const emitLostAtSea: (
6
+ engine: Engine
7
+ ) => (unit: ITransport) => void;
8
+ export declare const countLosses: (
9
+ unitLossRegistry: UnitLossRegistry,
10
+ transportRegistry: TransportRegistry
11
+ ) => (transport: ITransport) => void;
12
+ export declare const sink: (unit: ITransport) => void;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sink = exports.countLosses = exports.emitLostAtSea = void 0;
4
+ // What a ruleset's `LostAtSea` rules do when a transport is lost at sea.
5
+ // Emits `unit:lost-at-sea` with the transport.
6
+ const emitLostAtSea = (engine) => (unit) => {
7
+ engine.emit('unit:lost-at-sea', unit);
8
+ };
9
+ exports.emitLostAtSea = emitLostAtSea;
10
+ // Records the transport and everything aboard that isn't destroyed yet as losses of their players, though no one
11
+ // destroyed them. Run it before `sink`, while the manifests still say what's aboard.
12
+ const countLosses = (unitLossRegistry, transportRegistry) => (transport) => [
13
+ transport,
14
+ ...transportRegistry
15
+ .getByTransport(transport)
16
+ .map((manifest) => manifest.unit()),
17
+ ]
18
+ .filter((unit) => !unit.destroyed())
19
+ .forEach((unit) => {
20
+ unitLossRegistry.record(unit.player(), unit.sourceClass());
21
+ });
22
+ exports.countLosses = countLosses;
23
+ // Destroys the transport, by no one.
24
+ const sink = (unit) => unit.destroy(null);
25
+ exports.sink = sink;
26
+ //# sourceMappingURL=lostAtSea.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lostAtSea.js","sourceRoot":"","sources":["lostAtSea.ts"],"names":[],"mappings":";;;AAOA,yEAAyE;AAEzE,+CAA+C;AACxC,MAAM,aAAa,GACxB,CAAC,MAAc,EAAE,EAAE,CACnB,CAAC,IAAgB,EAAQ,EAAE;IACzB,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;AACxC,CAAC,CAAC;AAJS,QAAA,aAAa,iBAItB;AAEJ,iHAAiH;AACjH,sFAAsF;AAC/E,MAAM,WAAW,GACtB,CAAC,gBAAkC,EAAE,iBAAoC,EAAE,EAAE,CAC7E,CAAC,SAAqB,EAAQ,EAAE,CAC9B;IACE,SAA4B;IAC5B,GAAG,iBAAiB;SACjB,cAAc,CAAC,SAAS,CAAC;SACzB,GAAG,CAAC,CAAC,QAA2B,EAAQ,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;CAC/D;KACE,MAAM,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;KAClD,OAAO,CAAC,CAAC,IAAU,EAAQ,EAAE;IAC5B,gBAAgB,CAAC,MAAM,CACrB,IAAI,CAAC,MAAM,EAAE,EACb,IAAI,CAAC,WAAW,EAAiB,CAClC,CAAC;AACJ,CAAC,CAAC,CAAC;AAfI,QAAA,WAAW,eAef;AAET,qCAAqC;AAC9B,MAAM,IAAI,GAAG,CAAC,IAAgB,EAAQ,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAAtD,QAAA,IAAI,QAAkD"}
@@ -0,0 +1,37 @@
1
+ import Engine from '@civ-clone/core-engine/Engine';
2
+ import { ITransport } from '@civ-clone/core-unit-transport/Transport';
3
+ import TransportManifest from '@civ-clone/core-unit-transport/TransportManifest';
4
+ import TransportRegistry from '@civ-clone/core-unit-transport/TransportRegistry';
5
+ import Unit from '@civ-clone/core-unit/Unit';
6
+ import UnitLossRegistry from '@civ-clone/core-unit/UnitLossRegistry';
7
+
8
+ // What a ruleset's `LostAtSea` rules do when a transport is lost at sea.
9
+
10
+ // Emits `unit:lost-at-sea` with the transport.
11
+ export const emitLostAtSea =
12
+ (engine: Engine) =>
13
+ (unit: ITransport): void => {
14
+ engine.emit('unit:lost-at-sea', unit);
15
+ };
16
+
17
+ // Records the transport and everything aboard that isn't destroyed yet as losses of their players, though no one
18
+ // destroyed them. Run it before `sink`, while the manifests still say what's aboard.
19
+ export const countLosses =
20
+ (unitLossRegistry: UnitLossRegistry, transportRegistry: TransportRegistry) =>
21
+ (transport: ITransport): void =>
22
+ [
23
+ transport as unknown as Unit,
24
+ ...transportRegistry
25
+ .getByTransport(transport)
26
+ .map((manifest: TransportManifest): Unit => manifest.unit()),
27
+ ]
28
+ .filter((unit: Unit): boolean => !unit.destroyed())
29
+ .forEach((unit: Unit): void => {
30
+ unitLossRegistry.record(
31
+ unit.player(),
32
+ unit.sourceClass() as typeof Unit
33
+ );
34
+ });
35
+
36
+ // Destroys the transport, by no one.
37
+ export const sink = (unit: ITransport): void => unit.destroy(null);
@@ -0,0 +1,22 @@
1
+ import Player from '@civ-clone/core-player/Player';
2
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+ import UnitRegistry from '@civ-clone/core-unit/UnitRegistry';
5
+ export type UnitPlayerActionType<T extends PlayerAction> = new (
6
+ player: Player,
7
+ unit: Unit
8
+ ) => T;
9
+ export declare const hasActiveUnits: (
10
+ unitRegistry: UnitRegistry
11
+ ) => (player: Player) => boolean;
12
+ export declare const activeUnits: <T extends PlayerAction<any>>(
13
+ unitRegistry: UnitRegistry,
14
+ PlayerActionType: UnitPlayerActionType<T>
15
+ ) => (player: Player) => T[];
16
+ export declare const hasInactiveUnits: (
17
+ unitRegistry: UnitRegistry
18
+ ) => (player: Player) => boolean;
19
+ export declare const inactiveUnits: <T extends PlayerAction<any>>(
20
+ unitRegistry: UnitRegistry,
21
+ PlayerActionType: UnitPlayerActionType<T>
22
+ ) => (player: Player) => T[];
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inactiveUnits = exports.hasInactiveUnits = exports.activeUnits = exports.hasActiveUnits = void 0;
4
+ // The player has an active unit with moves left.
5
+ const hasActiveUnits = (unitRegistry) => (player) => unitRegistry
6
+ .getByPlayer(player)
7
+ .some((unit) => unit.active() && unit.moves().value());
8
+ exports.hasActiveUnits = hasActiveUnits;
9
+ // The player's active units with moves left, as `PlayerActionType`s.
10
+ const activeUnits = (unitRegistry, PlayerActionType) => (player) => unitRegistry
11
+ .getByPlayer(player)
12
+ .filter((unit) => unit.active() && unit.moves().value() > 0)
13
+ .sort((a, b) => (a.waiting() ? 1 : 0) - (b.waiting() ? 1 : 0))
14
+ .map((unit) => new PlayerActionType(player, unit));
15
+ exports.activeUnits = activeUnits;
16
+ // The player has a unit that is inactive or has no moves left.
17
+ const hasInactiveUnits = (unitRegistry) => (player) => unitRegistry
18
+ .getByPlayer(player)
19
+ .some((unit) => !unit.active() || !unit.moves().value());
20
+ exports.hasInactiveUnits = hasInactiveUnits;
21
+ // The player's units that are inactive or have no moves left, as `PlayerActionType`s.
22
+ const inactiveUnits = (unitRegistry, PlayerActionType) => (player) => unitRegistry
23
+ .getByPlayer(player)
24
+ .filter((unit) => !unit.active() || unit.moves().value() === 0)
25
+ .sort((a, b) => (a.waiting() ? 1 : 0) - (b.waiting() ? 1 : 0))
26
+ .map((unit) => new PlayerActionType(player, unit));
27
+ exports.inactiveUnits = inactiveUnits;
28
+ //# sourceMappingURL=playerAction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playerAction.js","sourceRoot":"","sources":["playerAction.ts"],"names":[],"mappings":";;;AAeA,iDAAiD;AAC1C,MAAM,cAAc,GACzB,CAAC,YAA0B,EAAE,EAAE,CAC/B,CAAC,MAAc,EAAW,EAAE,CAC1B,YAAY;KACT,WAAW,CAAC,MAAM,CAAC;KACnB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;AALhD,QAAA,cAAc,kBAKkC;AAE7D,qEAAqE;AAC9D,MAAM,WAAW,GACtB,CACE,YAA0B,EAC1B,gBAAyC,EACzC,EAAE,CACJ,CAAC,MAAc,EAAO,EAAE,CACtB,YAAY;KACT,WAAW,CAAC,MAAM,CAAC;KACnB,MAAM,CACL,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CACnE;KACA,IAAI,CACH,CAAC,CAAO,EAAE,CAAO,EAAU,EAAE,CAC3B,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAChD;KACA,GAAG,CAAC,CAAC,IAAU,EAAK,EAAE,CAAC,IAAI,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AAfrD,QAAA,WAAW,eAe0C;AAElE,+DAA+D;AACxD,MAAM,gBAAgB,GAC3B,CAAC,YAA0B,EAAE,EAAE,CAC/B,CAAC,MAAc,EAAW,EAAE,CAC1B,YAAY;KACT,WAAW,CAAC,MAAM,CAAC;KACnB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;AALlD,QAAA,gBAAgB,oBAKkC;AAE/D,sFAAsF;AAC/E,MAAM,aAAa,GACxB,CACE,YAA0B,EAC1B,gBAAyC,EACzC,EAAE,CACJ,CAAC,MAAc,EAAO,EAAE,CACtB,YAAY;KACT,WAAW,CAAC,MAAM,CAAC;KACnB,MAAM,CAAC,CAAC,IAAU,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;KACpE,IAAI,CACH,CAAC,CAAO,EAAE,CAAO,EAAU,EAAE,CAC3B,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAChD;KACA,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AAb5C,QAAA,aAAa,iBAa+B"}
@@ -0,0 +1,64 @@
1
+ import Player from '@civ-clone/core-player/Player';
2
+ import PlayerAction from '@civ-clone/core-player/PlayerAction';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+ import UnitRegistry from '@civ-clone/core-unit/UnitRegistry';
5
+
6
+ // What a ruleset's player `Action` rules ask and offer: the player's units that can take orders this turn, and those
7
+ // that can't. Each list puts waiting units last and otherwise keeps the registry's order. `PlayerActionType` is the
8
+ // player action each unit is offered as (an active unit, an inactive one).
9
+
10
+ // The constructor of a player action for one unit.
11
+ export type UnitPlayerActionType<T extends PlayerAction> = new (
12
+ player: Player,
13
+ unit: Unit
14
+ ) => T;
15
+
16
+ // The player has an active unit with moves left.
17
+ export const hasActiveUnits =
18
+ (unitRegistry: UnitRegistry) =>
19
+ (player: Player): boolean =>
20
+ unitRegistry
21
+ .getByPlayer(player)
22
+ .some((unit) => unit.active() && unit.moves().value());
23
+
24
+ // The player's active units with moves left, as `PlayerActionType`s.
25
+ export const activeUnits =
26
+ <T extends PlayerAction>(
27
+ unitRegistry: UnitRegistry,
28
+ PlayerActionType: UnitPlayerActionType<T>
29
+ ) =>
30
+ (player: Player): T[] =>
31
+ unitRegistry
32
+ .getByPlayer(player)
33
+ .filter(
34
+ (unit: Unit): boolean => unit.active() && unit.moves().value() > 0
35
+ )
36
+ .sort(
37
+ (a: Unit, b: Unit): number =>
38
+ (a.waiting() ? 1 : 0) - (b.waiting() ? 1 : 0)
39
+ )
40
+ .map((unit: Unit): T => new PlayerActionType(player, unit));
41
+
42
+ // The player has a unit that is inactive or has no moves left.
43
+ export const hasInactiveUnits =
44
+ (unitRegistry: UnitRegistry) =>
45
+ (player: Player): boolean =>
46
+ unitRegistry
47
+ .getByPlayer(player)
48
+ .some((unit) => !unit.active() || !unit.moves().value());
49
+
50
+ // The player's units that are inactive or have no moves left, as `PlayerActionType`s.
51
+ export const inactiveUnits =
52
+ <T extends PlayerAction>(
53
+ unitRegistry: UnitRegistry,
54
+ PlayerActionType: UnitPlayerActionType<T>
55
+ ) =>
56
+ (player: Player): T[] =>
57
+ unitRegistry
58
+ .getByPlayer(player)
59
+ .filter((unit: Unit) => !unit.active() || unit.moves().value() === 0)
60
+ .sort(
61
+ (a: Unit, b: Unit): number =>
62
+ (a.waiting() ? 1 : 0) - (b.waiting() ? 1 : 0)
63
+ )
64
+ .map((unit) => new PlayerActionType(player, unit));
@@ -0,0 +1,2 @@
1
+ import Unit from '@civ-clone/core-unit/Unit';
2
+ export declare const sleepAboard: (unit: Unit) => void;
package/lib/stowed.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sleepAboard = void 0;
4
+ const Sleep_1 = require("@civ-clone/base-unit-action-sleep/Sleep");
5
+ // A unit that has just boarded a transport sleeps aboard it. Returns what `unit.action` returns.
6
+ const sleepAboard = (unit) => unit.action(new Sleep_1.default(unit.tile(), unit.tile(), unit));
7
+ exports.sleepAboard = sleepAboard;
8
+ //# sourceMappingURL=stowed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stowed.js","sourceRoot":"","sources":["stowed.ts"],"names":[],"mappings":";;;AAAA,mEAA4D;AAG5D,iGAAiG;AAC1F,MAAM,WAAW,GAAG,CAAC,IAAU,EAAE,EAAE,CACxC,IAAI,CAAC,MAAM,CAAC,IAAI,eAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;AAD5C,QAAA,WAAW,eACiC"}
package/lib/stowed.ts ADDED
@@ -0,0 +1,6 @@
1
+ import Sleep from '@civ-clone/base-unit-action-sleep/Sleep';
2
+ import Unit from '@civ-clone/core-unit/Unit';
3
+
4
+ // A unit that has just boarded a transport sleeps aboard it. Returns what `unit.action` returns.
5
+ export const sleepAboard = (unit: Unit) =>
6
+ unit.action(new Sleep(unit.tile(), unit.tile(), unit));
@@ -0,0 +1,6 @@
1
+ import City from '@civ-clone/core-city/City';
2
+ import Engine from '@civ-clone/core-engine/Engine';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+ export declare const emitUnsupported: (
5
+ engine: Engine
6
+ ) => (city: City, unit: Unit) => void;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emitUnsupported = void 0;
4
+ // Emits `unit:unsupported` with the city that could no longer support the unit, and the unit.
5
+ const emitUnsupported = (engine) => (city, unit) => {
6
+ engine.emit('unit:unsupported', city, unit);
7
+ };
8
+ exports.emitUnsupported = emitUnsupported;
9
+ //# sourceMappingURL=unsupported.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsupported.js","sourceRoot":"","sources":["unsupported.ts"],"names":[],"mappings":";;;AAIA,8FAA8F;AACvF,MAAM,eAAe,GAC1B,CAAC,MAAc,EAAE,EAAE,CACnB,CAAC,IAAU,EAAE,IAAU,EAAQ,EAAE;IAC/B,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC,CAAC;AAJS,QAAA,eAAe,mBAIxB"}
@@ -0,0 +1,10 @@
1
+ import City from '@civ-clone/core-city/City';
2
+ import Engine from '@civ-clone/core-engine/Engine';
3
+ import Unit from '@civ-clone/core-unit/Unit';
4
+
5
+ // Emits `unit:unsupported` with the city that could no longer support the unit, and the unit.
6
+ export const emitUnsupported =
7
+ (engine: Engine) =>
8
+ (city: City, unit: Unit): void => {
9
+ engine.emit('unit:unsupported', city, unit);
10
+ };
@@ -0,0 +1,11 @@
1
+ import Unit from '@civ-clone/core-unit/Unit';
2
+ export declare const hasMovesFor: (unit: Unit, movementCost: number) => boolean;
3
+ export declare const spendMoves: (unit: Unit, movementCost: number) => boolean;
4
+ export declare const shortOfMovesFor: (
5
+ unit: Unit,
6
+ movementCost: number
7
+ ) => boolean;
8
+ export declare const spendShort: (
9
+ shortfallFactor: number,
10
+ randomNumberGenerator: () => number
11
+ ) => (unit: Unit, movementCost: number) => boolean;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.spendShort = exports.shortOfMovesFor = exports.spendMoves = exports.hasMovesFor = void 0;
4
+ const moveShortfall_1 = require("@civ-clone/base-unit-action-rules/lib/moveShortfall");
5
+ // What a ruleset's `ValidateMove` rules ask and do when `unit` makes a step costing `movementCost` moves.
6
+ // The unit has the moves the step costs.
7
+ const hasMovesFor = (unit, movementCost) => unit.moves().value() >= movementCost;
8
+ exports.hasMovesFor = hasMovesFor;
9
+ // Spends the moves the step costs. The step succeeds.
10
+ const spendMoves = (unit, movementCost) => {
11
+ unit.moves().subtract(movementCost);
12
+ return true;
13
+ };
14
+ exports.spendMoves = spendMoves;
15
+ // The unit is short of the moves the step costs.
16
+ const shortOfMovesFor = (unit, movementCost) => unit.moves().value() < movementCost;
17
+ exports.shortOfMovesFor = shortOfMovesFor;
18
+ // Spends all the unit's remaining moves on a step it is short of moves for, and says whether it gets there
19
+ // (`succeedsShort`, with `shortfallFactor` and a draw of `randomNumberGenerator`).
20
+ const spendShort = (shortfallFactor, randomNumberGenerator) => (unit, movementCost) => {
21
+ const remainingMoves = unit.moves().value();
22
+ unit.moves().set(0);
23
+ return (0, moveShortfall_1.succeedsShort)(remainingMoves, movementCost, shortfallFactor, randomNumberGenerator);
24
+ };
25
+ exports.spendShort = spendShort;
26
+ //# sourceMappingURL=validateMove.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateMove.js","sourceRoot":"","sources":["validateMove.ts"],"names":[],"mappings":";;;AACA,uFAAoF;AAEpF,0GAA0G;AAE1G,yCAAyC;AAClC,MAAM,WAAW,GAAG,CAAC,IAAU,EAAE,YAAoB,EAAW,EAAE,CACvE,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,YAAY,CAAC;AAD1B,QAAA,WAAW,eACe;AAEvC,sDAAsD;AAC/C,MAAM,UAAU,GAAG,CAAC,IAAU,EAAE,YAAoB,EAAW,EAAE;IACtE,IAAI,CAAC,KAAK,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAEpC,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAJW,QAAA,UAAU,cAIrB;AAEF,iDAAiD;AAC1C,MAAM,eAAe,GAAG,CAAC,IAAU,EAAE,YAAoB,EAAW,EAAE,CAC3E,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,YAAY,CAAC;AADzB,QAAA,eAAe,mBACU;AAEtC,2GAA2G;AAC3G,oFAAoF;AAC7E,MAAM,UAAU,GACrB,CAAC,eAAuB,EAAE,qBAAmC,EAAE,EAAE,CACjE,CAAC,IAAU,EAAE,YAAoB,EAAW,EAAE;IAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC;IAE5C,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAEpB,OAAO,IAAA,6BAAa,EAClB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,qBAAqB,CACtB,CAAC;AACJ,CAAC,CAAC;AAbS,QAAA,UAAU,cAanB"}
@@ -0,0 +1,36 @@
1
+ import Unit from '@civ-clone/core-unit/Unit';
2
+ import { succeedsShort } from '@civ-clone/base-unit-action-rules/lib/moveShortfall';
3
+
4
+ // What a ruleset's `ValidateMove` rules ask and do when `unit` makes a step costing `movementCost` moves.
5
+
6
+ // The unit has the moves the step costs.
7
+ export const hasMovesFor = (unit: Unit, movementCost: number): boolean =>
8
+ unit.moves().value() >= movementCost;
9
+
10
+ // Spends the moves the step costs. The step succeeds.
11
+ export const spendMoves = (unit: Unit, movementCost: number): boolean => {
12
+ unit.moves().subtract(movementCost);
13
+
14
+ return true;
15
+ };
16
+
17
+ // The unit is short of the moves the step costs.
18
+ export const shortOfMovesFor = (unit: Unit, movementCost: number): boolean =>
19
+ unit.moves().value() < movementCost;
20
+
21
+ // Spends all the unit's remaining moves on a step it is short of moves for, and says whether it gets there
22
+ // (`succeedsShort`, with `shortfallFactor` and a draw of `randomNumberGenerator`).
23
+ export const spendShort =
24
+ (shortfallFactor: number, randomNumberGenerator: () => number) =>
25
+ (unit: Unit, movementCost: number): boolean => {
26
+ const remainingMoves = unit.moves().value();
27
+
28
+ unit.moves().set(0);
29
+
30
+ return succeedsShort(
31
+ remainingMoves,
32
+ movementCost,
33
+ shortfallFactor,
34
+ randomNumberGenerator
35
+ );
36
+ };
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "@civ-clone/base-unit-lifecycle",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Building blocks for a ruleset's unit lifecycle rules: what happens when a unit is created, activated, moved, stowed aboard a transport, halted, defeated, destroyed, lost at sea or left unsupported, and which of a player's units can take orders. The ruleset keeps its own rules, ids and order, and builds their criteria and effects from these.",
5
+ "repository": "git@github.com:civ-clone/base-unit-lifecycle.git",
6
+ "keywords": [
7
+ "typescript",
8
+ "civilization"
9
+ ],
10
+ "author": "dom111 <dom111@users.noreply.github.com>",
11
+ "license": "MIT",
12
+ "private": false,
13
+ "scripts": {
14
+ "build": "npm run ts:compile && npm run prettier:format",
15
+ "prettier:check": "prettier --config .prettierrc --check '**/*.ts'",
16
+ "prettier:format": "prettier --config .prettierrc '**/*.ts' --write",
17
+ "test": "ts-mocha ./tests/*.test.ts",
18
+ "ts:compile": "tsc --build tsconfig.json"
19
+ },
20
+ "devDependencies": {
21
+ "@types/chai": "^4.0.0",
22
+ "@types/mocha": "^10.0.0",
23
+ "@types/node": "^16.0.0",
24
+ "chai": "^4.0.0",
25
+ "mocha": "^10.0.0",
26
+ "prettier": "^2.8.8",
27
+ "ts-mocha": "^10.0.0",
28
+ "typescript": "^4.0.0"
29
+ },
30
+ "dependencies": {
31
+ "@civ-clone/base-unit-action-embark": "^0.1.0",
32
+ "@civ-clone/base-unit-action-rules": "^0.1.5",
33
+ "@civ-clone/base-unit-action-sleep": "^0.1.0",
34
+ "@civ-clone/base-unit-trait-carries": "^0.1.0",
35
+ "@civ-clone/core-city": "^0.1.19",
36
+ "@civ-clone/core-engine": "^0.1.0",
37
+ "@civ-clone/core-player": "^0.1.0",
38
+ "@civ-clone/core-rule": "^0.1.9",
39
+ "@civ-clone/core-terrain": "^0.1.0",
40
+ "@civ-clone/core-unit": "^0.1.31",
41
+ "@civ-clone/core-unit-improvement": "^0.1.0",
42
+ "@civ-clone/core-unit-transport": "^0.1.8",
43
+ "@civ-clone/core-world": "^0.1.0"
44
+ }
45
+ }