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

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)`;
@@ -55,8 +55,10 @@ returns the rules with ids `<prefix>:unit/<type>/<name>`, the same names and ord
55
55
  rule (`START_AFRESH_RULE`) before it, as it does today.
56
56
  - `HALVE_CITIES_PRIORITY` and `halveCity` now live in `lib/detonatedRules` as well as civ1-unit's `lib/nuclear`; R2b
57
57
  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.
58
+ - The defaults reproduce civ1-unit exactly: `shortStep`, `vetoes` and `sparesStack` are for other rulesets (Civ2's
59
+ short step, SDI Defense, and the squares besides a city where a stack outlives its defender), and leaving them out
60
+ changes nothing: `lifecycleRules`' `destroy-stack` rule only gains its `notSpared` criterion when `sparesStack` is
61
+ given.
60
62
 
61
63
  A library: no `main`, so it registers nothing on its own. Import what you need, for example
62
64
  `@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) =>
@@ -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":";;;AAAA,oEAG2C;AAC3C,0DAGuC;AACvC,oEAG2C;AAC3C,wFAG0D;AAC1D,sGAGkE;AAClE,4EAG+C;AAC/C,iDAMwB;AACxB,oEAG2C;AAC3C,uCAKmB;AACnB,2CAWqB;AACrB,uCAAuD;AACvD,yCAOoB;AACpB,2CAA+D;AAC/D,yCAMoB;AACpB,6CAAoE;AACpE,iDAKwB;AACxB,gEAAyD;AACzD,kEAA2D;AAC3D,0EAAmE;AACnE,gEAAyD;AACzD,8DAAuD;AACvD,kEAA2D;AAC3D,oEAA6D;AAC7D,wDAAiD;AACjD,gGAAyF;AACzF,0EAAmE;AACnE,4EAAqE;AACrE,8EAAuE;AAGvE,wEAAiE;AAEjE,wEAAiE;AAGjE,wEAAiE;AACjE,0EAAmE;AACnE,qCAAsC;AACtC,+CAAgD;AAChD,uFAAmF;AACnF,wDAAiE;AACjE,qCAAuC;AAmDvC;;;;GAIG;AACI,MAAM,cAAc,GAAG,CAC5B,MAAc,EACd,EACE,YAAY,GAAG,uBAAoB,EACnC,MAAM,GAAG,iBAAc,EACvB,qBAAqB,GAAG,sBAAW,EACnC,YAAY,GAAG,uBAAoB,EACnC,iBAAiB,GAAG,4BAAyB,EAC7C,uBAAuB,GAAG,kCAA+B,EACzD,gBAAgB,GAAG,2BAAwB,EAC3C,YAAY,GAAG,uBAAoB,GACb,EACxB,EACE,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,eAAe,EACf,SAAS,GACQ,EACnB,EAAE;IACF,MAAM,EAAE,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,MAAM,SAAS,IAAI,EAAE,CAAC;IAE9D,OAAO;QACL,QAAQ,EAAE;YACR,IAAI,kBAAQ,CACV,EAAE,CAAC,oBAAoB,CAAC,EACxB,IAAI,mBAAS,CAAC,mBAAQ,CAAC,EACvB,IAAI,gBAAM,CAAC,oBAAS,CAAC,CACtB;YACD,IAAI,kBAAQ,CACV,EAAE,CAAC,qBAAqB,CAAC,EACzB,IAAI,mBAAS,CAAC,iBAAM,CAAC,EACrB,IAAI,gBAAM,CAAC,oBAAS,CAAC,CACtB;YACD,GAAG,iBAAiB,CAAC,GAAG,CACtB,CAAC,mBAA2C,EAAY,EAAE,CACxD,IAAI,kBAAQ,CACV,EAAE,CAAC,+BAA+B,mBAAmB,CAAC,IAAI,EAAE,CAAC,EAC7D,IAAI,mBAAS,CACX,IAAA,yBAAc,EAAC,uBAAuB,EAAE,mBAAmB,CAAC,CAC7D,EACD,IAAI,gBAAM,CACR,IAAA,4BAAiB,EAAC,uBAAuB,EAAE,mBAAmB,CAAC,CAChE,CACF,CACJ;SACF;QAED,OAAO,EAAE,QAAQ,CAAC,GAAG,CACnB,CAAC,gBAA6B,EAAW,EAAE,CACzC,IAAI,iBAAO,CACT,EAAE,CAAC,YAAY,gBAAgB,CAAC,IAAI,EAAE,CAAC,EACvC,IAAI,mBAAS,CAAC,IAAA,uBAAa,EAAC,gBAAgB,CAAC,CAAC,EAC9C,IAAI,gBAAM,CAAC,IAAA,qBAAW,EAAC,gBAAgB,CAAC,CAAC,CAC1C,CACJ;QAED,OAAO,EAAE;YACP,IAAI,iBAAO,CACT,EAAE,CAAC,kBAAkB,CAAC,EACtB,IAAI,gBAAM,CAAC,IAAA,sBAAY,EAAC,YAAY,CAAC,CAAC,CACvC;YACD,IAAI,iBAAO,CAAC,EAAE,CAAC,0BAA0B,CAAC,EAAE,IAAI,gBAAM,CAAC,yBAAe,CAAC,CAAC;YACxE,IAAI,iBAAO,CAAC,EAAE,CAAC,mBAAmB,CAAC,EAAE,IAAI,gBAAM,CAAC,kBAAQ,CAAC,CAAC;YAC1D,IAAI,iBAAO,CAAC,EAAE,CAAC,cAAc,CAAC,EAAE,IAAI,gBAAM,CAAC,IAAA,qBAAW,EAAC,MAAM,CAAC,CAAC,CAAC;SACjE;QAED,QAAQ,EAAE;YACR,IAAI,kBAAQ,CACV,EAAE,CAAC,2BAA2B,CAAC,EAC/B,IAAI,gBAAM,CAAC,IAAA,yBAAc,EAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CACjD;YACD,IAAI,kBAAQ;YACV,6GAA6G;YAC7G,EAAE,CAAC,wBAAwB,CAAC,EAC5B,IAAI,mBAAS,CAAC,mBAAQ,CAAC,EACvB,IAAI,mBAAS,CAAC,IAAA,oBAAS,EAAC,YAAY,CAAC,CAAC,EACtC,IAAI,mBAAS,CAAC,IAAA,mBAAQ,EAAC,YAAY,CAAC,CAAC,EACrC,IAAI,gBAAM,CAAC,IAAA,uBAAY,EAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CACrD;SACF;QAED,SAAS,EAAE;YACT,IAAI,mBAAS,CAAC,EAAE,CAAC,gBAAgB,CAAC,EAAE,IAAI,gBAAM,CAAC,IAAA,yBAAa,EAAC,MAAM,CAAC,CAAC,CAAC;YACtE,IAAI,mBAAS;YACX,qDAAqD;YACrD,EAAE,CAAC,sBAAsB,CAAC,EAC1B,IAAI,mBAAS,CAAC,8BAAkB,CAAC,EACjC,IAAI,mBAAS,CAAC,2BAAe,CAAC,EAC9B,IAAI,gBAAM,CAAC,IAAA,qBAAS,EAAC,gBAAgB,CAAC,CAAC,CACxC;YACD,IAAI,mBAAS,CAAC,EAAE,CAAC,sBAAsB,CAAC,EAAE,IAAI,gBAAM,CAAC,sBAAU,CAAC,CAAC;YACjE,IAAI,mBAAS,CACX,EAAE,CAAC,+BAA+B,CAAC,EACnC,IAAI,gBAAM,CAAC,IAAA,8BAAkB,EAAC,uBAAuB,CAAC,CAAC,CACxD;YACD,IAAI,mBAAS,CACX,EAAE,CAAC,sBAAsB,CAAC,EAC1B,IAAI,mBAAS,CAAC,IAAA,wBAAY,EAAC,iBAAiB,CAAC,CAAC,EAC9C,IAAI,gBAAM,CAAC,IAAA,qBAAS,EAAC,iBAAiB,CAAC,CAAC,CACzC;YACD,IAAI,mBAAS,CACX,EAAE,CAAC,2BAA2B,CAAC,EAC/B,IAAI,mBAAS,CAAC,IAAA,oBAAQ,EAAC,iBAAiB,CAAC,CAAC,EAC1C,IAAI,gBAAM,CAAC,IAAA,6BAAiB,EAAC,iBAAiB,CAAC,CAAC,CACjD;SACF;QAED,oBAAoB,EAAE;YACpB,IAAI,8BAAoB;YACtB,oGAAoG;YACpG,EAAE,CAAC,kCAAkC,CAAC,EACtC,IAAI,mBAAS,CACX,CAAC,IAAU,EAAE,YAAoB,EAAE,QAAgB,EAAW,EAAE,CAC9D,QAAQ,GAAG,CAAC,CACf,EACD,IAAI,gBAAM,CACR,CAAC,IAAU,EAAE,YAAoB,EAAE,QAAgB,EAAU,EAAE,CAC7D,SAAS,KAAK,SAAS;gBACrB,CAAC,CAAC,IAAA,4BAAY,EAAC,QAAQ,EAAE,YAAY,EAAE,eAAyB,CAAC;gBACjE,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,QAAQ,EAAE,YAAY,CAAC,CACrD,CACF;SACF;QAED,MAAM,EAAE,CAAC,IAAI,gBAAM,CAAC,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,gBAAM,CAAC,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAEvE,UAAU,EACR,SAAS,KAAK,SAAS;YACrB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,IAAI,oBAAU;gBACZ,+FAA+F;gBAC/F,EAAE,CAAC,iCAAiC,CAAC,EACrC,IAAI,mBAAS,CAAC,qBAAQ,CAAC,EACvB,IAAI,mBAAS,CAAC,IAAA,2BAAc,EAAC,SAAS,CAAC,CAAC,EACxC,IAAI,gBAAM,CAAC,uBAAU,CAAC,CACvB;aACF;QAEP,SAAS,EAAE;YACT,IAAI,mBAAS,CAAC,EAAE,CAAC,kBAAkB,CAAC,EAAE,IAAI,gBAAM,CAAC,IAAA,yBAAa,EAAC,MAAM,CAAC,CAAC,CAAC;YACxE,IAAI,mBAAS;YACX,iEAAiE;YACjE,EAAE,CAAC,wBAAwB,CAAC,EAC5B,IAAI,gBAAM,CAAC,IAAA,uBAAW,EAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAC,CAC7D;YACD,IAAI,mBAAS,CAAC,EAAE,CAAC,qBAAqB,CAAC,EAAE,IAAI,gBAAM,CAAC,gBAAI,CAAC,CAAC;SAC3D;QAED,YAAY,EAAE;YACZ,IAAI,gBAAM,CACR,GAAG,MAAM,6BAA6B,EACtC,IAAI,mBAAS,CAAC,IAAA,6BAAc,EAAC,YAAY,CAAC,CAAC,EAC3C,IAAI,gBAAM,CAAC,IAAA,0BAAW,EAAC,YAAY,EAAE,UAAU,CAAC,CAAC,CAClD;YACD,IAAI,gBAAM,CACR,GAAG,MAAM,+BAA+B,EACxC,IAAI,mBAAS,CAAC,IAAA,+BAAgB,EAAC,YAAY,CAAC,CAAC,EAC7C,IAAI,gBAAM,CAAC,IAAA,4BAAa,EAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CACtD;SACF;QAED,MAAM,EAAE,CAAC,IAAI,gBAAM,CAAC,EAAE,CAAC,cAAc,CAAC,EAAE,IAAI,gBAAM,CAAC,oBAAW,CAAC,CAAC,CAAC;QAEjE,WAAW,EAAE;YACX,IAAI,qBAAW,CACb,EAAE,CAAC,kBAAkB,CAAC,EACtB,IAAI,gBAAM,CACR,CAAC,IAAU,EAAE,MAAc,EAAE,cAAsB,EAAQ,EAAE;gBAC3D,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;YAChE,CAAC,CACF,CACF;SACF;QAED,WAAW,EAAE;YACX,IAAI,qBAAW,CACb,EAAE,CAAC,kBAAkB,CAAC,EACtB,IAAI,gBAAM,CAAC,IAAA,6BAAe,EAAC,MAAM,CAAC,CAAC,CACpC;SACF;QAED,YAAY,EAAE;YACZ,IAAI,sBAAY,CACd,EAAE,CAAC,4BAA4B,CAAC,EAChC,IAAI,mBAAS,CAAC,0BAAW,CAAC,EAC1B,IAAI,gBAAM,CAAC,yBAAU,CAAC,CACvB;YACD,IAAI,sBAAY,CACd,EAAE,CAAC,gCAAgC,CAAC,EACpC,IAAI,mBAAS,CAAC,8BAAe,CAAC,EAC9B,IAAI,gBAAM,CACR,SAAS,KAAK,SAAS;gBACrB,CAAC,CAAC,IAAA,yBAAU,EAAC,eAAyB,EAAE,qBAAqB,CAAC;gBAC9D,CAAC,CAAC,CAAC,IAAU,EAAE,YAAoB,EAAW,EAAE;oBAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EACpC,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC;oBAErC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBAEpB,OAAO,SAAS,CAAC,QAAQ,CACvB,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,qBAAqB,CACtB,CAAC;gBACJ,CAAC,CACN,CACF;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAvNW,QAAA,cAAc,kBAuNzB;AAIF,kBAAe,sBAAc,CAAC"}
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
  ),
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.2",
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 => {
@@ -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',