@civ-clone/base-unit-lifecycle 0.1.2 → 0.1.3

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 CHANGED
@@ -42,9 +42,17 @@ returns the rules with ids `<prefix>:unit/<type>/<name>`, the same names and ord
42
42
  `defeated`, `destroyed`, `expectedMovementCost`, `halted`, `impassable`, `lostAtSea`, `playerAction`, `stowed`,
43
43
  `transferred`, `unsupported`, `validateMove`), so each list can be registered where the ruleset wants it.
44
44
  - `lib/movedRules`: `movedRules(prefix, deps, options)`, the `Moved` rules: cargo, collecting, taking off, a
45
- `CoastBound` unit lost at sea, `AttackEndsTurn`, and a broken peace treaty.
45
+ `CoastBound` unit lost at sea, `AttackEndsTurn`, and a broken peace treaty. `coastBoundLoss` is the chance a
46
+ `CoastBound` unit out of sight of land is lost: a number (always one draw, as civ1-unit's rule), or a function of the
47
+ unit and its tile (Civ2's odds by advance and wonder; a chance of 0 draws nothing). The optional `nearLand(tile)`
48
+ says what is in sight of land: by default `Tile#isCoast`, or `adjacentToLand`, Civ2's "the tile or a neighbour is
49
+ land" (the same at sea).
46
50
  - `lib/detonatedRules`: `detonatedRules(prefix, deps, options)`, what a nuclear detonation does to the 9 squares.
47
- `sparesUnits` spares a city's units (Civ1's SDI Defense); an optional `vetoes` stops a detonation altogether.
51
+ `sparesUnits` spares a city's units (Civ1's SDI Defense); an optional `vetoes` stops a detonation altogether, and
52
+ the weapon is used up all the same. `protectedByCityWithin(cityRegistry, radius, protects, distance?)` builds a
53
+ `vetoes` from the cities within `radius` of the target (Civ2's SDI Defense of another civ, radius 3), measured by
54
+ `distance(target, cityTile)`: by default `Tile#distanceFrom`, which is straight-line on the square and staggered
55
+ grids, so Civ2 passes its own `map_distance`.
48
56
  - `lib/diplomatRules`: `diplomatRules(prefix, deps)`, the embassy, investigation and leader-meeting rules.
49
57
  - `lib/treatyPartner`: `treatyPartner(improvements, deps)`, a `partnerOf` for `impassable` and the treaty actions: the
50
58
  partner working an improved tile.
@@ -20,6 +20,19 @@ export declare const halveCity: (
20
20
  unit: Unit,
21
21
  cityGrowthRegistry?: CityGrowthRegistry
22
22
  ) => void;
23
+ /**
24
+ * A `vetoes` for cities that defend the squares around them: a detonation over `tile` is stopped when a city within
25
+ * `radius` of it `protects` it from `unit`, such as another player's city with a missile defence. The whole attack is
26
+ * stopped, and the weapon is used up all the same. `distance` measures from the target to a city: by default the map's
27
+ * own `Tile#distanceFrom` (straight-line on the square and staggered grids), or the ruleset's own measure, such as a
28
+ * count of moves.
29
+ */
30
+ export declare const protectedByCityWithin: (
31
+ cityRegistry: CityRegistry,
32
+ radius: number,
33
+ protects: (city: City, unit: Unit) => boolean,
34
+ distance?: (from: Tile, to: Tile) => number
35
+ ) => (unit: Unit, tile: Tile) => boolean;
23
36
  export interface DetonatedDependencies {
24
37
  cityGrowthRegistry?: CityGrowthRegistry;
25
38
  cityRegistry?: CityRegistry;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detonatedRules = exports.halveCity = exports.HALVE_CITIES_PRIORITY = void 0;
3
+ exports.detonatedRules = exports.protectedByCityWithin = exports.halveCity = exports.HALVE_CITIES_PRIORITY = void 0;
4
4
  const CityGrowthRegistry_1 = require("@civ-clone/core-city-growth/CityGrowthRegistry");
5
5
  const CityRegistry_1 = require("@civ-clone/core-city/CityRegistry");
6
6
  const Engine_1 = require("@civ-clone/core-engine/Engine");
@@ -27,6 +27,17 @@ const halveCity = (city, unit, cityGrowthRegistry = CityGrowthRegistry_1.instanc
27
27
  }
28
28
  };
29
29
  exports.halveCity = halveCity;
30
+ /**
31
+ * A `vetoes` for cities that defend the squares around them: a detonation over `tile` is stopped when a city within
32
+ * `radius` of it `protects` it from `unit`, such as another player's city with a missile defence. The whole attack is
33
+ * stopped, and the weapon is used up all the same. `distance` measures from the target to a city: by default the map's
34
+ * own `Tile#distanceFrom` (straight-line on the square and staggered grids), or the ruleset's own measure, such as a
35
+ * count of moves.
36
+ */
37
+ const protectedByCityWithin = (cityRegistry, radius, protects, distance = (from, to) => from.distanceFrom(to)) => (unit, tile) => cityRegistry
38
+ .entries()
39
+ .some((city) => distance(tile, city.tile()) <= radius && protects(city, unit));
40
+ exports.protectedByCityWithin = protectedByCityWithin;
30
41
  /**
31
42
  * A ruleset's `Detonated` rules, with ids `<prefix>:unit/detonated/<name>` as civ1-unit's are, over the square a
32
43
  * nuclear weapon detonates over and the 8 around it: tell the client, destroy every unit (but those `sparesUnits`
@@ -1 +1 @@
1
- {"version":3,"file":"detonatedRules.js","sourceRoot":"","sources":["detonatedRules.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"detonatedRules.js","sourceRoot":"","sources":["detonatedRules.ts"],"names":[],"mappings":";;;AAAA,uFAGwD;AACxD,oEAG2C;AAC3C,0DAGuC;AACvC,oEAG2C;AAC3C,sGAGkE;AAClE,oEAG2C;AAE3C,oEAA6D;AAC7D,0FAAmF;AACnF,wDAAiD;AACjD,+DAAwD;AACxD,6DAAsD;AACtD,4DAAqD;AAIrD,wDAAiE;AAEjE;;;GAGG;AACU,QAAA,qBAAqB,GAAG,IAAI,CAAC;AAE1C,4GAA4G;AACrG,MAAM,SAAS,GAAG,CACvB,IAAU,EACV,IAAU,EACV,qBAAyC,6BAA0B,EAC7D,EAAE;IACR,MAAM,UAAU,GAAG,kBAAkB,CAAC,SAAS,CAAC,IAAI,CAAC,EACnD,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAE3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE;QAC7B,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;KACzB;AACH,CAAC,CAAC;AAXW,QAAA,SAAS,aAWpB;AAEF;;;;;;GAMG;AACI,MAAM,qBAAqB,GAChC,CACE,YAA0B,EAC1B,MAAc,EACd,QAA6C,EAC7C,WAA6C,CAAC,IAAU,EAAE,EAAQ,EAAE,EAAE,CACpE,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,EACvB,EAAE,CACJ,CAAC,IAAU,EAAE,IAAU,EAAW,EAAE,CAClC,YAAY;KACT,OAAO,EAAE;KACT,IAAI,CACH,CAAC,IAAU,EAAW,EAAE,CACtB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,MAAM,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAChE,CAAC;AAdK,QAAA,qBAAqB,yBAc1B;AAwBR;;;;GAIG;AACI,MAAM,cAAc,GAAG,CAC5B,MAAc,EACd,EACE,kBAAkB,GAAG,6BAA0B,EAC/C,YAAY,GAAG,uBAAoB,EACnC,MAAM,GAAG,iBAAc,EACvB,qBAAqB,GAAG,sBAAW,EACnC,YAAY,GAAG,uBAAoB,EACnC,uBAAuB,GAAG,kCAA+B,EACzD,YAAY,GAAG,uBAAoB,GACb,EACxB,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAoB,EACtD,EAAE;IACf,MAAM,EAAE,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,MAAM,mBAAmB,IAAI,EAAE,EACrE,IAAI,GAAG,CAAC,IAAU,EAAU,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;IAC9D,8GAA8G;IAC9G,sHAAsH;IACtH,MAAM,GAAG,IAAI,OAAO,EAAiB,EACrC,OAAO,GAAG,CAAC,IAAU,EAAE,IAAU,EAAW,EAAE,WAC5C,OAAA,MAAM,KAAK,SAAS,IAAI,CAAC,MAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,mCAAI,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA,EAAA,CAAC;IAErE,OAAO;QACL,IAAI,mBAAS;QACX,uEAAuE;QACvE,EAAE,CAAC,MAAM,CAAC,EACV,IAAI,cAAI,EAAE,EACV,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAE,IAAU,EAAQ,EAAE;YAC1C,IAAI,MAAM,KAAK,SAAS,EAAE;gBACxB,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;aACtC;YAED,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACrD,CAAC,CAAC,CACH;QAED,IAAI,mBAAS,CACX,EAAE,CAAC,eAAe,CAAC,EACnB,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAE,IAAU,EAAQ,EAAE,CAC1C,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;YACjB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,QAAc,EAAQ,EAAE;gBAC1C,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;gBAE9C,IAAI,IAAI,KAAK,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE;oBACtC,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;oBAE9C,OAAO;iBACR;gBAED,YAAY;qBACT,SAAS,CAAC,QAAQ,CAAC;qBACnB,OAAO,CAAC,CAAC,QAAc,EAAQ,EAAE;oBAChC,gGAAgG;oBAChG,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE;wBACxB,OAAO;qBACR;oBAED,YAAY,CAAC,OAAO,CAAC,mBAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC3D,CAAC,CAAC,CAAC;YACP,CAAC,CAAC,CACP,CACF;QAED,IAAI,mBAAS,CACX,EAAE,CAAC,cAAc,CAAC,EAClB,IAAI,kBAAQ,CAAC,6BAAqB,CAAC,EACnC,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAE,IAAU,EAAQ,EAAE,CAC1C,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;YACjB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,QAAc,EAAQ,EAAE;gBAC1C,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;gBAE9C,IAAI,IAAI,KAAK,IAAI,EAAE;oBACjB,OAAO;iBACR;gBAED,IAAA,iBAAS,EAAC,IAAI,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC;YAC5C,CAAC,CAAC,CACP,CACF;QAED,IAAI,mBAAS,CACX,EAAE,CAAC,SAAS,CAAC,EACb,IAAI,aAAG,EAAE,EACT,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAE,IAAU,EAAQ,EAAE,CAC1C,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;YACjB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;iBACP,MAAM,CACL,CAAC,QAAc,EAAW,EAAE,CAC1B,QAAQ,CAAC,MAAM,EAAE;gBACjB,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI;gBACzC,CAAC,uBAAuB;qBACrB,SAAS,CAAC,QAAQ,CAAC;qBACnB,IAAI,CACH,CAAC,WAA4B,EAAW,EAAE,CACxC,WAAW,YAAY,SAAS,CACnC,CACN;iBACA,OAAO,CAAC,CAAC,QAAc,EAAQ,EAAE;gBAChC,IAAI,qBAAqB,EAAE,IAAI,aAAa,EAAE;oBAC5C,OAAO;iBACR;gBAED,4DAA4D;gBAC5D,IAAI,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;YACxC,CAAC,CAAC,CACT,CACF;KACF,CAAC;AACJ,CAAC,CAAC;AA9GW,QAAA,cAAc,kBA8GzB;AAEF,kBAAe,sBAAc,CAAC"}
@@ -54,6 +54,29 @@ export const halveCity = (
54
54
  }
55
55
  };
56
56
 
57
+ /**
58
+ * A `vetoes` for cities that defend the squares around them: a detonation over `tile` is stopped when a city within
59
+ * `radius` of it `protects` it from `unit`, such as another player's city with a missile defence. The whole attack is
60
+ * stopped, and the weapon is used up all the same. `distance` measures from the target to a city: by default the map's
61
+ * own `Tile#distanceFrom` (straight-line on the square and staggered grids), or the ruleset's own measure, such as a
62
+ * count of moves.
63
+ */
64
+ export const protectedByCityWithin =
65
+ (
66
+ cityRegistry: CityRegistry,
67
+ radius: number,
68
+ protects: (city: City, unit: Unit) => boolean,
69
+ distance: (from: Tile, to: Tile) => number = (from: Tile, to: Tile) =>
70
+ from.distanceFrom(to)
71
+ ) =>
72
+ (unit: Unit, tile: Tile): boolean =>
73
+ cityRegistry
74
+ .entries()
75
+ .some(
76
+ (city: City): boolean =>
77
+ distance(tile, city.tile()) <= radius && protects(city, unit)
78
+ );
79
+
57
80
  export interface DetonatedDependencies {
58
81
  cityGrowthRegistry?: CityGrowthRegistry;
59
82
  cityRegistry?: CityRegistry;
@@ -6,6 +6,8 @@ import { TransportRegistry } from '@civ-clone/core-unit-transport/TransportRegis
6
6
  import { UnitRegistry } from '@civ-clone/core-unit/UnitRegistry';
7
7
  import Action from '@civ-clone/core-unit/Action';
8
8
  import Moved from '@civ-clone/core-unit/Rules/Moved';
9
+ import Tile from '@civ-clone/core-world/Tile';
10
+ import Unit from '@civ-clone/core-unit/Unit';
9
11
  type ActionType = abstract new (...args: any[]) => Action;
10
12
  export interface MovedDependencies {
11
13
  cityRegistry?: CityRegistry;
@@ -21,8 +23,11 @@ export interface MovedOptions {
21
23
  Disembark: ActionType;
22
24
  attacks: ActionType[];
23
25
  noVendetta: ActionType[];
24
- coastBoundLoss: number;
26
+ coastBoundLoss: number | CoastBoundLoss;
27
+ nearLand?: (tile: Tile) => boolean;
25
28
  }
29
+ export type CoastBoundLoss = (unit: Unit, tile: Tile) => number;
30
+ export declare const adjacentToLand: (tile: Tile) => boolean;
26
31
  /**
27
32
  * A ruleset's `Moved` rules, in order, with ids `<prefix>:unit/moved/<name>` as civ1-unit's are: telling the client,
28
33
  * vision, ending a unit's moves, transports carrying and collecting their cargo, leaving a transport, a `CoastBound`
@@ -39,6 +44,13 @@ export declare const movedRules: (
39
44
  transportRegistry,
40
45
  unitRegistry,
41
46
  }: MovedDependencies,
42
- { Move, Disembark, attacks, noVendetta, coastBoundLoss }: MovedOptions
47
+ {
48
+ Move,
49
+ Disembark,
50
+ attacks,
51
+ noVendetta,
52
+ coastBoundLoss,
53
+ nearLand,
54
+ }: MovedOptions
43
55
  ) => Moved[];
44
56
  export default movedRules;
package/lib/movedRules.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.movedRules = void 0;
3
+ exports.movedRules = exports.adjacentToLand = void 0;
4
4
  const CityRegistry_1 = require("@civ-clone/core-city/CityRegistry");
5
5
  const Engine_1 = require("@civ-clone/core-engine/Engine");
6
6
  const InteractionRegistry_1 = require("@civ-clone/core-diplomacy/InteractionRegistry");
@@ -22,12 +22,26 @@ const Sleeping_1 = require("@civ-clone/base-unit-action-sleep/Rules/Sleeping");
22
22
  const Stowed_1 = require("@civ-clone/base-unit-action-embark/Busy/Stowed");
23
23
  const holds_1 = require("@civ-clone/library-diplomacy/lib/holds");
24
24
  const core_random_1 = require("@civ-clone/core-random");
25
+ // In sight of land: the tile or any of its neighbours is land. For a unit at sea that is `Tile#isCoast`; it differs
26
+ // only on land (a unit in a city is on land, so it is safe either way).
27
+ const adjacentToLand = (tile) => tile.isLand() ||
28
+ tile.getNeighbours().some((neighbour) => neighbour.isLand());
29
+ exports.adjacentToLand = adjacentToLand;
30
+ // Whether a `CoastBound` unit ending its move on `tile` is lost: one draw from `randomNumberGenerator`, lost when it is
31
+ // no more than the chance.
32
+ const lostAtSea = (randomNumberGenerator, coastBoundLoss, unit, tile) => {
33
+ if (typeof coastBoundLoss === 'number') {
34
+ return randomNumberGenerator() <= coastBoundLoss;
35
+ }
36
+ const chance = coastBoundLoss(unit, tile);
37
+ return chance > 0 && randomNumberGenerator() <= chance;
38
+ };
25
39
  /**
26
40
  * A ruleset's `Moved` rules, in order, with ids `<prefix>:unit/moved/<name>` as civ1-unit's are: telling the client,
27
41
  * vision, ending a unit's moves, transports carrying and collecting their cargo, leaving a transport, a `CoastBound`
28
42
  * unit lost at sea, an `AttackEndsTurn` unit's turn ending, and a broken peace treaty.
29
43
  */
30
- const movedRules = (prefix, { cityRegistry = CityRegistry_1.instance, engine = Engine_1.instance, interactionRegistry = InteractionRegistry_1.instance, randomNumberGenerator = core_random_1.instance, ruleRegistry = RuleRegistry_1.instance, transportRegistry = TransportRegistry_1.instance, unitRegistry = UnitRegistry_1.instance, }, { Move, Disembark, attacks, noVendetta, coastBoundLoss }) => {
44
+ const movedRules = (prefix, { cityRegistry = CityRegistry_1.instance, engine = Engine_1.instance, interactionRegistry = InteractionRegistry_1.instance, randomNumberGenerator = core_random_1.instance, ruleRegistry = RuleRegistry_1.instance, transportRegistry = TransportRegistry_1.instance, unitRegistry = UnitRegistry_1.instance, }, { Move, Disembark, attacks, noVendetta, coastBoundLoss, nearLand = (tile) => tile.isCoast(), }) => {
31
45
  const id = (name) => `${prefix}:unit/moved/${name}`,
32
46
  // Stows each of `units` aboard `transport`, as `Embark` and `LandAircraft` do, until it is full.
33
47
  collect = (transport, units) => units.forEach((unit) => {
@@ -96,7 +110,7 @@ const movedRules = (prefix, { cityRegistry = CityRegistry_1.instance, engine = E
96
110
  unit.setBusy();
97
111
  }
98
112
  })),
99
- new Moved_1.default(id('trireme-lost-at-sea'), new Criterion_1.default((unit) => unit.hasTrait(CoastBound_1.default)), new Criterion_1.default((unit) => unit.moves().value() === 0), new Criterion_1.default((unit) => !unit.tile().isCoast()), new Criterion_1.default(() => randomNumberGenerator() <= coastBoundLoss), new Effect_1.default((unit) => {
113
+ new Moved_1.default(id('trireme-lost-at-sea'), new Criterion_1.default((unit) => unit.hasTrait(CoastBound_1.default)), new Criterion_1.default((unit) => unit.moves().value() === 0), new Criterion_1.default((unit) => !nearLand(unit.tile())), new Criterion_1.default((unit) => lostAtSea(randomNumberGenerator, coastBoundLoss, unit, unit.tile())), new Effect_1.default((unit) => {
100
114
  ruleRegistry.process(LostAtSea_1.default, unit);
101
115
  })),
102
116
  new Moved_1.default(
@@ -1 +1 @@
1
- {"version":3,"file":"movedRules.js","sourceRoot":"","sources":["movedRules.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"movedRules.js","sourceRoot":"","sources":["movedRules.ts"],"names":[],"mappings":"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"}
package/lib/movedRules.ts CHANGED
@@ -62,10 +62,40 @@ export interface MovedOptions {
62
62
  attacks: ActionType[];
63
63
  // Actions with an enemy that end a peace treaty without a grudge (walking onto, or pillaging, the partner's land).
64
64
  noVendetta: ActionType[];
65
- // The chance a `CoastBound` unit that ends its move out of sight of land is lost.
66
- coastBoundLoss: number;
65
+ // The chance a `CoastBound` unit that ends its move out of sight of land is lost: a number, or a function of the
66
+ // unit and the tile it ended on (odds that change with the player's advances or wonders). A function's chance of 0
67
+ // or less draws nothing and loses nothing; a number always draws.
68
+ coastBoundLoss: number | CoastBoundLoss;
69
+ // Whether a `CoastBound` unit that ends its move on `tile` is in sight of land, and so safe. By default the tile is
70
+ // on the coast (`Tile#isCoast`); `adjacentToLand` is the other common test.
71
+ nearLand?: (tile: Tile) => boolean;
67
72
  }
68
73
 
74
+ export type CoastBoundLoss = (unit: Unit, tile: Tile) => number;
75
+
76
+ // In sight of land: the tile or any of its neighbours is land. For a unit at sea that is `Tile#isCoast`; it differs
77
+ // only on land (a unit in a city is on land, so it is safe either way).
78
+ export const adjacentToLand = (tile: Tile): boolean =>
79
+ tile.isLand() ||
80
+ tile.getNeighbours().some((neighbour: Tile): boolean => neighbour.isLand());
81
+
82
+ // Whether a `CoastBound` unit ending its move on `tile` is lost: one draw from `randomNumberGenerator`, lost when it is
83
+ // no more than the chance.
84
+ const lostAtSea = (
85
+ randomNumberGenerator: () => number,
86
+ coastBoundLoss: number | CoastBoundLoss,
87
+ unit: Unit,
88
+ tile: Tile
89
+ ): boolean => {
90
+ if (typeof coastBoundLoss === 'number') {
91
+ return randomNumberGenerator() <= coastBoundLoss;
92
+ }
93
+
94
+ const chance = coastBoundLoss(unit, tile);
95
+
96
+ return chance > 0 && randomNumberGenerator() <= chance;
97
+ };
98
+
69
99
  /**
70
100
  * A ruleset's `Moved` rules, in order, with ids `<prefix>:unit/moved/<name>` as civ1-unit's are: telling the client,
71
101
  * vision, ending a unit's moves, transports carrying and collecting their cargo, leaving a transport, a `CoastBound`
@@ -82,7 +112,14 @@ export const movedRules = (
82
112
  transportRegistry = transportRegistryInstance,
83
113
  unitRegistry = unitRegistryInstance,
84
114
  }: MovedDependencies,
85
- { Move, Disembark, attacks, noVendetta, coastBoundLoss }: MovedOptions
115
+ {
116
+ Move,
117
+ Disembark,
118
+ attacks,
119
+ noVendetta,
120
+ coastBoundLoss,
121
+ nearLand = (tile: Tile): boolean => tile.isCoast(),
122
+ }: MovedOptions
86
123
  ): Moved[] => {
87
124
  const id = (name: string): string => `${prefix}:unit/moved/${name}`,
88
125
  // Stows each of `units` aboard `transport`, as `Embark` and `LandAircraft` do, until it is full.
@@ -239,8 +276,10 @@ export const movedRules = (
239
276
  id('trireme-lost-at-sea'),
240
277
  new Criterion((unit: Unit): boolean => unit.hasTrait(CoastBound)),
241
278
  new Criterion((unit: Unit): boolean => unit.moves().value() === 0),
242
- new Criterion((unit: Unit): boolean => !unit.tile().isCoast()),
243
- new Criterion((): boolean => randomNumberGenerator() <= coastBoundLoss),
279
+ new Criterion((unit: Unit): boolean => !nearLand(unit.tile())),
280
+ new Criterion((unit: Unit): boolean =>
281
+ lostAtSea(randomNumberGenerator, coastBoundLoss, unit, unit.tile())
282
+ ),
244
283
  new Effect((unit: Unit): void => {
245
284
  ruleRegistry.process(LostAtSea, unit as unknown as ITransport);
246
285
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civ-clone/base-unit-lifecycle",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
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
5
  "repository": "git@github.com:civ-clone/base-unit-lifecycle.git",
6
6
  "keywords": [
@@ -12,11 +12,11 @@ import Tile from '@civ-clone/core-world/Tile';
12
12
  import TileImprovement from '@civ-clone/core-tile-improvement/TileImprovement';
13
13
  import Unit from '@civ-clone/core-unit/Unit';
14
14
  import UnitImprovement from '@civ-clone/core-unit-improvement/UnitImprovement';
15
- import detonatedRules from '../lib/detonatedRules';
15
+ import detonatedRules, { protectedByCityWithin } from '../lib/detonatedRules';
16
16
  import diplomatRules from '../lib/diplomatRules';
17
17
  import { expect } from 'chai';
18
18
  import lifecycleRules from '../lib/lifecycleRules';
19
- import movedRules from '../lib/movedRules';
19
+ import movedRules, { adjacentToLand } from '../lib/movedRules';
20
20
  import treatyPartner from '../lib/treatyPartner';
21
21
 
22
22
  // civ1-unit 0.1.40's own rules (835709d), dumped from its compiled `getRules`: id, rule type, priority and how many
@@ -231,9 +231,177 @@ describe('movedRules', (): void => {
231
231
  expect(rules(0.26).validate(unit(false))).to.false;
232
232
  expect(rules(0).validate(unit(true))).to.false;
233
233
  });
234
+
235
+ // Civ2's odds (survey-units §5, `unit_move_dir_trireme_loss`): lost 1 time in N, N = 2, ×2 with Seafaring, ×2 again
236
+ // with Navigation; never with the Lighthouse.
237
+ it("takes the odds as a function of the unit and the tile it ended on, and draws nothing when they're 0", (): void => {
238
+ const asked: unknown[][] = [],
239
+ draws: number[] = [],
240
+ odds = (unit: { n: number }, tile: unknown): number => {
241
+ asked.push([unit, tile]);
242
+
243
+ return unit.n === 0 ? 0 : 1 / unit.n;
244
+ },
245
+ rule = (draw: number) =>
246
+ movedRules(
247
+ 'game',
248
+ {
249
+ randomNumberGenerator: (): number => {
250
+ draws.push(draw);
251
+
252
+ return draw;
253
+ },
254
+ },
255
+ { ...options, coastBoundLoss: odds as never }
256
+ ).find((rule) => rule.id()!.endsWith('/trireme-lost-at-sea'))!,
257
+ sea = { isCoast: (): boolean => false },
258
+ // N: 2 with neither advance, 4 with Seafaring, 8 with Navigation too, 0 for the Lighthouse's "never".
259
+ trireme = (n: number) => ({
260
+ n,
261
+ hasTrait: (Trait: unknown): boolean => Trait === CoastBound,
262
+ moves: () => ({ value: (): number => 0 }),
263
+ tile: () => sea,
264
+ });
265
+
266
+ expect(rule(0.5).validate(trireme(2))).to.true;
267
+ expect(rule(0.51).validate(trireme(2))).to.false;
268
+ expect(rule(0.25).validate(trireme(4))).to.true;
269
+ expect(rule(0.26).validate(trireme(4))).to.false;
270
+ expect(rule(0.125).validate(trireme(8))).to.true;
271
+ expect(rule(0.13).validate(trireme(8))).to.false;
272
+ expect(asked.every(([, tile]) => tile === sea)).to.true;
273
+ expect(draws).to.have.length(6);
274
+
275
+ // The Lighthouse: never lost, even on a draw of 0, and nothing is drawn.
276
+ expect(rule(0).validate(trireme(0))).to.false;
277
+ expect(draws).to.have.length(6);
278
+ });
279
+
280
+ it('draws once for a number, as civ1-unit does, and only for a unit out of sight of land', (): void => {
281
+ let draws = 0;
282
+
283
+ const rule = movedRules(
284
+ 'game',
285
+ {
286
+ randomNumberGenerator: (): number => {
287
+ draws += 1;
288
+
289
+ return 0.9;
290
+ },
291
+ },
292
+ options
293
+ ).find((rule) => rule.id()!.endsWith('/trireme-lost-at-sea'))!,
294
+ unit = (coast: boolean) => ({
295
+ hasTrait: (Trait: unknown): boolean => Trait === CoastBound,
296
+ moves: () => ({ value: (): number => 0 }),
297
+ tile: () => ({ isCoast: (): boolean => coast }),
298
+ });
299
+
300
+ expect(rule.validate(unit(true))).to.false;
301
+ expect(draws).to.equal(0);
302
+ expect(rule.validate(unit(false))).to.false;
303
+ expect(draws).to.equal(1);
304
+ });
305
+
306
+ it('asks nearLand whether a unit is in sight of land, instead of whether its tile is on the coast', (): void => {
307
+ const near = new Set<unknown>(),
308
+ rule = movedRules(
309
+ 'game',
310
+ { randomNumberGenerator: (): number => 0 },
311
+ { ...options, nearLand: (tile: Tile): boolean => near.has(tile) }
312
+ ).find((rule) => rule.id()!.endsWith('/trireme-lost-at-sea'))!,
313
+ // `isCoast` says otherwise each time, so only `nearLand` can decide.
314
+ tile = { isCoast: (): boolean => near.size === 0 },
315
+ unit = {
316
+ hasTrait: (Trait: unknown): boolean => Trait === CoastBound,
317
+ moves: () => ({ value: (): number => 0 }),
318
+ tile: () => tile,
319
+ };
320
+
321
+ expect(rule.validate(unit)).to.true;
322
+ near.add(tile);
323
+ expect(rule.validate(unit)).to.false;
324
+ });
325
+
326
+ it('is in sight of land, as Civ2 asks, on land or beside it (survey-units §5: any of the 9 tiles)', (): void => {
327
+ const square = (land: boolean, neighbours: boolean[] = []) =>
328
+ ({
329
+ isLand: (): boolean => land,
330
+ getNeighbours: () => neighbours.map((land) => square(land)),
331
+ } as unknown as Tile);
332
+
333
+ expect(adjacentToLand(square(true))).to.true;
334
+ expect(adjacentToLand(square(false, [false, false, true]))).to.true;
335
+ expect(adjacentToLand(square(false, Array(8).fill(false)))).to.false;
336
+ });
234
337
  });
235
338
 
236
339
  describe('detonatedRules', (): void => {
340
+ // Civ2 (survey-units §2.1, `unit_nuclear_attack`): any SDI Defense city of another civ within distance 3 of the
341
+ // target stops the attack. Civ2's distance is its own `map_distance`, which civ2 passes as `distance`.
342
+ it('is vetoed by a city within the radius that protects the target from the unit', (): void => {
343
+ const target = {
344
+ distanceFrom: (tile: { at: number }): number => tile.at,
345
+ } as unknown as Tile,
346
+ city = (at: number, sdi: boolean, owner: string) => ({
347
+ at,
348
+ sdi,
349
+ player: () => owner,
350
+ tile: () => ({ at }),
351
+ }),
352
+ cities: ReturnType<typeof city>[] = [],
353
+ vetoes = protectedByCityWithin(
354
+ { entries: () => cities } as never,
355
+ 3,
356
+ (city: any, unit: any): boolean =>
357
+ city.sdi && city.player() !== unit.player()
358
+ ),
359
+ unit = { player: () => 'us' } as unknown as Unit;
360
+
361
+ expect(vetoes(unit, target)).to.false;
362
+
363
+ cities.push(
364
+ city(4, true, 'them'),
365
+ city(2, false, 'them'),
366
+ city(1, true, 'us')
367
+ );
368
+ expect(vetoes(unit, target)).to.false;
369
+
370
+ cities.push(city(3, true, 'them'));
371
+ expect(vetoes(unit, target)).to.true;
372
+ });
373
+
374
+ it("measures with the ruleset's own distance when given one", (): void => {
375
+ const asked: unknown[][] = [],
376
+ target = {
377
+ distanceFrom: (): number => {
378
+ throw new Error('the default distance was used');
379
+ },
380
+ } as unknown as Tile,
381
+ near = { name: 'near' },
382
+ far = { name: 'far' },
383
+ city = (tile: unknown) => ({ tile: () => tile }),
384
+ cities = [city(far)],
385
+ // As Civ2's `map_distance` might put a city 3 moves away that straight-line distance puts further.
386
+ distance = (from: Tile, to: Tile): number => {
387
+ asked.push([from, to]);
388
+
389
+ return to === (near as unknown) ? 3 : 4;
390
+ },
391
+ vetoes = protectedByCityWithin(
392
+ { entries: () => cities } as never,
393
+ 3,
394
+ (): boolean => true,
395
+ distance
396
+ ),
397
+ unit = {} as Unit;
398
+
399
+ expect(vetoes(unit, target)).to.false;
400
+ cities.push(city(near));
401
+ expect(vetoes(unit, target)).to.true;
402
+ expect(asked[0]).to.deep.equal([target, far]);
403
+ });
404
+
237
405
  it("gives civ1-unit's Rules/Unit/detonated rules, in order", (): void => {
238
406
  expect(
239
407
  fingerprint(