ogamejs 4.1.0 → 4.2.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/README.md +31 -7
- package/package.json +1 -1
- package/src/fleets/combat.js +145 -36
- package/src/fleets/combatStatistics.js +189 -0
- package/src/fleets/index.js +7 -1
- package/types/fleets/combat.d.ts +88 -24
- package/types/fleets/combat.d.ts.map +1 -1
- package/types/fleets/combatStatistics.d.ts +67 -0
- package/types/fleets/combatStatistics.d.ts.map +1 -0
- package/types/fleets/index.d.ts +5 -2
- package/types/fleets/index.d.ts.map +1 -1
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -321,7 +321,7 @@ Ogame.Fleets.getDebris(Ogame.models.Destroyable[1], 100, 0.3);
|
|
|
321
321
|
|
|
322
322
|
#### `simulateCombat(attacker, defender, options = {})`
|
|
323
323
|
|
|
324
|
-
A full battle: up to six rounds, every unit fires once per round at a random enemy, rapid fire grants extra shots, shots below 1% of the target's shield bounce off, shields come back every round, and a unit under 70% hull may explode
|
|
324
|
+
A full battle: up to six rounds, every unit fires once per round at a random enemy, rapid fire grants extra shots, shots below 1% of the target's shield bounce off, shields come back every round, and a unit hit while under 70% hull may explode. Afterwards, destroyed defenses are rebuilt (70% chance each), the debris field gives the moon chance, and a winning attacker loads what its surviving ships can carry.
|
|
325
325
|
|
|
326
326
|
```javascript
|
|
327
327
|
const battle = Ogame.Fleets.simulateCombat(
|
|
@@ -333,26 +333,50 @@ const battle = Ogame.Fleets.simulateCombat(
|
|
|
333
333
|
fleet: [{ ship: Ogame.models.Destroyable[201], count: 200 }],
|
|
334
334
|
techs: { weapons: 10, shielding: 10, armour: 10 },
|
|
335
335
|
},
|
|
336
|
-
{
|
|
336
|
+
{
|
|
337
|
+
seed: 42,
|
|
338
|
+
debrisFactor: 0.3,
|
|
339
|
+
plunder: { resources: { metal: 500000, crystal: 200000, deuterium: 100000 }, hyperspaceLevel: 8 },
|
|
340
|
+
},
|
|
337
341
|
);
|
|
338
342
|
// → {
|
|
339
343
|
// winner: 'attacker' | 'defender' | 'draw',
|
|
340
344
|
// rounds: Number,
|
|
341
345
|
// seed: Number,
|
|
342
346
|
// attacker: { survivors: [{ ship, count }], losses: [{ ship, count }] },
|
|
343
|
-
// defender: { survivors: [...], losses: [...] },
|
|
347
|
+
// defender: { survivors: [...], losses: [...], rebuilt: [...] },
|
|
344
348
|
// debris: { metal, crystal, deuterium },
|
|
349
|
+
// moonChance: Number, // in %
|
|
350
|
+
// plunder: { metal, crystal, deuterium },
|
|
345
351
|
// }
|
|
346
352
|
```
|
|
347
353
|
|
|
348
|
-
|
|
354
|
+
| Option | Default | |
|
|
355
|
+
| --- | --- | --- |
|
|
356
|
+
| `seed` | `Date.now()` | replays the exact same fight |
|
|
357
|
+
| `debrisFactor` | `0.3` | share of the destroyed ships' metal and crystal left as debris |
|
|
358
|
+
| `deuteriumDebrisFactor` | `0` | same for deuterium |
|
|
359
|
+
| `defenseDebrisFactor` | `0` (`debrisFactor` with `defenseDebris: true`) | same for destroyed defenses |
|
|
360
|
+
| `repairFactor` | `0.7` | chance for each destroyed defense to be rebuilt |
|
|
361
|
+
| `plunder` | none | `{ resources, ratio = 0.5, hyperspaceLevel, hyperspaceMultiplier, bonus }`; the loot is spread like the resources on the planet |
|
|
362
|
+
|
|
363
|
+
#### `simulateCombats(attacker, defender, options = {})` / `getCombatStatistics(results)`
|
|
364
|
+
|
|
365
|
+
A battle is **random**, so one run is one possible outcome. `simulateCombats` runs the same battle `runs` times (100 by default, seeds `seed`, `seed + 1`…) and averages them; `getCombatStatistics` does the averaging on results you simulated yourself, for instance in batches from a Web Worker.
|
|
349
366
|
|
|
350
367
|
```javascript
|
|
351
|
-
|
|
352
|
-
|
|
368
|
+
Ogame.Fleets.simulateCombats(attacker, defender, { runs: 100, seed: 1, debrisFactor: 0.3 });
|
|
369
|
+
// → {
|
|
370
|
+
// runs: 100,
|
|
371
|
+
// outcomes: { attacker: 0.92, defender: 0, draw: 0.08 }, // shares, from 0 to 1
|
|
372
|
+
// rounds: 2.4,
|
|
373
|
+
// attacker: { survivors, losses, lostResources },
|
|
374
|
+
// defender: { survivors, losses, rebuilt, lostResources },
|
|
375
|
+
// debris, moonChance, plunder,
|
|
376
|
+
// }
|
|
353
377
|
```
|
|
354
378
|
|
|
355
|
-
|
|
379
|
+
Counts are averages, so not whole numbers. `lostResources` is what the units lost for good cost: rebuilt defenses do not count.
|
|
356
380
|
|
|
357
381
|
#### `getMoonbreakChance(moonSize, fleets)` / `getMoonbreakLosses(moonSize, fleets)` / `getMoonbreakWaves(rip)`
|
|
358
382
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ogamejs",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "OGame formulas as a dependency-free ESM library: building and research costs, production, build times, fleet debris and marketplace rates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/fleets/combat.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import DESTROYABLE, { ATTRIBUTES } from '../models/destroyable.js';
|
|
2
2
|
import getDebris from './getDebris.js';
|
|
3
|
+
import { getCargoCapacity } from './expedition.js';
|
|
4
|
+
import { MOON_DEBRIS_THRESHOLD, MAX_MOON_CHANCE } from './moonLock.js';
|
|
3
5
|
|
|
4
6
|
const ROUNDS = 6;
|
|
5
7
|
|
|
@@ -12,9 +14,18 @@ const MAX_SHOTS = 10000;
|
|
|
12
14
|
/** A shot weaker than this share of the target shield simply bounces off. */
|
|
13
15
|
const BOUNCE_RATIO = 0.01;
|
|
14
16
|
|
|
15
|
-
/** Below that share of its hull, a unit
|
|
17
|
+
/** Below that share of its hull, a unit hit by a shot may explode on the spot. */
|
|
16
18
|
const EXPLOSION_THRESHOLD = 0.7;
|
|
17
19
|
|
|
20
|
+
/** The share of destroyed defenses that come back after a battle, on most universes. */
|
|
21
|
+
const DEFAULT_REPAIR_FACTOR = 0.7;
|
|
22
|
+
|
|
23
|
+
/** The share of the defender's resources a winning attacker can carry away. */
|
|
24
|
+
const DEFAULT_PLUNDER_RATIO = 0.5;
|
|
25
|
+
|
|
26
|
+
/** Every 100 000 units of debris add 1% to the moon chance. */
|
|
27
|
+
const MOON_DEBRIS_PER_PERCENT = MOON_DEBRIS_THRESHOLD / MAX_MOON_CHANCE;
|
|
28
|
+
|
|
18
29
|
/** Rapid-fire tables point at library ids, so we need the way back. */
|
|
19
30
|
const LIBRARY_ID = new Map(
|
|
20
31
|
Object.entries(DESTROYABLE).map(([id, entry]) => [entry, Number(id)]),
|
|
@@ -106,6 +117,10 @@ function rapidFireAgainst(unit, target) {
|
|
|
106
117
|
/**
|
|
107
118
|
* One unit fires at a random enemy, then keeps firing while its rapid-fire
|
|
108
119
|
* bonus rolls in its favour.
|
|
120
|
+
*
|
|
121
|
+
* Every shot that gets through may blow up a target left under 70% of its
|
|
122
|
+
* hull, so a unit hit several times in a round gets several rolls. A wrecked
|
|
123
|
+
* unit stays a target until the end of the round, and shots on it are wasted.
|
|
109
124
|
*/
|
|
110
125
|
function fire(unit, enemies, random) {
|
|
111
126
|
for (let shots = 0; shots < MAX_SHOTS; shots += 1) {
|
|
@@ -117,13 +132,19 @@ function fire(unit, enemies, random) {
|
|
|
117
132
|
const damage = unit.attack;
|
|
118
133
|
|
|
119
134
|
// A shot too weak to dent the shield bounces off without doing anything.
|
|
120
|
-
if (damage >= target.shield * BOUNCE_RATIO) {
|
|
135
|
+
if (target.hull > 0 && damage >= target.shield * BOUNCE_RATIO) {
|
|
121
136
|
if (damage <= target.shield) {
|
|
122
137
|
target.shield -= damage;
|
|
123
138
|
} else {
|
|
124
139
|
target.hull -= damage - target.shield;
|
|
125
140
|
target.shield = 0;
|
|
126
141
|
}
|
|
142
|
+
|
|
143
|
+
const integrity = target.hull / target.maxHull;
|
|
144
|
+
|
|
145
|
+
if (target.hull > 0 && integrity < EXPLOSION_THRESHOLD && random() < 1 - integrity) {
|
|
146
|
+
target.hull = 0;
|
|
147
|
+
}
|
|
127
148
|
}
|
|
128
149
|
|
|
129
150
|
const rapid = rapidFireAgainst(unit, target);
|
|
@@ -135,11 +156,8 @@ function fire(unit, enemies, random) {
|
|
|
135
156
|
}
|
|
136
157
|
}
|
|
137
158
|
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
* shield back for the next round.
|
|
141
|
-
*/
|
|
142
|
-
function endRound(units, random) {
|
|
159
|
+
/** Destroyed units are removed, survivors get their shield back for the next round. */
|
|
160
|
+
function endRound(units) {
|
|
143
161
|
const survivors = [];
|
|
144
162
|
|
|
145
163
|
for (const unit of units) {
|
|
@@ -147,12 +165,6 @@ function endRound(units, random) {
|
|
|
147
165
|
continue;
|
|
148
166
|
}
|
|
149
167
|
|
|
150
|
-
const integrity = unit.hull / unit.maxHull;
|
|
151
|
-
|
|
152
|
-
if (integrity < EXPLOSION_THRESHOLD && random() < 1 - integrity) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
168
|
unit.shield = unit.maxShield;
|
|
157
169
|
survivors.push(unit);
|
|
158
170
|
}
|
|
@@ -186,12 +198,13 @@ function summarise(units, initial) {
|
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
function debrisOf(losses, options) {
|
|
189
|
-
const { debrisFactor, deuteriumDebrisFactor,
|
|
201
|
+
const { debrisFactor, deuteriumDebrisFactor, defenseDebrisFactor } = options;
|
|
190
202
|
|
|
191
203
|
return losses
|
|
192
|
-
// On most universes only ships leave debris behind.
|
|
193
|
-
.
|
|
194
|
-
|
|
204
|
+
// On most universes only ships leave debris behind, defenses have their own factor.
|
|
205
|
+
.map(({ ship, count }) => (ship.category === ATTRIBUTES.CATEGORIES.SHIPS
|
|
206
|
+
? getDebris(ship, count, debrisFactor, deuteriumDebrisFactor)
|
|
207
|
+
: getDebris(ship, count, defenseDebrisFactor, deuteriumDebrisFactor > 0 ? defenseDebrisFactor : 0)))
|
|
195
208
|
.reduce((total, debris) => ({
|
|
196
209
|
metal: total.metal + debris.metal,
|
|
197
210
|
crystal: total.crystal + debris.crystal,
|
|
@@ -199,17 +212,101 @@ function debrisOf(losses, options) {
|
|
|
199
212
|
}), { metal: 0, crystal: 0, deuterium: 0 });
|
|
200
213
|
}
|
|
201
214
|
|
|
215
|
+
/** Each destroyed defense is rebuilt, or not, on its own roll. */
|
|
216
|
+
function rebuild(losses, repairFactor, random) {
|
|
217
|
+
const rebuilt = [];
|
|
218
|
+
|
|
219
|
+
for (const { ship, count } of losses) {
|
|
220
|
+
if (ship.category !== ATTRIBUTES.CATEGORIES.DEFENSES) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let back = 0;
|
|
225
|
+
|
|
226
|
+
for (let i = 0; i < count; i += 1) {
|
|
227
|
+
if (random() < repairFactor) {
|
|
228
|
+
back += 1;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (back > 0) {
|
|
233
|
+
rebuilt.push({ ship, count: back });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return rebuilt;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* The surviving ships carry away up to `ratio` of the defender's resources,
|
|
242
|
+
* spread in proportion to what lies on the planet.
|
|
243
|
+
*/
|
|
244
|
+
function plunderOf(survivors, plunder) {
|
|
245
|
+
const {
|
|
246
|
+
resources, ratio = DEFAULT_PLUNDER_RATIO, hyperspaceLevel, hyperspaceMultiplier, bonus,
|
|
247
|
+
} = plunder;
|
|
248
|
+
|
|
249
|
+
const lootable = {
|
|
250
|
+
metal: (resources.metal ?? 0) * ratio,
|
|
251
|
+
crystal: (resources.crystal ?? 0) * ratio,
|
|
252
|
+
deuterium: (resources.deuterium ?? 0) * ratio,
|
|
253
|
+
};
|
|
254
|
+
const total = lootable.metal + lootable.crystal + lootable.deuterium;
|
|
255
|
+
const capacity = survivors
|
|
256
|
+
.map(({ ship, count }) => getCargoCapacity(ship, { hyperspaceLevel, hyperspaceMultiplier, bonus }) * count)
|
|
257
|
+
.reduce((sum, value) => sum + value, 0);
|
|
258
|
+
|
|
259
|
+
if (total === 0 || capacity >= total) {
|
|
260
|
+
return lootable;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const share = capacity / total;
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
metal: Math.floor(lootable.metal * share),
|
|
267
|
+
crystal: Math.floor(lootable.crystal * share),
|
|
268
|
+
deuterium: Math.floor(lootable.deuterium * share),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function moonChanceOf(debris) {
|
|
273
|
+
const total = debris.metal + debris.crystal + debris.deuterium;
|
|
274
|
+
|
|
275
|
+
return Math.min(MAX_MOON_CHANCE, Math.floor(total / MOON_DEBRIS_PER_PERCENT));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @typedef {object} CombatResult One simulated battle
|
|
280
|
+
* @property {'attacker'|'defender'|'draw'} winner Who won
|
|
281
|
+
* @property {number} rounds How many rounds it lasted
|
|
282
|
+
* @property {number} seed The PRNG seed it ran with
|
|
283
|
+
* @property {{survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[]}} attacker
|
|
284
|
+
* What is left of the attacker, and what it lost
|
|
285
|
+
* @property {{
|
|
286
|
+
* survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[],
|
|
287
|
+
* rebuilt: import('../types.js').FleetEntry[],
|
|
288
|
+
* }} defender What is left of the defender, what it lost, and which of its lost defenses come back
|
|
289
|
+
* @property {import('../types.js').Resources} debris The debris field
|
|
290
|
+
* @property {number} moonChance The moon chance it gives, in %
|
|
291
|
+
* @property {import('../types.js').Resources} plunder What the attacker carries away
|
|
292
|
+
*/
|
|
293
|
+
|
|
202
294
|
/**
|
|
203
295
|
*
|
|
204
296
|
* Simulate a battle between two fleets
|
|
205
297
|
*
|
|
206
298
|
* Follows the game rules: up to six rounds, every unit fires once per round at a
|
|
207
299
|
* random enemy, rapid fire grants extra shots, shots below 1% of the target
|
|
208
|
-
* shield bounce off, shields come back every round, and a unit
|
|
209
|
-
* may explode
|
|
300
|
+
* shield bounce off, shields come back every round, and a unit hit while under
|
|
301
|
+
* 70% hull may explode.
|
|
302
|
+
*
|
|
303
|
+
* Afterwards, each destroyed defense is rebuilt with a `repairFactor` chance,
|
|
304
|
+
* the debris field gives the moon chance (1% per 100 000, up to 20%), and a
|
|
305
|
+
* winning attacker loads what it can of the defender's resources when
|
|
306
|
+
* `plunder` says what lies on the planet.
|
|
210
307
|
*
|
|
211
308
|
* A battle is random, so one run is one possible outcome. The `seed` makes a run
|
|
212
|
-
* reproducible;
|
|
309
|
+
* reproducible; `getCombatStatistics` averages several of them.
|
|
213
310
|
* @param {object} attacker The attacking side
|
|
214
311
|
* @param {import('../types.js').FleetEntry[]} attacker.fleet Its ships
|
|
215
312
|
* @param {import('../types.js').CombatTechs} [attacker.techs] Its combat technology levels
|
|
@@ -218,13 +315,16 @@ function debrisOf(losses, options) {
|
|
|
218
315
|
* @param {number} [options.seed] The PRNG seed, for a reproducible battle
|
|
219
316
|
* @param {number} [options.debrisFactor] The universe debris factor
|
|
220
317
|
* @param {number} [options.deuteriumDebrisFactor] The universe deuterium debris factor
|
|
221
|
-
* @param {boolean} [options.defenseDebris] Whether destroyed defenses leave debris
|
|
222
|
-
* @
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* }
|
|
318
|
+
* @param {boolean} [options.defenseDebris] Whether destroyed defenses leave debris, at `debrisFactor`
|
|
319
|
+
* @param {number} [options.defenseDebrisFactor] The universe defense debris factor, overrides `defenseDebris`
|
|
320
|
+
* @param {number} [options.repairFactor] The chance for a destroyed defense to be rebuilt, 0.7 by default
|
|
321
|
+
* @param {object} [options.plunder] What the attacker may carry away if it wins
|
|
322
|
+
* @param {import('../types.js').Resources} options.plunder.resources The resources on the planet
|
|
323
|
+
* @param {number} [options.plunder.ratio] The share of them up for grabs, 0.5 by default
|
|
324
|
+
* @param {number} [options.plunder.hyperspaceLevel] The attacker's Hyperspace Technology level
|
|
325
|
+
* @param {number} [options.plunder.hyperspaceMultiplier] The % of base cargo per level, 5 by default
|
|
326
|
+
* @param {number} [options.plunder.bonus] Any other cargo bonus, as a fraction
|
|
327
|
+
* @returns {CombatResult} Who won, how long it took, what is left on each side, and what the battle left behind
|
|
228
328
|
*/
|
|
229
329
|
function simulateCombat(attacker, defender, options = {}) {
|
|
230
330
|
const {
|
|
@@ -232,6 +332,9 @@ function simulateCombat(attacker, defender, options = {}) {
|
|
|
232
332
|
debrisFactor = 0.3,
|
|
233
333
|
deuteriumDebrisFactor = 0,
|
|
234
334
|
defenseDebris = false,
|
|
335
|
+
defenseDebrisFactor = defenseDebris ? debrisFactor : 0,
|
|
336
|
+
repairFactor = DEFAULT_REPAIR_FACTOR,
|
|
337
|
+
plunder,
|
|
235
338
|
} = options;
|
|
236
339
|
|
|
237
340
|
const random = createRandom(seed);
|
|
@@ -258,8 +361,8 @@ function simulateCombat(attacker, defender, options = {}) {
|
|
|
258
361
|
fire(unit, attackers, random);
|
|
259
362
|
}
|
|
260
363
|
|
|
261
|
-
attackers = endRound(attackers
|
|
262
|
-
defenders = endRound(defenders
|
|
364
|
+
attackers = endRound(attackers);
|
|
365
|
+
defenders = endRound(defenders);
|
|
263
366
|
}
|
|
264
367
|
|
|
265
368
|
const attackerResult = summarise(attackers, attackerFleet);
|
|
@@ -273,19 +376,25 @@ function simulateCombat(attacker, defender, options = {}) {
|
|
|
273
376
|
winner = 'defender';
|
|
274
377
|
}
|
|
275
378
|
|
|
379
|
+
const debris = debrisOf([...attackerResult.losses, ...defenderResult.losses], {
|
|
380
|
+
debrisFactor,
|
|
381
|
+
deuteriumDebrisFactor,
|
|
382
|
+
defenseDebrisFactor,
|
|
383
|
+
});
|
|
384
|
+
|
|
276
385
|
return {
|
|
277
386
|
winner,
|
|
278
387
|
rounds,
|
|
279
388
|
seed,
|
|
280
389
|
attacker: attackerResult,
|
|
281
|
-
defender: defenderResult,
|
|
282
|
-
debris
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
390
|
+
defender: { ...defenderResult, rebuilt: rebuild(defenderResult.losses, repairFactor, random) },
|
|
391
|
+
debris,
|
|
392
|
+
moonChance: moonChanceOf(debris),
|
|
393
|
+
plunder: plunder && winner === 'attacker'
|
|
394
|
+
? plunderOf(attackerResult.survivors, plunder)
|
|
395
|
+
: { metal: 0, crystal: 0, deuterium: 0 },
|
|
287
396
|
};
|
|
288
397
|
}
|
|
289
398
|
|
|
290
|
-
export { createRandom, ROUNDS };
|
|
399
|
+
export { createRandom, ROUNDS, DEFAULT_REPAIR_FACTOR, DEFAULT_PLUNDER_RATIO };
|
|
291
400
|
export default simulateCombat;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import simulateCombat from './combat.js';
|
|
2
|
+
|
|
3
|
+
/** Enough runs for the averages to settle, without making a big battle wait. */
|
|
4
|
+
const DEFAULT_RUNS = 100;
|
|
5
|
+
|
|
6
|
+
const NO_RESOURCES = Object.freeze({ metal: 0, crystal: 0, deuterium: 0 });
|
|
7
|
+
|
|
8
|
+
function addResources(total, resources) {
|
|
9
|
+
return {
|
|
10
|
+
metal: total.metal + resources.metal,
|
|
11
|
+
crystal: total.crystal + resources.crystal,
|
|
12
|
+
deuterium: total.deuterium + resources.deuterium,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function scaleResources(resources, factor) {
|
|
17
|
+
return {
|
|
18
|
+
metal: resources.metal * factor,
|
|
19
|
+
crystal: resources.crystal * factor,
|
|
20
|
+
deuterium: resources.deuterium * factor,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function costOf(fleet) {
|
|
25
|
+
return fleet
|
|
26
|
+
.map(({ ship, count }) => ({
|
|
27
|
+
metal: (ship.cost.metal ?? 0) * count,
|
|
28
|
+
crystal: (ship.cost.crystal ?? 0) * count,
|
|
29
|
+
deuterium: (ship.cost.deuterium ?? ship.cost.deut ?? 0) * count,
|
|
30
|
+
}))
|
|
31
|
+
.reduce(addResources, NO_RESOURCES);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Two copies of the same model entry are the same ship. */
|
|
35
|
+
function keyOf(ship) {
|
|
36
|
+
return ship.ogameId ?? ship;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Sum the counts of every run, keeping the order ships first appear in. */
|
|
40
|
+
function addFleets(totals, fleet) {
|
|
41
|
+
for (const { ship, count } of fleet) {
|
|
42
|
+
const key = keyOf(ship);
|
|
43
|
+
const entry = totals.get(key);
|
|
44
|
+
|
|
45
|
+
if (entry) {
|
|
46
|
+
entry.count += count;
|
|
47
|
+
} else {
|
|
48
|
+
totals.set(key, { ship, count });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function averageFleet(totals, runs) {
|
|
54
|
+
return [...totals.values()].map(({ ship, count }) => ({ ship, count: count / runs }));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Take back what was rebuilt from what was lost, ship by ship.
|
|
59
|
+
* @param {import('../types.js').FleetEntry[]} losses What was destroyed
|
|
60
|
+
* @param {import('../types.js').FleetEntry[]} rebuilt What came back
|
|
61
|
+
* @returns {import('../types.js').FleetEntry[]} What is gone for good
|
|
62
|
+
*/
|
|
63
|
+
function subtractFleet(losses, rebuilt) {
|
|
64
|
+
return losses
|
|
65
|
+
.map(({ ship, count }) => ({
|
|
66
|
+
ship,
|
|
67
|
+
count: count - (rebuilt.find((entry) => keyOf(entry.ship) === keyOf(ship))?.count ?? 0),
|
|
68
|
+
}))
|
|
69
|
+
.filter(({ count }) => count > 0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
*
|
|
74
|
+
* Average a set of simulated battles
|
|
75
|
+
*
|
|
76
|
+
* Counts are averages, so they are not whole numbers. `lostResources` is what
|
|
77
|
+
* the units lost for good would cost to build again: every loss for the
|
|
78
|
+
* attacker, the defender's losses minus its rebuilt defenses.
|
|
79
|
+
* @param {import('./combat.js').CombatResult[]} results Battles from `simulateCombat`
|
|
80
|
+
* @returns {{
|
|
81
|
+
* runs: number,
|
|
82
|
+
* outcomes: {attacker: number, defender: number, draw: number},
|
|
83
|
+
* rounds: number,
|
|
84
|
+
* attacker: {
|
|
85
|
+
* survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[],
|
|
86
|
+
* lostResources: import('../types.js').Resources,
|
|
87
|
+
* },
|
|
88
|
+
* defender: {
|
|
89
|
+
* survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[],
|
|
90
|
+
* rebuilt: import('../types.js').FleetEntry[], lostResources: import('../types.js').Resources,
|
|
91
|
+
* },
|
|
92
|
+
* debris: import('../types.js').Resources,
|
|
93
|
+
* moonChance: number,
|
|
94
|
+
* plunder: import('../types.js').Resources,
|
|
95
|
+
* }} The share of battles each side won (from 0 to 1), and the average of everything else
|
|
96
|
+
*/
|
|
97
|
+
function getCombatStatistics(results) {
|
|
98
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
99
|
+
throw new Error('results must be a non empty array of simulateCombat results');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const runs = results.length;
|
|
103
|
+
const outcomes = { attacker: 0, defender: 0, draw: 0 };
|
|
104
|
+
const fleets = {
|
|
105
|
+
attackerSurvivors: new Map(),
|
|
106
|
+
attackerLosses: new Map(),
|
|
107
|
+
defenderSurvivors: new Map(),
|
|
108
|
+
defenderLosses: new Map(),
|
|
109
|
+
defenderRebuilt: new Map(),
|
|
110
|
+
};
|
|
111
|
+
let rounds = 0;
|
|
112
|
+
let moonChance = 0;
|
|
113
|
+
let debris = NO_RESOURCES;
|
|
114
|
+
let plunder = NO_RESOURCES;
|
|
115
|
+
let attackerLost = NO_RESOURCES;
|
|
116
|
+
let defenderLost = NO_RESOURCES;
|
|
117
|
+
|
|
118
|
+
for (const result of results) {
|
|
119
|
+
outcomes[result.winner] += 1;
|
|
120
|
+
rounds += result.rounds;
|
|
121
|
+
moonChance += result.moonChance;
|
|
122
|
+
debris = addResources(debris, result.debris);
|
|
123
|
+
plunder = addResources(plunder, result.plunder);
|
|
124
|
+
attackerLost = addResources(attackerLost, costOf(result.attacker.losses));
|
|
125
|
+
defenderLost = addResources(
|
|
126
|
+
defenderLost,
|
|
127
|
+
costOf(subtractFleet(result.defender.losses, result.defender.rebuilt)),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
addFleets(fleets.attackerSurvivors, result.attacker.survivors);
|
|
131
|
+
addFleets(fleets.attackerLosses, result.attacker.losses);
|
|
132
|
+
addFleets(fleets.defenderSurvivors, result.defender.survivors);
|
|
133
|
+
addFleets(fleets.defenderLosses, result.defender.losses);
|
|
134
|
+
addFleets(fleets.defenderRebuilt, result.defender.rebuilt);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
runs,
|
|
139
|
+
outcomes: {
|
|
140
|
+
attacker: outcomes.attacker / runs,
|
|
141
|
+
defender: outcomes.defender / runs,
|
|
142
|
+
draw: outcomes.draw / runs,
|
|
143
|
+
},
|
|
144
|
+
rounds: rounds / runs,
|
|
145
|
+
attacker: {
|
|
146
|
+
survivors: averageFleet(fleets.attackerSurvivors, runs),
|
|
147
|
+
losses: averageFleet(fleets.attackerLosses, runs),
|
|
148
|
+
lostResources: scaleResources(attackerLost, 1 / runs),
|
|
149
|
+
},
|
|
150
|
+
defender: {
|
|
151
|
+
survivors: averageFleet(fleets.defenderSurvivors, runs),
|
|
152
|
+
losses: averageFleet(fleets.defenderLosses, runs),
|
|
153
|
+
rebuilt: averageFleet(fleets.defenderRebuilt, runs),
|
|
154
|
+
lostResources: scaleResources(defenderLost, 1 / runs),
|
|
155
|
+
},
|
|
156
|
+
debris: scaleResources(debris, 1 / runs),
|
|
157
|
+
moonChance: moonChance / runs,
|
|
158
|
+
plunder: scaleResources(plunder, 1 / runs),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
*
|
|
164
|
+
* Simulate the same battle many times and average the outcomes
|
|
165
|
+
*
|
|
166
|
+
* Run `i` uses the seed `seed + i`, so the whole set is reproducible.
|
|
167
|
+
* @param {Parameters<typeof simulateCombat>[0]} attacker The attacking side, as for `simulateCombat`
|
|
168
|
+
* @param {Parameters<typeof simulateCombat>[1]} defender The defending side, as for `simulateCombat`
|
|
169
|
+
* @param {Parameters<typeof simulateCombat>[2] & {runs?: number}} [options] The `simulateCombat`
|
|
170
|
+
* options, plus `runs`, how many battles to simulate (100 by default)
|
|
171
|
+
* @returns {ReturnType<typeof getCombatStatistics>} The averaged outcome, see `getCombatStatistics`
|
|
172
|
+
*/
|
|
173
|
+
function simulateCombats(attacker, defender, options = {}) {
|
|
174
|
+
const { runs = DEFAULT_RUNS, seed = Date.now(), ...rest } = options;
|
|
175
|
+
|
|
176
|
+
if (!Number.isInteger(runs) || runs < 1) {
|
|
177
|
+
throw new Error(`runs must be an integer >= 1, received ${runs}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const results = [];
|
|
181
|
+
|
|
182
|
+
for (let i = 0; i < runs; i += 1) {
|
|
183
|
+
results.push(simulateCombat(attacker, defender, { ...rest, seed: seed + i }));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return getCombatStatistics(results);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export { getCombatStatistics, simulateCombats, DEFAULT_RUNS };
|
package/src/fleets/index.js
CHANGED
|
@@ -2,7 +2,8 @@ import getDebris from './getDebris.js';
|
|
|
2
2
|
import getDistance from './distance.js';
|
|
3
3
|
import getShipSpeed, { getActiveDrive, getFleetSpeed } from './speed.js';
|
|
4
4
|
import { getFlightTime, getFuelConsumption, getTrip } from './flight.js';
|
|
5
|
-
import simulateCombat from './combat.js';
|
|
5
|
+
import simulateCombat, { DEFAULT_REPAIR_FACTOR, DEFAULT_PLUNDER_RATIO } from './combat.js';
|
|
6
|
+
import { getCombatStatistics, simulateCombats, DEFAULT_RUNS } from './combatStatistics.js';
|
|
6
7
|
import {
|
|
7
8
|
getWaveChance,
|
|
8
9
|
getMoonbreakWaves,
|
|
@@ -31,6 +32,8 @@ const Fleets = {
|
|
|
31
32
|
getFuelConsumption,
|
|
32
33
|
getTrip,
|
|
33
34
|
simulateCombat,
|
|
35
|
+
simulateCombats,
|
|
36
|
+
getCombatStatistics,
|
|
34
37
|
getWaveChance,
|
|
35
38
|
getMoonbreakWaves,
|
|
36
39
|
getMoonbreakChance,
|
|
@@ -49,6 +52,9 @@ export {
|
|
|
49
52
|
EXPEDITION_TOP_TIER_BASE,
|
|
50
53
|
MOON_DEBRIS_THRESHOLD,
|
|
51
54
|
MAX_MOON_CHANCE,
|
|
55
|
+
DEFAULT_REPAIR_FACTOR,
|
|
56
|
+
DEFAULT_PLUNDER_RATIO,
|
|
57
|
+
DEFAULT_RUNS,
|
|
52
58
|
};
|
|
53
59
|
|
|
54
60
|
export default Fleets;
|
package/types/fleets/combat.d.ts
CHANGED
|
@@ -1,19 +1,84 @@
|
|
|
1
1
|
declare const ROUNDS = 6;
|
|
2
|
+
/** The share of destroyed defenses that come back after a battle, on most universes. */
|
|
3
|
+
declare const DEFAULT_REPAIR_FACTOR = 0.7;
|
|
4
|
+
/** The share of the defender's resources a winning attacker can carry away. */
|
|
5
|
+
declare const DEFAULT_PLUNDER_RATIO = 0.5;
|
|
2
6
|
/**
|
|
3
7
|
* A tiny deterministic PRNG (mulberry32), so a battle can be replayed.
|
|
4
8
|
*/
|
|
5
9
|
declare function createRandom(seed: any): () => number;
|
|
10
|
+
export type CombatResult = {
|
|
11
|
+
/**
|
|
12
|
+
* Who won
|
|
13
|
+
*/
|
|
14
|
+
winner: 'attacker' | 'defender' | 'draw';
|
|
15
|
+
/**
|
|
16
|
+
* How many rounds it lasted
|
|
17
|
+
*/
|
|
18
|
+
rounds: number;
|
|
19
|
+
/**
|
|
20
|
+
* The PRNG seed it ran with
|
|
21
|
+
*/
|
|
22
|
+
seed: number;
|
|
23
|
+
/**
|
|
24
|
+
* What is left of the attacker, and what it lost
|
|
25
|
+
*/
|
|
26
|
+
attacker: {
|
|
27
|
+
survivors: import('../types.js').FleetEntry[];
|
|
28
|
+
losses: import('../types.js').FleetEntry[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* What is left of the defender, what it lost, and which of its lost defenses come back
|
|
32
|
+
*/
|
|
33
|
+
defender: {
|
|
34
|
+
survivors: import('../types.js').FleetEntry[];
|
|
35
|
+
losses: import('../types.js').FleetEntry[];
|
|
36
|
+
rebuilt: import('../types.js').FleetEntry[];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The debris field
|
|
40
|
+
*/
|
|
41
|
+
debris: import('../types.js').Resources;
|
|
42
|
+
/**
|
|
43
|
+
* The moon chance it gives, in %
|
|
44
|
+
*/
|
|
45
|
+
moonChance: number;
|
|
46
|
+
/**
|
|
47
|
+
* What the attacker carries away
|
|
48
|
+
*/
|
|
49
|
+
plunder: import('../types.js').Resources;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {object} CombatResult One simulated battle
|
|
53
|
+
* @property {'attacker'|'defender'|'draw'} winner Who won
|
|
54
|
+
* @property {number} rounds How many rounds it lasted
|
|
55
|
+
* @property {number} seed The PRNG seed it ran with
|
|
56
|
+
* @property {{survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[]}} attacker
|
|
57
|
+
* What is left of the attacker, and what it lost
|
|
58
|
+
* @property {{
|
|
59
|
+
* survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[],
|
|
60
|
+
* rebuilt: import('../types.js').FleetEntry[],
|
|
61
|
+
* }} defender What is left of the defender, what it lost, and which of its lost defenses come back
|
|
62
|
+
* @property {import('../types.js').Resources} debris The debris field
|
|
63
|
+
* @property {number} moonChance The moon chance it gives, in %
|
|
64
|
+
* @property {import('../types.js').Resources} plunder What the attacker carries away
|
|
65
|
+
*/
|
|
6
66
|
/**
|
|
7
67
|
*
|
|
8
68
|
* Simulate a battle between two fleets
|
|
9
69
|
*
|
|
10
70
|
* Follows the game rules: up to six rounds, every unit fires once per round at a
|
|
11
71
|
* random enemy, rapid fire grants extra shots, shots below 1% of the target
|
|
12
|
-
* shield bounce off, shields come back every round, and a unit
|
|
13
|
-
* may explode
|
|
72
|
+
* shield bounce off, shields come back every round, and a unit hit while under
|
|
73
|
+
* 70% hull may explode.
|
|
74
|
+
*
|
|
75
|
+
* Afterwards, each destroyed defense is rebuilt with a `repairFactor` chance,
|
|
76
|
+
* the debris field gives the moon chance (1% per 100 000, up to 20%), and a
|
|
77
|
+
* winning attacker loads what it can of the defender's resources when
|
|
78
|
+
* `plunder` says what lies on the planet.
|
|
14
79
|
*
|
|
15
80
|
* A battle is random, so one run is one possible outcome. The `seed` makes a run
|
|
16
|
-
* reproducible;
|
|
81
|
+
* reproducible; `getCombatStatistics` averages several of them.
|
|
17
82
|
* @param {object} attacker The attacking side
|
|
18
83
|
* @param {import('../types.js').FleetEntry[]} attacker.fleet Its ships
|
|
19
84
|
* @param {import('../types.js').CombatTechs} [attacker.techs] Its combat technology levels
|
|
@@ -22,13 +87,16 @@ declare function createRandom(seed: any): () => number;
|
|
|
22
87
|
* @param {number} [options.seed] The PRNG seed, for a reproducible battle
|
|
23
88
|
* @param {number} [options.debrisFactor] The universe debris factor
|
|
24
89
|
* @param {number} [options.deuteriumDebrisFactor] The universe deuterium debris factor
|
|
25
|
-
* @param {boolean} [options.defenseDebris] Whether destroyed defenses leave debris
|
|
26
|
-
* @
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* }
|
|
90
|
+
* @param {boolean} [options.defenseDebris] Whether destroyed defenses leave debris, at `debrisFactor`
|
|
91
|
+
* @param {number} [options.defenseDebrisFactor] The universe defense debris factor, overrides `defenseDebris`
|
|
92
|
+
* @param {number} [options.repairFactor] The chance for a destroyed defense to be rebuilt, 0.7 by default
|
|
93
|
+
* @param {object} [options.plunder] What the attacker may carry away if it wins
|
|
94
|
+
* @param {import('../types.js').Resources} options.plunder.resources The resources on the planet
|
|
95
|
+
* @param {number} [options.plunder.ratio] The share of them up for grabs, 0.5 by default
|
|
96
|
+
* @param {number} [options.plunder.hyperspaceLevel] The attacker's Hyperspace Technology level
|
|
97
|
+
* @param {number} [options.plunder.hyperspaceMultiplier] The % of base cargo per level, 5 by default
|
|
98
|
+
* @param {number} [options.plunder.bonus] Any other cargo bonus, as a fraction
|
|
99
|
+
* @returns {CombatResult} Who won, how long it took, what is left on each side, and what the battle left behind
|
|
32
100
|
*/
|
|
33
101
|
declare function simulateCombat(attacker: {
|
|
34
102
|
fleet: import('../types.js').FleetEntry[];
|
|
@@ -38,20 +106,16 @@ declare function simulateCombat(attacker: {
|
|
|
38
106
|
debrisFactor?: number;
|
|
39
107
|
deuteriumDebrisFactor?: number;
|
|
40
108
|
defenseDebris?: boolean;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
109
|
+
defenseDebrisFactor?: number;
|
|
110
|
+
repairFactor?: number;
|
|
111
|
+
plunder?: {
|
|
112
|
+
resources: import('../types.js').Resources;
|
|
113
|
+
ratio?: number;
|
|
114
|
+
hyperspaceLevel?: number;
|
|
115
|
+
hyperspaceMultiplier?: number;
|
|
116
|
+
bonus?: number;
|
|
48
117
|
};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
losses: import('../types.js').FleetEntry[];
|
|
52
|
-
};
|
|
53
|
-
debris: import('../types.js').Resources;
|
|
54
|
-
};
|
|
55
|
-
export { createRandom, ROUNDS };
|
|
118
|
+
}): CombatResult;
|
|
119
|
+
export { createRandom, ROUNDS, DEFAULT_REPAIR_FACTOR, DEFAULT_PLUNDER_RATIO };
|
|
56
120
|
export default simulateCombat;
|
|
57
121
|
//# sourceMappingURL=combat.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"combat.d.ts","sourceRoot":"","sources":["../../src/fleets/combat.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"combat.d.ts","sourceRoot":"","sources":["../../src/fleets/combat.js"],"names":[],"mappings":"AAKA,QAAA,MAAM,MAAM,IAAI,CAAC;AAcjB,wFAAwF;AACxF,QAAA,MAAM,qBAAqB,MAAM,CAAC;AAElC,+EAA+E;AAC/E,QAAA,MAAM,qBAAqB,MAAM,CAAC;AA0BlC;;GAEG;AACH,iBAAS,YAAY,CAAC,IAAI,KAAA,gBAWzB;AAuNE,YAAkB,YAAY,GAC9B;;;;IAAyC,MAAM,EAApC,UAAU,GAAC,UAAU,GAAC,MAAM,CACvC;;;;IAAmB,MAAM,EAAd,MAAM,CACjB;;;;IAAmB,IAAI,EAAZ,MAAM,CACjB;;;;IAAwG,QAAQ,EAArG;QAAC,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAA;KAAC,CAEtG;;;;IAGG,QAAQ,EAHA;QACT,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAC1F,OAAO,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;KAC7C,CACD;;;;IAA4C,MAAM,EAAvC,OAAO,aAAa,EAAE,SAAS,CAC1C;;;;IAAmB,UAAU,EAAlB,MAAM,CACjB;;;;IAA4C,OAAO,EAAxC,OAAO,aAAa,EAAE,SAAS,CAC5C;CAAA,CAAA;AAdD;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,iBAAS,cAAc,CAAC,QAAQ,EAlB7B;IAAqD,KAAK,EAAlD,OAAO,aAAa,EAAE,UAAU,EAAE,CAC1C;IAAqD,KAAK,AAA1D,CACA,EADQ,OAAO,aAAa,EAAE,WAAW,CACzC;CAgB6B,EAAE,QAAQ,EAhB/B,MAgB+B,EAAE,OAAO,AAfhD,CAaA,EAZA;IAAyB,IAAI,AAA7B,CACA,EADQ,MAAM,CACd;IAAyB,YAAY,AAArC,CACA,EADQ,MAAM,CACd;IAAyB,qBAAqB,AAA9C,CACA,EADQ,MAAM,CACd;IAA0B,aAAa,AAAvC,CACA,EADQ,OAAO,CACf;IAAyB,mBAAmB,AAA5C,CACA,EADQ,MAAM,CACd;IAAyB,YAAY,AAArC,CACA,EADQ,MAAM,CACd;IAAyB,OAAO,AAAhC,CAMA,EALA;QAAyD,SAAS,EAA1D,OAAO,aAAa,EAAE,SAAS,CACvC;QAAiC,KAAK,AAAtC,CACA,EADQ,MAAM,CACd;QAAiC,eAAe,AAAhD,CACA,EADQ,MAAM,CACd;QAAiC,oBAAoB,AAArD,CACA,EADQ,MAAM,CACd;QAAiC,KAAK,AAAtC,CACA,EADQ,MAAM,CACd;KAAA,CAAA;CAEqD,GAF3C,YAAY,CAsExB;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,CAAC;eAC/D,cAAc"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import simulateCombat from './combat.js';
|
|
2
|
+
/** Enough runs for the averages to settle, without making a big battle wait. */
|
|
3
|
+
declare const DEFAULT_RUNS = 100;
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* Average a set of simulated battles
|
|
7
|
+
*
|
|
8
|
+
* Counts are averages, so they are not whole numbers. `lostResources` is what
|
|
9
|
+
* the units lost for good would cost to build again: every loss for the
|
|
10
|
+
* attacker, the defender's losses minus its rebuilt defenses.
|
|
11
|
+
* @param {import('./combat.js').CombatResult[]} results Battles from `simulateCombat`
|
|
12
|
+
* @returns {{
|
|
13
|
+
* runs: number,
|
|
14
|
+
* outcomes: {attacker: number, defender: number, draw: number},
|
|
15
|
+
* rounds: number,
|
|
16
|
+
* attacker: {
|
|
17
|
+
* survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[],
|
|
18
|
+
* lostResources: import('../types.js').Resources,
|
|
19
|
+
* },
|
|
20
|
+
* defender: {
|
|
21
|
+
* survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[],
|
|
22
|
+
* rebuilt: import('../types.js').FleetEntry[], lostResources: import('../types.js').Resources,
|
|
23
|
+
* },
|
|
24
|
+
* debris: import('../types.js').Resources,
|
|
25
|
+
* moonChance: number,
|
|
26
|
+
* plunder: import('../types.js').Resources,
|
|
27
|
+
* }} The share of battles each side won (from 0 to 1), and the average of everything else
|
|
28
|
+
*/
|
|
29
|
+
declare function getCombatStatistics(results: import('./combat.js').CombatResult[]): {
|
|
30
|
+
runs: number;
|
|
31
|
+
outcomes: {
|
|
32
|
+
attacker: number;
|
|
33
|
+
defender: number;
|
|
34
|
+
draw: number;
|
|
35
|
+
};
|
|
36
|
+
rounds: number;
|
|
37
|
+
attacker: {
|
|
38
|
+
survivors: import('../types.js').FleetEntry[];
|
|
39
|
+
losses: import('../types.js').FleetEntry[];
|
|
40
|
+
lostResources: import('../types.js').Resources;
|
|
41
|
+
};
|
|
42
|
+
defender: {
|
|
43
|
+
survivors: import('../types.js').FleetEntry[];
|
|
44
|
+
losses: import('../types.js').FleetEntry[];
|
|
45
|
+
rebuilt: import('../types.js').FleetEntry[];
|
|
46
|
+
lostResources: import('../types.js').Resources;
|
|
47
|
+
};
|
|
48
|
+
debris: import('../types.js').Resources;
|
|
49
|
+
moonChance: number;
|
|
50
|
+
plunder: import('../types.js').Resources;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* Simulate the same battle many times and average the outcomes
|
|
55
|
+
*
|
|
56
|
+
* Run `i` uses the seed `seed + i`, so the whole set is reproducible.
|
|
57
|
+
* @param {Parameters<typeof simulateCombat>[0]} attacker The attacking side, as for `simulateCombat`
|
|
58
|
+
* @param {Parameters<typeof simulateCombat>[1]} defender The defending side, as for `simulateCombat`
|
|
59
|
+
* @param {Parameters<typeof simulateCombat>[2] & {runs?: number}} [options] The `simulateCombat`
|
|
60
|
+
* options, plus `runs`, how many battles to simulate (100 by default)
|
|
61
|
+
* @returns {ReturnType<typeof getCombatStatistics>} The averaged outcome, see `getCombatStatistics`
|
|
62
|
+
*/
|
|
63
|
+
declare function simulateCombats(attacker: Parameters<typeof simulateCombat>[0], defender: Parameters<typeof simulateCombat>[1], options?: Parameters<typeof simulateCombat>[2] & {
|
|
64
|
+
runs?: number;
|
|
65
|
+
}): ReturnType<typeof getCombatStatistics>;
|
|
66
|
+
export { getCombatStatistics, simulateCombats, DEFAULT_RUNS };
|
|
67
|
+
//# sourceMappingURL=combatStatistics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combatStatistics.d.ts","sourceRoot":"","sources":["../../src/fleets/combatStatistics.js"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,aAAa,CAAC;AAEzC,gFAAgF;AAChF,QAAA,MAAM,YAAY,MAAM,CAAC;AAoEzB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iBAAS,mBAAmB,CAAC,OAAO,EAlBzB,OAAO,aAAa,EAAE,YAAY,EAkBT,GAjBvB;IACR,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,CAAC;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QACR,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAC1F,aAAa,EAAE,OAAO,aAAa,EAAE,SAAS,CAAC;KAChD,CAAC;IACF,QAAQ,EAAE;QACR,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAC1F,OAAO,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,aAAa,EAAE,OAAO,aAAa,EAAE,SAAS,CAAC;KAC7F,CAAC;IACF,MAAM,EAAE,OAAO,aAAa,EAAE,SAAS,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,aAAa,EAAE,SAAS,CAAC;CAC1C,CAiEH;AAED;;;;;;;;;;GAUG;AACH,iBAAS,eAAe,CAAC,QAAQ,EANtB,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAMb,EAAE,QAAQ,EALhC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAKH,EAAE,OAAO,AAJjD,CAEA,EAFQ,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAIP,GAF5C,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAgBlD;AAED,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC"}
|
package/types/fleets/index.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import getDebris from './getDebris.js';
|
|
|
2
2
|
import getDistance from './distance.js';
|
|
3
3
|
import getShipSpeed, { getActiveDrive, getFleetSpeed } from './speed.js';
|
|
4
4
|
import { getFlightTime, getFuelConsumption, getTrip } from './flight.js';
|
|
5
|
-
import simulateCombat from './combat.js';
|
|
5
|
+
import simulateCombat, { DEFAULT_REPAIR_FACTOR, DEFAULT_PLUNDER_RATIO } from './combat.js';
|
|
6
|
+
import { getCombatStatistics, simulateCombats, DEFAULT_RUNS } from './combatStatistics.js';
|
|
6
7
|
import { getWaveChance, getMoonbreakWaves, getMoonbreakChance, getMoonbreakLosses, MIN_MOON_SIZE, MAX_MOON_SIZE, WAVES_PER_ATTACKER } from './moonbreak.js';
|
|
7
8
|
import { getExpeditionFindBase, getExpeditionMaxFind, getCargoCapacity, EXPEDITION_FIND_TIERS, EXPEDITION_TOP_TIER_BASE } from './expedition.js';
|
|
8
9
|
import { getMoonLockShips, MOON_DEBRIS_THRESHOLD, MAX_MOON_CHANCE } from './moonLock.js';
|
|
@@ -16,6 +17,8 @@ declare const Fleets: {
|
|
|
16
17
|
getFuelConsumption: typeof getFuelConsumption;
|
|
17
18
|
getTrip: typeof getTrip;
|
|
18
19
|
simulateCombat: typeof simulateCombat;
|
|
20
|
+
simulateCombats: typeof simulateCombats;
|
|
21
|
+
getCombatStatistics: typeof getCombatStatistics;
|
|
19
22
|
getWaveChance: typeof getWaveChance;
|
|
20
23
|
getMoonbreakWaves: typeof getMoonbreakWaves;
|
|
21
24
|
getMoonbreakChance: typeof getMoonbreakChance;
|
|
@@ -25,6 +28,6 @@ declare const Fleets: {
|
|
|
25
28
|
getCargoCapacity: typeof getCargoCapacity;
|
|
26
29
|
getMoonLockShips: typeof getMoonLockShips;
|
|
27
30
|
};
|
|
28
|
-
export { MIN_MOON_SIZE, MAX_MOON_SIZE, WAVES_PER_ATTACKER, EXPEDITION_FIND_TIERS, EXPEDITION_TOP_TIER_BASE, MOON_DEBRIS_THRESHOLD, MAX_MOON_CHANCE, };
|
|
31
|
+
export { MIN_MOON_SIZE, MAX_MOON_SIZE, WAVES_PER_ATTACKER, EXPEDITION_FIND_TIERS, EXPEDITION_TOP_TIER_BASE, MOON_DEBRIS_THRESHOLD, MAX_MOON_CHANCE, DEFAULT_REPAIR_FACTOR, DEFAULT_PLUNDER_RATIO, DEFAULT_RUNS, };
|
|
29
32
|
export default Fleets;
|
|
30
33
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fleets/index.js"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,WAAW,MAAM,eAAe,CAAC;AACxC,OAAO,YAAY,EAAE,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,cAAc,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fleets/index.js"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,WAAW,MAAM,eAAe,CAAC;AACxC,OAAO,YAAY,EAAE,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,cAAc,EAAE,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEzF,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;CAoBX,CAAC;AAEF,OAAO,EACL,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,YAAY,GACb,CAAC;eAEa,MAAM"}
|
package/types/index.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ declare const Ogame: {
|
|
|
31
31
|
getFuelConsumption: typeof import("./fleets/flight.js").getFuelConsumption;
|
|
32
32
|
getTrip: typeof import("./fleets/flight.js").getTrip;
|
|
33
33
|
simulateCombat: typeof import("./fleets/combat.js").default;
|
|
34
|
+
simulateCombats: typeof import("./fleets/combatStatistics.js").simulateCombats;
|
|
35
|
+
getCombatStatistics: typeof import("./fleets/combatStatistics.js").getCombatStatistics;
|
|
34
36
|
getWaveChance: typeof import("./fleets/moonbreak.js").getWaveChance;
|
|
35
37
|
getMoonbreakWaves: typeof import("./fleets/moonbreak.js").getMoonbreakWaves;
|
|
36
38
|
getMoonbreakChance: typeof import("./fleets/moonbreak.js").getMoonbreakChance;
|
package/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAS,MAAM,WAAW,CAAC;AAEvD,QAAA,MAAM,KAAK
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAS,MAAM,WAAW,CAAC;AAEvD,QAAA,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgBV,CAAC;eAEa,KAAK"}
|