@civ-clone/base-unit-fuel 0.0.0-stage → 0.1.0
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/.gitattributes +4 -0
- package/.github/workflows/build-on-push.yml +39 -0
- package/.prettierrc +4 -0
- package/LICENSE +21 -0
- package/README.md +57 -2
- package/lib/Aloft.d.ts +14 -0
- package/lib/Aloft.js +25 -0
- package/lib/Aloft.js.map +1 -0
- package/lib/Aloft.ts +29 -0
- package/lib/Fuel.d.ts +8 -0
- package/lib/Fuel.js +14 -0
- package/lib/Fuel.js.map +1 -0
- package/lib/Fuel.ts +10 -0
- package/lib/Rules.d.ts +10 -0
- package/lib/Rules.js +16 -0
- package/lib/Rules.js.map +1 -0
- package/lib/Rules.ts +12 -0
- package/lib/fuelRules.d.ts +95 -0
- package/lib/fuelRules.js +133 -0
- package/lib/fuelRules.js.map +1 -0
- package/lib/fuelRules.ts +277 -0
- package/lib/reach.d.ts +41 -0
- package/lib/reach.js +47 -0
- package/lib/reach.js.map +1 -0
- package/lib/reach.ts +97 -0
- package/lib/refuel.d.ts +38 -0
- package/lib/refuel.js +35 -0
- package/lib/refuel.js.map +1 -0
- package/lib/refuel.ts +98 -0
- package/package.json +43 -4
- package/tests/fuel.test.ts +460 -0
- package/tests/save.test.ts +54 -0
- package/tsconfig.json +16 -0
package/lib/fuelRules.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { OutOfFuel, Refuel } from './Rules';
|
|
2
|
+
import {
|
|
3
|
+
RuleRegistry,
|
|
4
|
+
instance as ruleRegistryInstance,
|
|
5
|
+
} from '@civ-clone/core-rule/RuleRegistry';
|
|
6
|
+
import {
|
|
7
|
+
UnitImprovementRegistry,
|
|
8
|
+
instance as unitImprovementRegistryInstance,
|
|
9
|
+
} from '@civ-clone/core-unit-improvement/UnitImprovementRegistry';
|
|
10
|
+
import {
|
|
11
|
+
UnitRegistry,
|
|
12
|
+
instance as unitRegistryInstance,
|
|
13
|
+
} from '@civ-clone/core-unit/UnitRegistry';
|
|
14
|
+
import Action from '@civ-clone/core-unit/Action';
|
|
15
|
+
import Aloft from './Aloft';
|
|
16
|
+
import Criterion from '@civ-clone/core-rule/Criterion';
|
|
17
|
+
import Effect from '@civ-clone/core-rule/Effect';
|
|
18
|
+
import Fuel from './Fuel';
|
|
19
|
+
import Moved from '@civ-clone/core-unit/Rules/Moved';
|
|
20
|
+
import Player from '@civ-clone/core-player/Player';
|
|
21
|
+
import Tile from '@civ-clone/core-world/Tile';
|
|
22
|
+
import TurnEnd from '@civ-clone/core-player/Rules/TurnEnd';
|
|
23
|
+
import Unit from '@civ-clone/core-unit/Unit';
|
|
24
|
+
import UnitImprovement from '@civ-clone/core-unit-improvement/UnitImprovement';
|
|
25
|
+
|
|
26
|
+
export interface FuelDependencies {
|
|
27
|
+
ruleRegistry?: RuleRegistry;
|
|
28
|
+
unitImprovementRegistry?: UnitImprovementRegistry;
|
|
29
|
+
unitRegistry?: UnitRegistry;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A unit's range: how many turns it can end away from a refuelling point. */
|
|
33
|
+
export type Range = (unit: Unit) => number;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* When a unit's turns out are counted:
|
|
37
|
+
*
|
|
38
|
+
* - `'turn-end'`: at the end of each of its player's turns, wherever it is (`checkFuel`);
|
|
39
|
+
* - `'out-of-moves'`: after each move, as the original does (`checkFuelAfterMove`).
|
|
40
|
+
*/
|
|
41
|
+
export type CountOn = 'turn-end' | 'out-of-moves';
|
|
42
|
+
|
|
43
|
+
export interface FuelOptions {
|
|
44
|
+
/**
|
|
45
|
+
* The rules' ids are `<prefix>:player/turn-end/fuel` (or `<prefix>:unit/moved/fuel`) and
|
|
46
|
+
* `<prefix>:unit/out-of-fuel/destroy`. Unnamed without.
|
|
47
|
+
*/
|
|
48
|
+
prefix?: string;
|
|
49
|
+
/** A unit's range: its `Fuel` yield by default. */
|
|
50
|
+
range?: Range;
|
|
51
|
+
/** When turns out are counted: `'turn-end'` by default. */
|
|
52
|
+
countOn?: CountOn;
|
|
53
|
+
/** For `'out-of-moves'`, whether a `Moved` action is a move: by default, one to another square (attacks included). */
|
|
54
|
+
countsAsMove?: (unit: Unit, action: Action) => boolean;
|
|
55
|
+
/** For `'out-of-moves'`, whether the action was an attack: by default, the unit didn't end up on its target. */
|
|
56
|
+
isAttack?: (unit: Unit, action: Action) => boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A unit's `Fuel` yield. */
|
|
60
|
+
export const fuelRange: Range = (unit: Unit): number => {
|
|
61
|
+
const [range] = unit.yield(new Fuel());
|
|
62
|
+
|
|
63
|
+
return range.value();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** `unit`'s `Aloft` mark, if it has one. */
|
|
67
|
+
export const aloftOf = (
|
|
68
|
+
unitImprovementRegistry: UnitImprovementRegistry,
|
|
69
|
+
unit: Unit
|
|
70
|
+
): Aloft | undefined =>
|
|
71
|
+
unitImprovementRegistry
|
|
72
|
+
.getByUnit(unit)
|
|
73
|
+
.find(
|
|
74
|
+
(unitImprovement: UnitImprovement): boolean =>
|
|
75
|
+
unitImprovement instanceof Aloft
|
|
76
|
+
) as Aloft | undefined;
|
|
77
|
+
|
|
78
|
+
/** How many turns `unit` has ended away from a refuelling point since it last refuelled. */
|
|
79
|
+
export const turnsAloft = (
|
|
80
|
+
unitImprovementRegistry: UnitImprovementRegistry,
|
|
81
|
+
unit: Unit
|
|
82
|
+
): number => aloftOf(unitImprovementRegistry, unit)?.turns() ?? 0;
|
|
83
|
+
|
|
84
|
+
/** Whether `unit` refuels on `tile` (see `Refuel`). */
|
|
85
|
+
export const refuels = (
|
|
86
|
+
ruleRegistry: RuleRegistry,
|
|
87
|
+
unit: Unit,
|
|
88
|
+
tile: Tile = unit.tile()
|
|
89
|
+
): boolean =>
|
|
90
|
+
ruleRegistry
|
|
91
|
+
.process(Refuel, unit, tile)
|
|
92
|
+
.some((answer: boolean | void): boolean => answer === true);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* At the end of `player`'s turn, for each of its units with a range (above 0): one that refuels where it is loses its
|
|
96
|
+
* `Aloft` mark; any other has ended one more turn out, and runs out of fuel (`OutOfFuel`) once that reaches its range.
|
|
97
|
+
*/
|
|
98
|
+
export const checkFuel =
|
|
99
|
+
(
|
|
100
|
+
ruleRegistry: RuleRegistry,
|
|
101
|
+
unitImprovementRegistry: UnitImprovementRegistry,
|
|
102
|
+
unitRegistry: UnitRegistry,
|
|
103
|
+
range: Range
|
|
104
|
+
) =>
|
|
105
|
+
(player: Player): void =>
|
|
106
|
+
unitRegistry.getByPlayer(player).forEach((unit: Unit): void => {
|
|
107
|
+
if (unit.destroyed()) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const unitRange = range(unit);
|
|
112
|
+
|
|
113
|
+
if (!(unitRange > 0)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const aloft = aloftOf(unitImprovementRegistry, unit);
|
|
118
|
+
|
|
119
|
+
if (refuels(ruleRegistry, unit)) {
|
|
120
|
+
if (aloft !== undefined) {
|
|
121
|
+
unitImprovementRegistry.unregister(aloft);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const turns = (aloft?.turns() ?? 0) + 1;
|
|
128
|
+
|
|
129
|
+
if (turns < unitRange) {
|
|
130
|
+
if (aloft === undefined) {
|
|
131
|
+
unitImprovementRegistry.register(new Aloft(unit, turns));
|
|
132
|
+
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
aloft.setTurns(turns);
|
|
137
|
+
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (aloft !== undefined) {
|
|
142
|
+
unitImprovementRegistry.unregister(aloft);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
ruleRegistry.process(OutOfFuel, unit);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* After `unit` moves (OpenCiv2, `unit_move_dir_air_fuel`), for a unit with a range (above 0):
|
|
150
|
+
*
|
|
151
|
+
* - where it refuels, it loses its `Aloft` mark and its remaining moves (it has landed), except that a unit with a
|
|
152
|
+
* range of 1 that attacked and still has moves left hasn't landed;
|
|
153
|
+
* - anywhere else, once it has no moves left, it has spent one more turn out, and runs out of fuel (`OutOfFuel`) when
|
|
154
|
+
* that reaches its range.
|
|
155
|
+
*/
|
|
156
|
+
export const checkFuelAfterMove =
|
|
157
|
+
(
|
|
158
|
+
ruleRegistry: RuleRegistry,
|
|
159
|
+
unitImprovementRegistry: UnitImprovementRegistry,
|
|
160
|
+
range: Range,
|
|
161
|
+
isAttack: (unit: Unit, action: Action) => boolean
|
|
162
|
+
) =>
|
|
163
|
+
(unit: Unit, action: Action): void => {
|
|
164
|
+
if (unit.destroyed()) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const unitRange = range(unit);
|
|
169
|
+
|
|
170
|
+
if (!(unitRange > 0)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const aloft = aloftOf(unitImprovementRegistry, unit),
|
|
175
|
+
landed =
|
|
176
|
+
refuels(ruleRegistry, unit) &&
|
|
177
|
+
!(
|
|
178
|
+
unitRange === 1 &&
|
|
179
|
+
unit.moves().value() > 0 &&
|
|
180
|
+
isAttack(unit, action)
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (landed) {
|
|
184
|
+
if (aloft !== undefined) {
|
|
185
|
+
unitImprovementRegistry.unregister(aloft);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
unit.moves().set(0);
|
|
189
|
+
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (unit.moves().value() > 0) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const turns = (aloft?.turns() ?? 0) + 1;
|
|
198
|
+
|
|
199
|
+
if (turns < unitRange) {
|
|
200
|
+
if (aloft === undefined) {
|
|
201
|
+
unitImprovementRegistry.register(new Aloft(unit, turns));
|
|
202
|
+
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
aloft.setTurns(turns);
|
|
207
|
+
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (aloft !== undefined) {
|
|
212
|
+
unitImprovementRegistry.unregister(aloft);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
ruleRegistry.process(OutOfFuel, unit);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* A ruleset's fuel rules: one that counts the turns each unit with a range spends away from a refuelling point, and an
|
|
220
|
+
* `OutOfFuel` rule that destroys a unit that runs out. A game adds its own `Refuel` rules (see `lib/refuel`), and its
|
|
221
|
+
* own `OutOfFuel` rules for anything else that should happen (a notice, say).
|
|
222
|
+
*
|
|
223
|
+
* With `countOn: 'turn-end'` (the default) the count is a `TurnEnd` rule (`checkFuel`): it doesn't matter whether a
|
|
224
|
+
* unit spent its moves. With `'out-of-moves'` it is a `Moved` rule (`checkFuelAfterMove`), as the original counts it.
|
|
225
|
+
*/
|
|
226
|
+
export const fuelRules = (
|
|
227
|
+
{
|
|
228
|
+
ruleRegistry = ruleRegistryInstance,
|
|
229
|
+
unitImprovementRegistry = unitImprovementRegistryInstance,
|
|
230
|
+
unitRegistry = unitRegistryInstance,
|
|
231
|
+
}: FuelDependencies,
|
|
232
|
+
{
|
|
233
|
+
prefix,
|
|
234
|
+
range = fuelRange,
|
|
235
|
+
countOn = 'turn-end',
|
|
236
|
+
countsAsMove = (unit: Unit, action: Action): boolean =>
|
|
237
|
+
action.from() !== action.to(),
|
|
238
|
+
isAttack = (unit: Unit, action: Action): boolean =>
|
|
239
|
+
unit.tile() !== action.to(),
|
|
240
|
+
}: FuelOptions = {}
|
|
241
|
+
): (TurnEnd | Moved | OutOfFuel)[] => {
|
|
242
|
+
const id = (name: string): string[] =>
|
|
243
|
+
prefix === undefined ? [] : [`${prefix}:${name}`];
|
|
244
|
+
|
|
245
|
+
return [
|
|
246
|
+
countOn === 'out-of-moves'
|
|
247
|
+
? new Moved(
|
|
248
|
+
...id('unit/moved/fuel'),
|
|
249
|
+
new Criterion(countsAsMove),
|
|
250
|
+
new Effect(
|
|
251
|
+
checkFuelAfterMove(
|
|
252
|
+
ruleRegistry,
|
|
253
|
+
unitImprovementRegistry,
|
|
254
|
+
range,
|
|
255
|
+
isAttack
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
: new TurnEnd(
|
|
260
|
+
...id('player/turn-end/fuel'),
|
|
261
|
+
new Effect(
|
|
262
|
+
checkFuel(
|
|
263
|
+
ruleRegistry,
|
|
264
|
+
unitImprovementRegistry,
|
|
265
|
+
unitRegistry,
|
|
266
|
+
range
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
),
|
|
270
|
+
new OutOfFuel(
|
|
271
|
+
...id('unit/out-of-fuel/destroy'),
|
|
272
|
+
new Effect((unit: Unit): void => unit.destroy())
|
|
273
|
+
),
|
|
274
|
+
];
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export default fuelRules;
|
package/lib/reach.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Range } from './fuelRules';
|
|
2
|
+
import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
|
|
3
|
+
import Tile from '@civ-clone/core-world/Tile';
|
|
4
|
+
import Unit from '@civ-clone/core-unit/Unit';
|
|
5
|
+
import UnitImprovementRegistry from '@civ-clone/core-unit-improvement/UnitImprovementRegistry';
|
|
6
|
+
export interface FuelState {
|
|
7
|
+
/** The unit's range. */
|
|
8
|
+
range: number;
|
|
9
|
+
/** The turns it has ended away from a refuelling point since it last refuelled. */
|
|
10
|
+
turnsAloft: number;
|
|
11
|
+
/**
|
|
12
|
+
* The turns it has left, this one included: it must be somewhere it refuels by the end of the last of them. 1 means
|
|
13
|
+
* by the end of this turn.
|
|
14
|
+
*/
|
|
15
|
+
turnsLeft: number;
|
|
16
|
+
/** How many moves it has before then: its moves left, and its full movement for each turn after this one. */
|
|
17
|
+
reach: number;
|
|
18
|
+
}
|
|
19
|
+
/** Where a fuelled `unit` stands, for a computer player deciding where to send it; `null` for a unit with no range. */
|
|
20
|
+
export declare const fuelState: (
|
|
21
|
+
unitImprovementRegistry: UnitImprovementRegistry,
|
|
22
|
+
unit: Unit,
|
|
23
|
+
range?: Range
|
|
24
|
+
) => FuelState | null;
|
|
25
|
+
export interface RefuelInReachOptions {
|
|
26
|
+
range?: Range;
|
|
27
|
+
/** How many moves apart two squares are: `Tile.distanceFrom` by default. */
|
|
28
|
+
distance?: (from: Tile, to: Tile) => number;
|
|
29
|
+
/** The squares to look at: by default the area around the unit as far as its reach. */
|
|
30
|
+
candidates?: (unit: Unit, reach: number) => Tile[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The nearest square within `unit`'s reach (see `fuelState`) where it refuels (`Refuel`), its own square first; `null`
|
|
34
|
+
* if there is none, or the unit has no range. The first of equally near squares.
|
|
35
|
+
*/
|
|
36
|
+
export declare const refuelInReach: (
|
|
37
|
+
ruleRegistry: RuleRegistry,
|
|
38
|
+
unitImprovementRegistry: UnitImprovementRegistry,
|
|
39
|
+
unit: Unit,
|
|
40
|
+
{ range, distance, candidates }?: RefuelInReachOptions
|
|
41
|
+
) => Tile | null;
|
package/lib/reach.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.refuelInReach = exports.fuelState = void 0;
|
|
4
|
+
const fuelRules_1 = require("./fuelRules");
|
|
5
|
+
/** Where a fuelled `unit` stands, for a computer player deciding where to send it; `null` for a unit with no range. */
|
|
6
|
+
const fuelState = (unitImprovementRegistry, unit, range = fuelRules_1.fuelRange) => {
|
|
7
|
+
const unitRange = range(unit);
|
|
8
|
+
if (!(unitRange > 0)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const aloft = (0, fuelRules_1.turnsAloft)(unitImprovementRegistry, unit), turnsLeft = Math.max(0, unitRange - aloft);
|
|
12
|
+
return {
|
|
13
|
+
range: unitRange,
|
|
14
|
+
turnsAloft: aloft,
|
|
15
|
+
turnsLeft,
|
|
16
|
+
reach: turnsLeft === 0
|
|
17
|
+
? 0
|
|
18
|
+
: unit.moves().value() + unit.movement().value() * (turnsLeft - 1),
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
exports.fuelState = fuelState;
|
|
22
|
+
/**
|
|
23
|
+
* The nearest square within `unit`'s reach (see `fuelState`) where it refuels (`Refuel`), its own square first; `null`
|
|
24
|
+
* if there is none, or the unit has no range. The first of equally near squares.
|
|
25
|
+
*/
|
|
26
|
+
const refuelInReach = (ruleRegistry, unitImprovementRegistry, unit, { range = fuelRules_1.fuelRange, distance = (from, to) => from.distanceFrom(to), candidates = (candidate, reach) => candidate.tile().getSurroundingArea(Math.floor(reach)).entries(), } = {}) => {
|
|
27
|
+
const state = (0, exports.fuelState)(unitImprovementRegistry, unit, range);
|
|
28
|
+
if (state === null) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const from = unit.tile();
|
|
32
|
+
if ((0, fuelRules_1.refuels)(ruleRegistry, unit, from)) {
|
|
33
|
+
return from;
|
|
34
|
+
}
|
|
35
|
+
let nearest = null;
|
|
36
|
+
candidates(unit, state.reach).forEach((tile) => {
|
|
37
|
+
const away = distance(from, tile);
|
|
38
|
+
if (away <= state.reach &&
|
|
39
|
+
(nearest === null || away < nearest[1]) &&
|
|
40
|
+
(0, fuelRules_1.refuels)(ruleRegistry, unit, tile)) {
|
|
41
|
+
nearest = [tile, away];
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return nearest === null ? null : nearest[0];
|
|
45
|
+
};
|
|
46
|
+
exports.refuelInReach = refuelInReach;
|
|
47
|
+
//# sourceMappingURL=reach.js.map
|
package/lib/reach.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reach.js","sourceRoot":"","sources":["reach.ts"],"names":[],"mappings":";;;AAAA,2CAAoE;AAoBpE,uHAAuH;AAChH,MAAM,SAAS,GAAG,CACvB,uBAAgD,EAChD,IAAU,EACV,QAAe,qBAAS,EACN,EAAE;IACpB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAE9B,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE;QACpB,OAAO,IAAI,CAAC;KACb;IAED,MAAM,KAAK,GAAG,IAAA,sBAAU,EAAC,uBAAuB,EAAE,IAAI,CAAC,EACrD,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;IAE7C,OAAO;QACL,KAAK,EAAE,SAAS;QAChB,UAAU,EAAE,KAAK;QACjB,SAAS;QACT,KAAK,EACH,SAAS,KAAK,CAAC;YACb,CAAC,CAAC,CAAC;YACH,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC;KACvE,CAAC;AACJ,CAAC,CAAC;AAvBW,QAAA,SAAS,aAuBpB;AAUF;;;GAGG;AACI,MAAM,aAAa,GAAG,CAC3B,YAA0B,EAC1B,uBAAgD,EAChD,IAAU,EACV,EACE,KAAK,GAAG,qBAAS,EACjB,QAAQ,GAAG,CAAC,IAAU,EAAE,EAAQ,EAAU,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,EAClE,UAAU,GAAG,CAAC,SAAe,EAAE,KAAa,EAAU,EAAE,CACtD,SAAS,CAAC,IAAI,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,MAC1C,EAAE,EACf,EAAE;IACf,MAAM,KAAK,GAAG,IAAA,iBAAS,EAAC,uBAAuB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAE9D,IAAI,KAAK,KAAK,IAAI,EAAE;QAClB,OAAO,IAAI,CAAC;KACb;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAEzB,IAAI,IAAA,mBAAO,EAAC,YAAY,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;QACrC,OAAO,IAAI,CAAC;KACb;IAED,IAAI,OAAO,GAA0B,IAAI,CAAC;IAE1C,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAU,EAAQ,EAAE;QACzD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAElC,IACE,IAAI,IAAI,KAAK,CAAC,KAAK;YACnB,CAAC,OAAO,KAAK,IAAI,IAAI,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YACvC,IAAA,mBAAO,EAAC,YAAY,EAAE,IAAI,EAAE,IAAI,CAAC,EACjC;YACA,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;SACxB;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,OAA0B,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC,CAAC;AAtCW,QAAA,aAAa,iBAsCxB"}
|
package/lib/reach.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Range, fuelRange, refuels, turnsAloft } from './fuelRules';
|
|
2
|
+
import RuleRegistry from '@civ-clone/core-rule/RuleRegistry';
|
|
3
|
+
import Tile from '@civ-clone/core-world/Tile';
|
|
4
|
+
import Unit from '@civ-clone/core-unit/Unit';
|
|
5
|
+
import UnitImprovementRegistry from '@civ-clone/core-unit-improvement/UnitImprovementRegistry';
|
|
6
|
+
|
|
7
|
+
export interface FuelState {
|
|
8
|
+
/** The unit's range. */
|
|
9
|
+
range: number;
|
|
10
|
+
/** The turns it has ended away from a refuelling point since it last refuelled. */
|
|
11
|
+
turnsAloft: number;
|
|
12
|
+
/**
|
|
13
|
+
* The turns it has left, this one included: it must be somewhere it refuels by the end of the last of them. 1 means
|
|
14
|
+
* by the end of this turn.
|
|
15
|
+
*/
|
|
16
|
+
turnsLeft: number;
|
|
17
|
+
/** How many moves it has before then: its moves left, and its full movement for each turn after this one. */
|
|
18
|
+
reach: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Where a fuelled `unit` stands, for a computer player deciding where to send it; `null` for a unit with no range. */
|
|
22
|
+
export const fuelState = (
|
|
23
|
+
unitImprovementRegistry: UnitImprovementRegistry,
|
|
24
|
+
unit: Unit,
|
|
25
|
+
range: Range = fuelRange
|
|
26
|
+
): FuelState | null => {
|
|
27
|
+
const unitRange = range(unit);
|
|
28
|
+
|
|
29
|
+
if (!(unitRange > 0)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const aloft = turnsAloft(unitImprovementRegistry, unit),
|
|
34
|
+
turnsLeft = Math.max(0, unitRange - aloft);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
range: unitRange,
|
|
38
|
+
turnsAloft: aloft,
|
|
39
|
+
turnsLeft,
|
|
40
|
+
reach:
|
|
41
|
+
turnsLeft === 0
|
|
42
|
+
? 0
|
|
43
|
+
: unit.moves().value() + unit.movement().value() * (turnsLeft - 1),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export interface RefuelInReachOptions {
|
|
48
|
+
range?: Range;
|
|
49
|
+
/** How many moves apart two squares are: `Tile.distanceFrom` by default. */
|
|
50
|
+
distance?: (from: Tile, to: Tile) => number;
|
|
51
|
+
/** The squares to look at: by default the area around the unit as far as its reach. */
|
|
52
|
+
candidates?: (unit: Unit, reach: number) => Tile[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The nearest square within `unit`'s reach (see `fuelState`) where it refuels (`Refuel`), its own square first; `null`
|
|
57
|
+
* if there is none, or the unit has no range. The first of equally near squares.
|
|
58
|
+
*/
|
|
59
|
+
export const refuelInReach = (
|
|
60
|
+
ruleRegistry: RuleRegistry,
|
|
61
|
+
unitImprovementRegistry: UnitImprovementRegistry,
|
|
62
|
+
unit: Unit,
|
|
63
|
+
{
|
|
64
|
+
range = fuelRange,
|
|
65
|
+
distance = (from: Tile, to: Tile): number => from.distanceFrom(to),
|
|
66
|
+
candidates = (candidate: Unit, reach: number): Tile[] =>
|
|
67
|
+
candidate.tile().getSurroundingArea(Math.floor(reach)).entries(),
|
|
68
|
+
}: RefuelInReachOptions = {}
|
|
69
|
+
): Tile | null => {
|
|
70
|
+
const state = fuelState(unitImprovementRegistry, unit, range);
|
|
71
|
+
|
|
72
|
+
if (state === null) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const from = unit.tile();
|
|
77
|
+
|
|
78
|
+
if (refuels(ruleRegistry, unit, from)) {
|
|
79
|
+
return from;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let nearest: [Tile, number] | null = null;
|
|
83
|
+
|
|
84
|
+
candidates(unit, state.reach).forEach((tile: Tile): void => {
|
|
85
|
+
const away = distance(from, tile);
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
away <= state.reach &&
|
|
89
|
+
(nearest === null || away < nearest[1]) &&
|
|
90
|
+
refuels(ruleRegistry, unit, tile)
|
|
91
|
+
) {
|
|
92
|
+
nearest = [tile, away];
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return nearest === null ? null : (nearest as [Tile, number])[0];
|
|
97
|
+
};
|
package/lib/refuel.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CityRegistry } from '@civ-clone/core-city/CityRegistry';
|
|
2
|
+
import { TileImprovementRegistry } from '@civ-clone/core-tile-improvement/TileImprovementRegistry';
|
|
3
|
+
import { TransportRegistry } from '@civ-clone/core-unit-transport/TransportRegistry';
|
|
4
|
+
import { UnitRegistry } from '@civ-clone/core-unit/UnitRegistry';
|
|
5
|
+
import { Refuel } from './Rules';
|
|
6
|
+
import TileImprovement from '@civ-clone/core-tile-improvement/TileImprovement';
|
|
7
|
+
import Unit from '@civ-clone/core-unit/Unit';
|
|
8
|
+
/** The units it applies to: all, without. */
|
|
9
|
+
export type Refuels = (unit: Unit) => boolean;
|
|
10
|
+
/** A `Refuel` rule: in one of the unit's player's own cities. */
|
|
11
|
+
export declare const refuelInOwnCity: (
|
|
12
|
+
cityRegistry?: CityRegistry,
|
|
13
|
+
applies?: Refuels,
|
|
14
|
+
id?: string
|
|
15
|
+
) => Refuel;
|
|
16
|
+
/** A `Refuel` rule: on a square with an `Improvement` (an airfield, say). */
|
|
17
|
+
export declare const refuelOnTileImprovement: (
|
|
18
|
+
Improvement: typeof TileImprovement,
|
|
19
|
+
tileImprovementRegistry?: TileImprovementRegistry,
|
|
20
|
+
applies?: Refuels,
|
|
21
|
+
id?: string
|
|
22
|
+
) => Refuel;
|
|
23
|
+
/**
|
|
24
|
+
* A `Refuel` rule: on a square with another of the player's units that `isBase` says the unit can refuel on (one that
|
|
25
|
+
* carries it, say).
|
|
26
|
+
*/
|
|
27
|
+
export declare const refuelInStackWith: (
|
|
28
|
+
isBase: (base: Unit, unit: Unit) => boolean,
|
|
29
|
+
unitRegistry?: UnitRegistry,
|
|
30
|
+
applies?: Refuels,
|
|
31
|
+
id?: string
|
|
32
|
+
) => Refuel;
|
|
33
|
+
/** A `Refuel` rule: aboard a transport. */
|
|
34
|
+
export declare const refuelAboard: (
|
|
35
|
+
transportRegistry?: TransportRegistry,
|
|
36
|
+
applies?: Refuels,
|
|
37
|
+
id?: string
|
|
38
|
+
) => Refuel;
|
package/lib/refuel.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.refuelAboard = exports.refuelInStackWith = exports.refuelOnTileImprovement = exports.refuelInOwnCity = void 0;
|
|
4
|
+
const CityRegistry_1 = require("@civ-clone/core-city/CityRegistry");
|
|
5
|
+
const TileImprovementRegistry_1 = require("@civ-clone/core-tile-improvement/TileImprovementRegistry");
|
|
6
|
+
const TransportRegistry_1 = require("@civ-clone/core-unit-transport/TransportRegistry");
|
|
7
|
+
const UnitRegistry_1 = require("@civ-clone/core-unit/UnitRegistry");
|
|
8
|
+
const Criterion_1 = require("@civ-clone/core-rule/Criterion");
|
|
9
|
+
const Effect_1 = require("@civ-clone/core-rule/Effect");
|
|
10
|
+
const Rules_1 = require("./Rules");
|
|
11
|
+
const refuel = (id, applies, where) => new Rules_1.Refuel(...(id === undefined ? [] : [id]), new Criterion_1.default((unit) => applies(unit)), new Criterion_1.default(where), new Effect_1.default(() => true));
|
|
12
|
+
const all = () => true;
|
|
13
|
+
/** A `Refuel` rule: in one of the unit's player's own cities. */
|
|
14
|
+
const refuelInOwnCity = (cityRegistry = CityRegistry_1.instance, applies = all, id) => refuel(id, applies, (unit, tile) => { var _a; return ((_a = cityRegistry.getByTile(tile)) === null || _a === void 0 ? void 0 : _a.player()) === unit.player(); });
|
|
15
|
+
exports.refuelInOwnCity = refuelInOwnCity;
|
|
16
|
+
/** A `Refuel` rule: on a square with an `Improvement` (an airfield, say). */
|
|
17
|
+
const refuelOnTileImprovement = (Improvement, tileImprovementRegistry = TileImprovementRegistry_1.instance, applies = all, id) => refuel(id, applies, (unit, tile) => tileImprovementRegistry
|
|
18
|
+
.getByTile(tile)
|
|
19
|
+
.some((tileImprovement) => tileImprovement instanceof Improvement));
|
|
20
|
+
exports.refuelOnTileImprovement = refuelOnTileImprovement;
|
|
21
|
+
/**
|
|
22
|
+
* A `Refuel` rule: on a square with another of the player's units that `isBase` says the unit can refuel on (one that
|
|
23
|
+
* carries it, say).
|
|
24
|
+
*/
|
|
25
|
+
const refuelInStackWith = (isBase, unitRegistry = UnitRegistry_1.instance, applies = all, id) => refuel(id, applies, (unit, tile) => unitRegistry
|
|
26
|
+
.getByTile(tile)
|
|
27
|
+
.some((base) => base !== unit &&
|
|
28
|
+
!base.destroyed() &&
|
|
29
|
+
base.player() === unit.player() &&
|
|
30
|
+
isBase(base, unit)));
|
|
31
|
+
exports.refuelInStackWith = refuelInStackWith;
|
|
32
|
+
/** A `Refuel` rule: aboard a transport. */
|
|
33
|
+
const refuelAboard = (transportRegistry = TransportRegistry_1.instance, applies = all, id) => refuel(id, applies, (unit) => transportRegistry.hasUnit(unit));
|
|
34
|
+
exports.refuelAboard = refuelAboard;
|
|
35
|
+
//# sourceMappingURL=refuel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refuel.js","sourceRoot":"","sources":["refuel.ts"],"names":[],"mappings":";;;AAAA,oEAG2C;AAC3C,sGAGkE;AAClE,wFAG0D;AAC1D,oEAG2C;AAC3C,8DAAuD;AACvD,wDAAiD;AACjD,mCAAiC;AAQjC,MAAM,MAAM,GAAG,CACb,EAAsB,EACtB,OAAgB,EAChB,KAA0C,EAClC,EAAE,CACV,IAAI,cAAM,CACR,GAAI,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAc,EAC/C,IAAI,mBAAS,CAAC,CAAC,IAAU,EAAW,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EACrD,IAAI,mBAAS,CAAC,KAAK,CAAC,EACpB,IAAI,gBAAM,CAAC,GAAY,EAAE,CAAC,IAAI,CAAC,CAChC,CAAC;AAEJ,MAAM,GAAG,GAAY,GAAY,EAAE,CAAC,IAAI,CAAC;AAEzC,iEAAiE;AAC1D,MAAM,eAAe,GAAG,CAC7B,eAA6B,uBAAoB,EACjD,UAAmB,GAAG,EACtB,EAAW,EACH,EAAE,CACV,MAAM,CACJ,EAAE,EACF,OAAO,EACP,CAAC,IAAU,EAAE,IAAU,EAAW,EAAE,WAClC,OAAA,CAAA,MAAA,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,0CAAE,MAAM,EAAE,MAAK,IAAI,CAAC,MAAM,EAAE,CAAA,EAAA,CAC3D,CAAC;AAVS,QAAA,eAAe,mBAUxB;AAEJ,6EAA6E;AACtE,MAAM,uBAAuB,GAAG,CACrC,WAAmC,EACnC,0BAAmD,kCAA+B,EAClF,UAAmB,GAAG,EACtB,EAAW,EACH,EAAE,CACV,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,IAAU,EAAE,IAAU,EAAW,EAAE,CACtD,uBAAuB;KACpB,SAAS,CAAC,IAAI,CAAC;KACf,IAAI,CACH,CAAC,eAAgC,EAAW,EAAE,CAC5C,eAAe,YAAY,WAAW,CACzC,CACJ,CAAC;AAbS,QAAA,uBAAuB,2BAahC;AAEJ;;;GAGG;AACI,MAAM,iBAAiB,GAAG,CAC/B,MAA2C,EAC3C,eAA6B,uBAAoB,EACjD,UAAmB,GAAG,EACtB,EAAW,EACH,EAAE,CACV,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,IAAU,EAAE,IAAU,EAAW,EAAE,CACtD,YAAY;KACT,SAAS,CAAC,IAAI,CAAC;KACf,IAAI,CACH,CAAC,IAAU,EAAW,EAAE,CACtB,IAAI,KAAK,IAAI;IACb,CAAC,IAAI,CAAC,SAAS,EAAE;IACjB,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,EAAE;IAC/B,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CACrB,CACJ,CAAC;AAhBS,QAAA,iBAAiB,qBAgB1B;AAEJ,2CAA2C;AACpC,MAAM,YAAY,GAAG,CAC1B,oBAAuC,4BAAyB,EAChE,UAAmB,GAAG,EACtB,EAAW,EACH,EAAE,CACV,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,IAAU,EAAW,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AALnE,QAAA,YAAY,gBAKuD"}
|
package/lib/refuel.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CityRegistry,
|
|
3
|
+
instance as cityRegistryInstance,
|
|
4
|
+
} from '@civ-clone/core-city/CityRegistry';
|
|
5
|
+
import {
|
|
6
|
+
TileImprovementRegistry,
|
|
7
|
+
instance as tileImprovementRegistryInstance,
|
|
8
|
+
} from '@civ-clone/core-tile-improvement/TileImprovementRegistry';
|
|
9
|
+
import {
|
|
10
|
+
TransportRegistry,
|
|
11
|
+
instance as transportRegistryInstance,
|
|
12
|
+
} from '@civ-clone/core-unit-transport/TransportRegistry';
|
|
13
|
+
import {
|
|
14
|
+
UnitRegistry,
|
|
15
|
+
instance as unitRegistryInstance,
|
|
16
|
+
} from '@civ-clone/core-unit/UnitRegistry';
|
|
17
|
+
import Criterion from '@civ-clone/core-rule/Criterion';
|
|
18
|
+
import Effect from '@civ-clone/core-rule/Effect';
|
|
19
|
+
import { Refuel } from './Rules';
|
|
20
|
+
import Tile from '@civ-clone/core-world/Tile';
|
|
21
|
+
import TileImprovement from '@civ-clone/core-tile-improvement/TileImprovement';
|
|
22
|
+
import Unit from '@civ-clone/core-unit/Unit';
|
|
23
|
+
|
|
24
|
+
/** The units it applies to: all, without. */
|
|
25
|
+
export type Refuels = (unit: Unit) => boolean;
|
|
26
|
+
|
|
27
|
+
const refuel = (
|
|
28
|
+
id: string | undefined,
|
|
29
|
+
applies: Refuels,
|
|
30
|
+
where: (unit: Unit, tile: Tile) => boolean
|
|
31
|
+
): Refuel =>
|
|
32
|
+
new Refuel(
|
|
33
|
+
...((id === undefined ? [] : [id]) as string[]),
|
|
34
|
+
new Criterion((unit: Unit): boolean => applies(unit)),
|
|
35
|
+
new Criterion(where),
|
|
36
|
+
new Effect((): boolean => true)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const all: Refuels = (): boolean => true;
|
|
40
|
+
|
|
41
|
+
/** A `Refuel` rule: in one of the unit's player's own cities. */
|
|
42
|
+
export const refuelInOwnCity = (
|
|
43
|
+
cityRegistry: CityRegistry = cityRegistryInstance,
|
|
44
|
+
applies: Refuels = all,
|
|
45
|
+
id?: string
|
|
46
|
+
): Refuel =>
|
|
47
|
+
refuel(
|
|
48
|
+
id,
|
|
49
|
+
applies,
|
|
50
|
+
(unit: Unit, tile: Tile): boolean =>
|
|
51
|
+
cityRegistry.getByTile(tile)?.player() === unit.player()
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/** A `Refuel` rule: on a square with an `Improvement` (an airfield, say). */
|
|
55
|
+
export const refuelOnTileImprovement = (
|
|
56
|
+
Improvement: typeof TileImprovement,
|
|
57
|
+
tileImprovementRegistry: TileImprovementRegistry = tileImprovementRegistryInstance,
|
|
58
|
+
applies: Refuels = all,
|
|
59
|
+
id?: string
|
|
60
|
+
): Refuel =>
|
|
61
|
+
refuel(id, applies, (unit: Unit, tile: Tile): boolean =>
|
|
62
|
+
tileImprovementRegistry
|
|
63
|
+
.getByTile(tile)
|
|
64
|
+
.some(
|
|
65
|
+
(tileImprovement: TileImprovement): boolean =>
|
|
66
|
+
tileImprovement instanceof Improvement
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A `Refuel` rule: on a square with another of the player's units that `isBase` says the unit can refuel on (one that
|
|
72
|
+
* carries it, say).
|
|
73
|
+
*/
|
|
74
|
+
export const refuelInStackWith = (
|
|
75
|
+
isBase: (base: Unit, unit: Unit) => boolean,
|
|
76
|
+
unitRegistry: UnitRegistry = unitRegistryInstance,
|
|
77
|
+
applies: Refuels = all,
|
|
78
|
+
id?: string
|
|
79
|
+
): Refuel =>
|
|
80
|
+
refuel(id, applies, (unit: Unit, tile: Tile): boolean =>
|
|
81
|
+
unitRegistry
|
|
82
|
+
.getByTile(tile)
|
|
83
|
+
.some(
|
|
84
|
+
(base: Unit): boolean =>
|
|
85
|
+
base !== unit &&
|
|
86
|
+
!base.destroyed() &&
|
|
87
|
+
base.player() === unit.player() &&
|
|
88
|
+
isBase(base, unit)
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/** A `Refuel` rule: aboard a transport. */
|
|
93
|
+
export const refuelAboard = (
|
|
94
|
+
transportRegistry: TransportRegistry = transportRegistryInstance,
|
|
95
|
+
applies: Refuels = all,
|
|
96
|
+
id?: string
|
|
97
|
+
): Refuel =>
|
|
98
|
+
refuel(id, applies, (unit: Unit): boolean => transportRegistry.hasUnit(unit));
|