@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 +5 -3
- package/lib/defeated.d.ts +5 -0
- package/lib/defeated.js +4 -1
- package/lib/defeated.js.map +1 -1
- package/lib/defeated.ts +11 -0
- package/lib/lifecycleRules.d.ts +3 -0
- package/lib/lifecycleRules.js +4 -2
- package/lib/lifecycleRules.js.map +1 -1
- package/lib/lifecycleRules.ts +8 -0
- package/package.json +1 -1
- package/tests/lifecycle.test.ts +20 -0
- package/tests/rules.test.ts +50 -0
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 `
|
|
59
|
-
Defense), and leaving them out
|
|
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())
|
package/lib/defeated.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defeated.js","sourceRoot":"","sources":["defeated.ts"],"names":[],"mappings":";;;AAEA,oEAA6D;
|
|
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) =>
|
package/lib/lifecycleRules.d.ts
CHANGED
|
@@ -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[];
|
package/lib/lifecycleRules.js
CHANGED
|
@@ -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)),
|
|
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,
|
|
1
|
+
{"version":3,"file":"lifecycleRules.js","sourceRoot":"","sources":["lifecycleRules.ts"],"names":[],"mappings":"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"}
|
package/lib/lifecycleRules.ts
CHANGED
|
@@ -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.
|
|
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": [
|
package/tests/lifecycle.test.ts
CHANGED
|
@@ -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 => {
|
package/tests/rules.test.ts
CHANGED
|
@@ -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',
|