@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 +15 -5
- 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/detonatedRules.d.ts +13 -0
- package/lib/detonatedRules.js +12 -1
- package/lib/detonatedRules.js.map +1 -1
- package/lib/detonatedRules.ts +23 -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/lib/movedRules.d.ts +14 -2
- package/lib/movedRules.js +17 -3
- package/lib/movedRules.js.map +1 -1
- package/lib/movedRules.ts +44 -5
- package/package.json +1 -1
- package/tests/lifecycle.test.ts +20 -0
- package/tests/rules.test.ts +220 -2
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 `
|
|
59
|
-
Defense), and leaving them out
|
|
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())
|
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/detonatedRules.d.ts
CHANGED
|
@@ -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;
|
package/lib/detonatedRules.js
CHANGED
|
@@ -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":";;;AAAA,uFAGwD;AACxD,oEAG2C;AAC3C,0DAGuC;AACvC,oEAG2C;AAC3C,sGAGkE;AAClE,oEAG2C;AAE3C,oEAA6D;AAC7D,0FAAmF;AACnF,wDAAiD;AACjD,+DAAwD;AACxD,6DAAsD;AACtD,4DAAqD;AAIrD,wDAAiE;AAEjE;;;GAGG;AACU,QAAA,qBAAqB,GAAG,IAAI,CAAC;AAE1C,4GAA4G;AACrG,MAAM,SAAS,GAAG,CACvB,IAAU,EACV,IAAU,EACV,qBAAyC,6BAA0B,EAC7D,EAAE;IACR,MAAM,UAAU,GAAG,kBAAkB,CAAC,SAAS,CAAC,IAAI,CAAC,EACnD,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAE3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE;QAC7B,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;KACzB;AACH,CAAC,CAAC;AAXW,QAAA,SAAS,aAWpB;
|
|
1
|
+
{"version":3,"file":"detonatedRules.js","sourceRoot":"","sources":["detonatedRules.ts"],"names":[],"mappings":"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"}
|
package/lib/detonatedRules.ts
CHANGED
|
@@ -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;
|
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/lib/movedRules.d.ts
CHANGED
|
@@ -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
|
-
{
|
|
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()
|
|
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(
|
package/lib/movedRules.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"movedRules.js","sourceRoot":"","sources":["movedRules.ts"],"names":[],"mappings":"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"}
|
package/lib/movedRules.ts
CHANGED
|
@@ -62,10 +62,40 @@ export interface MovedOptions {
|
|
|
62
62
|
attacks: ActionType[];
|
|
63
63
|
// Actions with an enemy that end a peace treaty without a grudge (walking onto, or pillaging, the partner's land).
|
|
64
64
|
noVendetta: ActionType[];
|
|
65
|
-
// The chance a `CoastBound` unit that ends its move out of sight of land is lost
|
|
66
|
-
|
|
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
|
-
{
|
|
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()
|
|
243
|
-
new Criterion((): boolean =>
|
|
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.
|
|
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": [
|
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
|
@@ -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(
|