@civ-clone/base-unit-lifecycle 0.1.1 → 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
@@ -20,7 +20,7 @@ registry, the engine or a number take it first and return the function.
20
20
  - `lib/halted`: `emitHalted(engine)` (`unit:halted`), a GoTo stopped next to its destination.
21
21
  - `lib/unsupported`: `emitUnsupported(engine)` (`unit:unsupported`).
22
22
  - `lib/defeated`: `emitAndDestroy(engine, ruleRegistry)` (`unit:defeated`, then `Destroyed`), and for a defeated
23
- defender's stack, `defended`, `notInCity(cityRegistry)`, `hasStack(unitRegistry)` and
23
+ defender's stack, `defended`, `notInCity(cityRegistry)`, `notSpared(sparesStack)`, `hasStack(unitRegistry)` and
24
24
  `destroyStack(unitRegistry, ruleRegistry)`.
25
25
  - `lib/destroyed`: `emitDestroyed(engine)` (`unit:destroyed`); `destroyedByAnother`, `notYetDestroyed` and
26
26
  `countLoss(unitLossRegistry)` for a loss report; `deactivate`; `removeImprovements(unitImprovementRegistry)`;
@@ -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.
@@ -55,8 +63,10 @@ returns the rules with ids `<prefix>:unit/<type>/<name>`, the same names and ord
55
63
  rule (`START_AFRESH_RULE`) before it, as it does today.
56
64
  - `HALVE_CITIES_PRIORITY` and `halveCity` now live in `lib/detonatedRules` as well as civ1-unit's `lib/nuclear`; R2b
57
65
  makes civ1-unit re-export the base ones, so there is one copy.
58
- - The defaults reproduce civ1-unit exactly: `shortStep` and `vetoes` are for other rulesets (Civ2's short step and SDI
59
- Defense), and leaving them out changes nothing.
66
+ - The defaults reproduce civ1-unit exactly: `shortStep`, `vetoes` and `sparesStack` are for other rulesets (Civ2's
67
+ short step, SDI Defense, and the squares besides a city where a stack outlives its defender), and leaving them out
68
+ changes nothing: `lifecycleRules`' `destroy-stack` rule only gains its `notSpared` criterion when `sparesStack` is
69
+ given.
60
70
 
61
71
  A library: no `main`, so it registers nothing on its own. Import what you need, for example
62
72
  `@civ-clone/base-unit-lifecycle/lib/destroyed`.
package/lib/defeated.d.ts CHANGED
@@ -2,6 +2,7 @@ import Action from '@civ-clone/core-unit/Action';
2
2
  import CityRegistry from '@civ-clone/core-city/CityRegistry';
3
3
  import Engine from '@civ-clone/core-engine/Engine';
4
4
  import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
5
+ import Tile from '@civ-clone/core-world/Tile';
5
6
  import Unit from '@civ-clone/core-unit/Unit';
6
7
  import UnitRegistry from '@civ-clone/core-unit/UnitRegistry';
7
8
  export declare const emitAndDestroy: (
@@ -16,6 +17,10 @@ export declare const defended: (
16
17
  export declare const notInCity: (
17
18
  cityRegistry: CityRegistry
18
19
  ) => (unit: Unit) => boolean;
20
+ export type SparesStack = (tile: Tile, defender: Unit) => boolean;
21
+ export declare const notSpared: (
22
+ sparesStack: SparesStack
23
+ ) => (unit: Unit) => boolean;
19
24
  export declare const hasStack: (
20
25
  unitRegistry: UnitRegistry
21
26
  ) => (unit: Unit) => boolean;
package/lib/defeated.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.destroyStack = exports.hasStack = exports.notInCity = exports.defended = exports.emitAndDestroy = void 0;
3
+ exports.destroyStack = exports.hasStack = exports.notSpared = exports.notInCity = exports.defended = exports.emitAndDestroy = void 0;
4
4
  const Destroyed_1 = require("@civ-clone/core-unit/Rules/Destroyed");
5
5
  // What a ruleset's `Defeated` rules ask and do when `unit` loses a fight to `by`. `action`, when given, is the attack:
6
6
  // `action.unit()` attacked.
@@ -17,6 +17,9 @@ exports.defended = defended;
17
17
  // The defeated unit is not in a city.
18
18
  const notInCity = (cityRegistry) => (unit) => cityRegistry.getByTile(unit.tile()) === null;
19
19
  exports.notInCity = notInCity;
20
+ // The square the defeated unit lost on doesn't keep its stack.
21
+ const notSpared = (sparesStack) => (unit) => !sparesStack(unit.tile(), unit);
22
+ exports.notSpared = notSpared;
20
23
  // Other units of the defeated unit's player share its tile.
21
24
  const hasStack = (unitRegistry) => (unit) => unitRegistry
22
25
  .getByTile(unit.tile())
@@ -1 +1 @@
1
- {"version":3,"file":"defeated.js","sourceRoot":"","sources":["defeated.ts"],"names":[],"mappings":";;;AAEA,oEAA6D;AAM7D,uHAAuH;AACvH,6BAA6B;AAE7B,oHAAoH;AACpH,wBAAwB;AACjB,MAAM,cAAc,GACzB,CAAC,MAAc,EAAE,YAA0B,EAAE,EAAE,CAC/C,CAAC,IAAU,EAAE,EAAQ,EAAE,MAAe,EAAQ,EAAE;IAC9C,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;IAE/C,YAAY,CAAC,OAAO,CAAC,mBAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AACrD,CAAC,CAAC;AANS,QAAA,cAAc,kBAMvB;AAEJ,uFAAuF;AAChF,MAAM,QAAQ,GAAG,CAAC,IAAU,EAAE,EAAQ,EAAE,MAAe,EAAW,EAAE,CACzE,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC;AADvB,QAAA,QAAQ,YACe;AAEpC,sCAAsC;AAC/B,MAAM,SAAS,GACpB,CAAC,YAA0B,EAAE,EAAE,CAC/B,CAAC,IAAU,EAAW,EAAE,CACtB,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;AAHpC,QAAA,SAAS,aAG2B;AAEjD,4DAA4D;AACrD,MAAM,QAAQ,GACnB,CAAC,YAA0B,EAAE,EAAE,CAC/B,CAAC,IAAU,EAAW,EAAE,CACtB,YAAY;KACT,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;KACtB,MAAM,CACL,CAAC,QAAc,EAAE,EAAE,CACjB,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,EAAE,CAC3D,CAAC,MAAM,GAAG,CAAC,CAAC;AARN,QAAA,QAAQ,YAQF;AAEnB,sHAAsH;AAC/G,MAAM,YAAY,GACvB,CAAC,YAA0B,EAAE,YAA0B,EAAE,EAAE,CAC3D,CAAC,IAAU,EAAE,EAAQ,EAAQ,EAAE,CAC7B,YAAY;KACT,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;KACtB,MAAM,CACL,CAAC,QAAc,EAAW,EAAE,CAC1B,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,EAAE,CAC3D;KACA,OAAO,CAAC,CAAC,QAAc,EAAQ,EAAE;IAChC,YAAY,CAAC,OAAO,CAAC,mBAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAXI,QAAA,YAAY,gBAWhB"}
1
+ {"version":3,"file":"defeated.js","sourceRoot":"","sources":["defeated.ts"],"names":[],"mappings":";;;AAEA,oEAA6D;AAO7D,uHAAuH;AACvH,6BAA6B;AAE7B,oHAAoH;AACpH,wBAAwB;AACjB,MAAM,cAAc,GACzB,CAAC,MAAc,EAAE,YAA0B,EAAE,EAAE,CAC/C,CAAC,IAAU,EAAE,EAAQ,EAAE,MAAe,EAAQ,EAAE;IAC9C,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;IAE/C,YAAY,CAAC,OAAO,CAAC,mBAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AACrD,CAAC,CAAC;AANS,QAAA,cAAc,kBAMvB;AAEJ,uFAAuF;AAChF,MAAM,QAAQ,GAAG,CAAC,IAAU,EAAE,EAAQ,EAAE,MAAe,EAAW,EAAE,CACzE,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC;AADvB,QAAA,QAAQ,YACe;AAEpC,sCAAsC;AAC/B,MAAM,SAAS,GACpB,CAAC,YAA0B,EAAE,EAAE,CAC/B,CAAC,IAAU,EAAW,EAAE,CACtB,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;AAHpC,QAAA,SAAS,aAG2B;AAMjD,+DAA+D;AACxD,MAAM,SAAS,GACpB,CAAC,WAAwB,EAAE,EAAE,CAC7B,CAAC,IAAU,EAAW,EAAE,CACtB,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;AAHvB,QAAA,SAAS,aAGc;AAEpC,4DAA4D;AACrD,MAAM,QAAQ,GACnB,CAAC,YAA0B,EAAE,EAAE,CAC/B,CAAC,IAAU,EAAW,EAAE,CACtB,YAAY;KACT,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;KACtB,MAAM,CACL,CAAC,QAAc,EAAE,EAAE,CACjB,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,EAAE,CAC3D,CAAC,MAAM,GAAG,CAAC,CAAC;AARN,QAAA,QAAQ,YAQF;AAEnB,sHAAsH;AAC/G,MAAM,YAAY,GACvB,CAAC,YAA0B,EAAE,YAA0B,EAAE,EAAE,CAC3D,CAAC,IAAU,EAAE,EAAQ,EAAQ,EAAE,CAC7B,YAAY;KACT,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;KACtB,MAAM,CACL,CAAC,QAAc,EAAW,EAAE,CAC1B,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,EAAE,CAC3D;KACA,OAAO,CAAC,CAAC,QAAc,EAAQ,EAAE;IAChC,YAAY,CAAC,OAAO,CAAC,mBAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAXI,QAAA,YAAY,gBAWhB"}
package/lib/defeated.ts CHANGED
@@ -3,6 +3,7 @@ import CityRegistry from '@civ-clone/core-city/CityRegistry';
3
3
  import Destroyed from '@civ-clone/core-unit/Rules/Destroyed';
4
4
  import Engine from '@civ-clone/core-engine/Engine';
5
5
  import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
6
+ import Tile from '@civ-clone/core-world/Tile';
6
7
  import Unit from '@civ-clone/core-unit/Unit';
7
8
  import UnitRegistry from '@civ-clone/core-unit/UnitRegistry';
8
9
 
@@ -29,6 +30,16 @@ export const notInCity =
29
30
  (unit: Unit): boolean =>
30
31
  cityRegistry.getByTile(unit.tile()) === null;
31
32
 
33
+ // Whether the square `defender` lost on keeps the units sharing it: a ruleset's own places (a fortress, say) where only
34
+ // the defender is lost.
35
+ export type SparesStack = (tile: Tile, defender: Unit) => boolean;
36
+
37
+ // The square the defeated unit lost on doesn't keep its stack.
38
+ export const notSpared =
39
+ (sparesStack: SparesStack) =>
40
+ (unit: Unit): boolean =>
41
+ !sparesStack(unit.tile(), unit);
42
+
32
43
  // Other units of the defeated unit's player share its tile.
33
44
  export const hasStack =
34
45
  (unitRegistry: UnitRegistry) =>
@@ -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":"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"}
@@ -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,7 @@ import { UnitImprovementRegistry } from '@civ-clone/core-unit-improvement/UnitIm
6
6
  import { UnitLossRegistry } from '@civ-clone/core-unit/UnitLossRegistry';
7
7
  import { UnitPlayerActionType } from './playerAction';
8
8
  import { UnitRegistry } from '@civ-clone/core-unit/UnitRegistry';
9
+ import { SparesStack } from './defeated';
9
10
  import Action from '@civ-clone/core-player/Rules/Action';
10
11
  import Activate from '@civ-clone/core-unit/Rules/Activate';
11
12
  import CanStow from '@civ-clone/core-unit-transport/Rules/CanStow';
@@ -41,6 +42,7 @@ export interface LifecycleBaseOptions {
41
42
  removedOnActivate: (typeof UnitImprovement)[];
42
43
  stowable: (typeof Unit)[];
43
44
  partnerOf?: (unit: Unit, tile: Tile) => Player | null;
45
+ sparesStack?: SparesStack;
44
46
  }
45
47
  /**
46
48
  * How a unit short of a step's moves fares: Civ1's `shortfallFactor` (`spendShort`), or a ruleset's own `shortStep`,
@@ -97,6 +99,7 @@ export declare const lifecycleRules: (
97
99
  partnerOf,
98
100
  shortfallFactor,
99
101
  shortStep,
102
+ sparesStack,
100
103
  }: LifecycleOptions
101
104
  ) => {
102
105
  activate: Activate[];
@@ -43,7 +43,7 @@ const stowed_1 = require("./stowed");
43
43
  * `<prefix>:unit/<type>/<name>` (`<prefix>:player/action/<name>` for the player actions), as civ1-unit's are, so a
44
44
  * ruleset registers each list where it wants it and keeps the ids another package may name.
45
45
  */
46
- const lifecycleRules = (prefix, { cityRegistry = CityRegistry_1.instance, engine = Engine_1.instance, randomNumberGenerator = core_random_1.instance, ruleRegistry = RuleRegistry_1.instance, transportRegistry = TransportRegistry_1.instance, unitImprovementRegistry = UnitImprovementRegistry_1.instance, unitLossRegistry = UnitLossRegistry_1.instance, unitRegistry = UnitRegistry_1.instance, }, { ActiveUnit, InactiveUnit, removedOnActivate, stowable, partnerOf, shortfallFactor, shortStep, }) => {
46
+ const lifecycleRules = (prefix, { cityRegistry = CityRegistry_1.instance, engine = Engine_1.instance, randomNumberGenerator = core_random_1.instance, ruleRegistry = RuleRegistry_1.instance, transportRegistry = TransportRegistry_1.instance, unitImprovementRegistry = UnitImprovementRegistry_1.instance, unitLossRegistry = UnitLossRegistry_1.instance, unitRegistry = UnitRegistry_1.instance, }, { ActiveUnit, InactiveUnit, removedOnActivate, stowable, partnerOf, shortfallFactor, shortStep, sparesStack, }) => {
47
47
  const id = (name) => `${prefix}:unit/${name}`;
48
48
  return {
49
49
  activate: [
@@ -62,7 +62,9 @@ const lifecycleRules = (prefix, { cityRegistry = CityRegistry_1.instance, engine
62
62
  new Defeated_1.default(id('defeated/emit-and-destroy'), new Effect_1.default((0, defeated_1.emitAndDestroy)(engine, ruleRegistry))),
63
63
  new Defeated_1.default(
64
64
  // `Attack` processes `Defeated` for a losing attacker too; only a defeated defender takes its stack with it.
65
- id('defeated/destroy-stack'), new Criterion_1.default(defeated_1.defended), new Criterion_1.default((0, defeated_1.notInCity)(cityRegistry)), new Criterion_1.default((0, defeated_1.hasStack)(unitRegistry)), new Effect_1.default((0, defeated_1.destroyStack)(unitRegistry, ruleRegistry))),
65
+ id('defeated/destroy-stack'), new Criterion_1.default(defeated_1.defended), new Criterion_1.default((0, defeated_1.notInCity)(cityRegistry)), ...(sparesStack === undefined
66
+ ? []
67
+ : [new Criterion_1.default((0, defeated_1.notSpared)(sparesStack))]), new Criterion_1.default((0, defeated_1.hasStack)(unitRegistry)), new Effect_1.default((0, defeated_1.destroyStack)(unitRegistry, ruleRegistry))),
66
68
  ],
67
69
  destroyed: [
68
70
  new Destroyed_1.default(id('destroyed/emit'), new Effect_1.default((0, destroyed_1.emitDestroyed)(engine))),
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycleRules.js","sourceRoot":"","sources":["lifecycleRules.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lifecycleRules.js","sourceRoot":"","sources":["lifecycleRules.ts"],"names":[],"mappings":"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"}
@@ -62,11 +62,13 @@ import {
62
62
  } from './activate';
63
63
  import { countLosses, emitLostAtSea, sink } from './lostAtSea';
64
64
  import {
65
+ SparesStack,
65
66
  defended,
66
67
  destroyStack,
67
68
  emitAndDestroy,
68
69
  hasStack,
69
70
  notInCity,
71
+ notSpared,
70
72
  } from './defeated';
71
73
  import { fromLand, impassable, toPartnersLand } from './impassable';
72
74
  import {
@@ -123,6 +125,8 @@ export interface LifecycleBaseOptions {
123
125
  stowable: (typeof Unit)[];
124
126
  // Whose land `tile` is among `unit`'s treaty partners, or `null`: routes go round it. Without it, no rule is made.
125
127
  partnerOf?: (unit: Unit, tile: Tile) => Player | null;
128
+ // Squares where a defeated defender's stack survives it, besides a city. Without it, only a city spares the stack.
129
+ sparesStack?: SparesStack;
126
130
  }
127
131
 
128
132
  /**
@@ -176,6 +180,7 @@ export const lifecycleRules = (
176
180
  partnerOf,
177
181
  shortfallFactor,
178
182
  shortStep,
183
+ sparesStack,
179
184
  }: LifecycleOptions
180
185
  ) => {
181
186
  const id = (name: string): string => `${prefix}:unit/${name}`;
@@ -235,6 +240,9 @@ export const lifecycleRules = (
235
240
  id('defeated/destroy-stack'),
236
241
  new Criterion(defended),
237
242
  new Criterion(notInCity(cityRegistry)),
243
+ ...(sparesStack === undefined
244
+ ? []
245
+ : [new Criterion(notSpared(sparesStack))]),
238
246
  new Criterion(hasStack(unitRegistry)),
239
247
  new Effect(destroyStack(unitRegistry, ruleRegistry))
240
248
  ),
@@ -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":";;;AAAA,oEAG2C;AAC3C,0DAGuC;AACvC,uFAGuD;AACvD,oEAG2C;AAC3C,wFAG0D;AAC1D,oEAG2C;AAC3C,4EAA4E;AAE5E,2DAAoD;AACpD,+FAAwF;AACxF,kFAA2E;AAC3E,8DAAuD;AACvD,wDAAiD;AACjD,+DAAwD;AAExD,8EAAuE;AACvE,4DAAqD;AACrD,6FAAsF;AACtF,oEAA6D;AAE7D,+EAAwE;AACxE,2EAAoE;AAGpE,kEAAqE;AACrE,wDAAiE;AAgCjE,oHAAoH;AACpH,yEAAyE;AAClE,MAAM,cAAc,GAAG,CAAC,IAAU,EAAW,EAAE,CACpD,IAAI,CAAC,MAAM,EAAE;IACb,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC,SAAe,EAAW,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;AAFjE,QAAA,cAAc,kBAEmD;AAE9E,wHAAwH;AACxH,4BAA4B;AAC5B,MAAM,SAAS,GAAG,CAChB,qBAAmC,EACnC,cAAuC,EACvC,IAAU,EACV,IAAU,EACD,EAAE;IACX,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE;QACtC,OAAO,qBAAqB,EAAE,IAAI,cAAc,CAAC;KAClD;IAED,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAE1C,OAAO,MAAM,GAAG,CAAC,IAAI,qBAAqB,EAAE,IAAI,MAAM,CAAC;AACzD,CAAC,CAAC;AAEF;;;;GAIG;AACI,MAAM,UAAU,GAAG,CACxB,MAAc,EACd,EACE,YAAY,GAAG,uBAAoB,EACnC,MAAM,GAAG,iBAAc,EACvB,mBAAmB,GAAG,8BAA2B,EACjD,qBAAqB,GAAG,sBAAW,EACnC,YAAY,GAAG,uBAAoB,EACnC,iBAAiB,GAAG,4BAAyB,EAC7C,YAAY,GAAG,uBAAoB,GACjB,EACpB,EACE,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAU,EACV,cAAc,EACd,QAAQ,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GACrC,EACN,EAAE;IACX,MAAM,EAAE,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,MAAM,eAAe,IAAI,EAAE;IACjE,iGAAiG;IACjG,OAAO,GAAG,CAAC,SAAyB,EAAE,KAAa,EAAQ,EAAE,CAC3D,KAAK,CAAC,OAAO,CAAC,CAAC,IAAU,EAAQ,EAAE;QACjC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACxD,OAAO;SACR;QAED,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAElC,IAAI,CAAC,OAAO,CAAC,IAAI,gBAAM,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC;IACJ,sGAAsG;IACtG,cAAc,GAAG,CAAC,SAAe,EAAE,OAAa,SAAS,CAAC,IAAI,EAAE,EAAU,EAAE,CAC1E,YAAY;SACT,SAAS,CAAC,IAAI,CAAC;SACf,MAAM,CACL,CAAC,IAAU,EAAW,EAAE,CACtB,IAAI,KAAK,SAAS;QAClB,IAAI,CAAC,MAAM,EAAE,KAAK,SAAS,CAAC,MAAM,EAAE;QACpC,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,CACnC;IACL,4GAA4G;IAC5G,cAAc,GAAG;QACf,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,YAAY,wBAAc,CAAC;QACtE,IAAI,mBAAS,CACX,CAAC,IAAU,EAAE,MAAc,EAAW,EAAE,CAAC,MAAM,YAAY,IAAI,CAChE;QACD,IAAI,mBAAS,CACX,CAAC,IAAU,EAAE,MAAc,EAAW,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE,CACrE;KACF,EACD,SAAS,GAAG,CAAC,IAAU,EAAW,EAAE,WAClC,OAAA,CAAA,MAAA,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,0CAAE,MAAM,EAAE,MAAK,IAAI,CAAC,MAAM,EAAE,CAAA,EAAA,CAAC;IAEpE,OAAO;QACL,IAAI,eAAK,CACP,EAAE,CAAC,MAAM,CAAC,EACV,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAE,MAAc,EAAQ,EAAE;YAC9C,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1C,CAAC,CAAC,CACH;QACD,IAAI,eAAK,CACP,EAAE,CAAC,kBAAkB,CAAC,EACtB,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAQ,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CACzD;QACD,IAAI,eAAK,CACP,EAAE,CAAC,0BAA0B,CAAC,EAC9B,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,EAClE,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAQ,EAAE;YAC9B,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACpB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC,CAAC,CACH;QACD,IAAI,eAAK;QACP,gHAAgH;QAChH,wFAAwF;QACxF,EAAE,CAAC,YAAY,CAAC,EAChB,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,YAAY,wBAAc,CAAC,EACtE,IAAI,mBAAS,CACX,CAAC,IAAU,EAAE,MAAc,EAAW,EAAE,CAAC,MAAM,YAAY,IAAI,CAChE,EACD,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CACnC,IAAuB,CAAC,QAAQ,EAAE,CACpC,EACD,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAE,MAAc,EAAQ,EAAE;;YAC9C,MAAM,SAAS,GAAG,IAAsB,EACtC,QAAQ,GACN,CAAA,MAAA,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,0CAAE,MAAM,EAAE,MAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YAEtE,SAAS,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC,KAAW,EAAQ,EAAE;gBAC9C,IACE,QAAQ;oBACR,CAAC,CAAC,KAAK,YAAY,aAAG,CAAC;oBACvB,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,YAAY,gBAAM,CAAC;oBACjC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,YAAY,kBAAQ,CAAC,EACnC;oBACA,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBAExB,OAAO;iBACR;gBAED,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CACH;QACD,wGAAwG;QACxG,IAAI,eAAK;QACP,gFAAgF;QAChF,EAAE,CAAC,kBAAkB,CAAC,EACtB,GAAG,cAAc,EACjB,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EACxD,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAQ,EAAE,CAC9B,OAAO,CACL,IAAsB,EACtB,cAAc,CAAC,IAAI,CAAC,CAAC,MAAM,CACzB,CAAC,QAAc,EAAW,EAAE,CAAC,QAAQ,YAAY,aAAG,CACrD,CACF,CACF,CACF;QACD,IAAI,eAAK;QACP,4FAA4F;QAC5F,EAAE,CAAC,kBAAkB,CAAC,EACtB,GAAG,cAAc,EACjB,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,EACvD,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAQ,EAAE,CAC9B,OAAO,CACL,IAAsB,EACtB,cAAc,CAAC,IAAI,CAAC,CAAC,MAAM,CACzB,CAAC,QAAc,EAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,kBAAQ,CACjE,CACF,CACF,CACF;QACD,IAAI,eAAK,CACP,EAAE,CAAC,WAAW,CAAC,EACf,IAAI,mBAAS,CACX,CAAC,IAAU,EAAE,MAAc,EAAW,EAAE,CAAC,MAAM,YAAY,SAAS,CACrE,EACD,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAQ,EAAE;YAC9B,MAAM,QAAQ,GAAG,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAEnD,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAElC,iBAAiB,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC,CAAC,CACH;QACD,IAAI,eAAK;QACP,kHAAkH;QAClH,wFAAwF;QACxF,EAAE,CAAC,UAAU,CAAC,EACd,IAAI,mBAAS,CACX,CAAC,IAAU,EAAE,MAAc,EAAW,EAAE,CAAC,MAAM,YAAY,IAAI,CAChE,EACD,IAAI,mBAAS,CACX,CAAC,IAAU,EAAE,MAAc,EAAW,EAAE,CAAC,CAAC,CAAC,MAAM,YAAY,SAAS,CAAC,CACxE,EACD,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EACvE,IAAI,mBAAS,CACX,CAAC,IAAU,EAAW,EAAE,CACtB,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE,CACvE,EACD,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAQ,EAAE;YAC9B,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAE3D,IAAI,IAAI,CAAC,IAAI,EAAE,YAAY,gBAAM,EAAE;gBACjC,IAAI,CAAC,OAAO,EAAE,CAAC;aAChB;QACH,CAAC,CAAC,CACH;QACD,IAAI,eAAK,CACP,EAAE,CAAC,qBAAqB,CAAC,EACzB,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,oBAAU,CAAC,CAAC,EACjE,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EAClE,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,EAC9D,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CACpC,SAAS,CAAC,qBAAqB,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CACpE,EACD,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAQ,EAAE;YAC9B,YAAY,CAAC,OAAO,CAAC,mBAAS,EAAE,IAA6B,CAAC,CAAC;QACjE,CAAC,CAAC,CACH;QACD,IAAI,eAAK;QACP,mEAAmE;QACnE,EAAE,CAAC,8BAA8B,CAAC,EAClC,IAAI,cAAI,EAAE,EACV,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,wBAAc,CAAC,CAAC,EACrE,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAE,MAAc,EAAW,EAAE,CACpD,OAAO,CAAC,IAAI,CACV,CAAC,UAAsB,EAAW,EAAE,CAAC,MAAM,YAAY,UAAU,CAClE,CACF,EACD,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAQ,EAAE;YAC9B,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACpB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC,CAAC,CACH;QACD,IAAI,eAAK,CACP,EAAE,CAAC,oBAAoB,CAAC;QACxB,oFAAoF;QACpF,IAAI,mBAAS,CACX,CAAC,IAAU,EAAE,MAAc,EAAE,EAAE,CAC7B,OAAQ,MAAyC,CAAC,KAAK,KAAK,UAAU,CACzE,EACD,IAAI,gBAAM,CAAC,CAAC,IAAU,EAAE,MAAc,EAAE,EAAE;YACxC,MAAM,KAAK,GAAI,MAAyC,CAAC,KAAK,EAAE,EAC9D,aAAa,GAAG,mBAAmB;iBAChC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC;iBAClC,MAAM,CACL,CAAC,WAAW,EAAwB,EAAE,CACpC,WAAW,YAAY,oBAAK,IAAI,WAAW,CAAC,MAAM,EAAE,CACvD,CAAC;YAEN,aAAa,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,EAAE,CACpC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAClC,CAAC;YAEF,gHAAgH;YAChH,IACE,aAAa,CAAC,MAAM,GAAG,CAAC;gBACxB,CAAC,UAAU,CAAC,IAAI,CACd,CAAC,UAAsB,EAAW,EAAE,CAAC,MAAM,YAAY,UAAU,CAClE;gBACD,CAAC,IAAA,mBAAW,EAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,EACvD;gBACA,mBAAmB,CAAC,QAAQ,CAC1B,IAAI,uBAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,eAAK,EAAE,EAAE,YAAY,CAAC,CAC9D,CAAC;aACH;QACH,CAAC,CAAC,CACH;KACF,CAAC;AACJ,CAAC,CAAC;AAzOW,QAAA,UAAU,cAyOrB;AAEF,kBAAe,kBAAU,CAAC"}
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.1",
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": [
@@ -39,6 +39,7 @@ import {
39
39
  emitAndDestroy,
40
40
  hasStack,
41
41
  notInCity,
42
+ notSpared,
42
43
  } from '../lib/defeated';
43
44
  import { fromLand, impassable, toPartnersLand } from '../lib/impassable';
44
45
  import {
@@ -396,6 +397,25 @@ describe('defeated', (): void => {
396
397
  [friend, winner],
397
398
  ]);
398
399
  });
400
+ it('asks the ruleset whether the square spares the stack, with the square and the defender', (): void => {
401
+ const tile = {},
402
+ fort = {},
403
+ unit = standIn({ tile: () => tile }),
404
+ fortified = standIn({ tile: () => fort }),
405
+ asked: any[][] = [],
406
+ sparesStack = (square: any, defender: Unit): boolean => {
407
+ asked.push([square, defender]);
408
+
409
+ return square === fort;
410
+ };
411
+
412
+ expect(notSpared(sparesStack as any)(unit)).to.true;
413
+ expect(notSpared(sparesStack as any)(fortified)).to.false;
414
+ expect(asked).to.eql([
415
+ [tile, unit],
416
+ [fort, fortified],
417
+ ]);
418
+ });
399
419
  });
400
420
 
401
421
  describe('destroyed', (): void => {
@@ -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
@@ -115,6 +115,56 @@ describe('lifecycleRules', (): void => {
115
115
  expect(rules.activate).to.length(2);
116
116
  });
117
117
 
118
+ it('spares the stack where sparesStack says so, with one criterion more, and only then', (): void => {
119
+ const city = {},
120
+ fort = {},
121
+ open = {},
122
+ cityRegistry = {
123
+ getByTile: (tile: any) => (tile === city ? {} : null),
124
+ } as any,
125
+ units = new Map<any, Unit[]>(),
126
+ unitRegistry = {
127
+ getByTile: (tile: any): Unit[] => units.get(tile) ?? [],
128
+ } as any,
129
+ player = {},
130
+ by = { player: () => ({}) } as unknown as Unit,
131
+ defender = (tile: any): Unit => {
132
+ const unit = { tile: () => tile, player: () => player } as any,
133
+ friend = { tile: () => tile, player: () => player } as any;
134
+
135
+ units.set(tile, [unit, friend]);
136
+
137
+ return unit;
138
+ },
139
+ destroyStackRule = (sparesStack?: (tile: Tile, unit: Unit) => boolean) =>
140
+ lifecycleRules(
141
+ 'game',
142
+ { cityRegistry, unitRegistry },
143
+ {
144
+ ActiveUnit: ActiveUnit as never,
145
+ InactiveUnit: InactiveUnit as never,
146
+ removedOnActivate: [],
147
+ stowable: [],
148
+ shortfallFactor: 0.5,
149
+ sparesStack,
150
+ }
151
+ ).defeated[1],
152
+ plain = destroyStackRule(),
153
+ sparing = destroyStackRule((tile: Tile): boolean => tile === fort);
154
+
155
+ expect(fingerprint([plain])[0][3]).to.equal(3);
156
+ expect(fingerprint([sparing])[0][3]).to.equal(4);
157
+ expect(sparing.id()).to.equal(plain.id());
158
+
159
+ expect(plain.validate(defender(open), by)).to.true;
160
+ expect(plain.validate(defender(fort), by)).to.true;
161
+ expect(plain.validate(defender(city), by)).to.false;
162
+
163
+ expect(sparing.validate(defender(open), by)).to.true;
164
+ expect(sparing.validate(defender(fort), by)).to.false;
165
+ expect(sparing.validate(defender(city), by)).to.false;
166
+ });
167
+
118
168
  it('prices a long step with the given shortfall factor', (): void => {
119
169
  const [rule] = lifecycleRules(
120
170
  'game',
@@ -181,9 +231,177 @@ describe('movedRules', (): void => {
181
231
  expect(rules(0.26).validate(unit(false))).to.false;
182
232
  expect(rules(0).validate(unit(true))).to.false;
183
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
+ });
184
337
  });
185
338
 
186
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
+
187
405
  it("gives civ1-unit's Rules/Unit/detonated rules, in order", (): void => {
188
406
  expect(
189
407
  fingerprint(