Record MIRV warhead intercepted stats, perf improvements (#1220)

## Description:

- Record MIRV warhead intercepted stats.
- Refactor `nearbyUnits()` to accept a predicate, and combine related
unnecessary `filter()` and `map()` calls.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

---------

Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
Scott Anderson
2025-06-19 15:58:27 -04:00
committed by GitHub
parent 3597f7b326
commit 2dad1dc9a6
12 changed files with 89 additions and 78 deletions
+2 -3
View File
@@ -528,11 +528,10 @@ export class ClientGameRunner {
UnitType.TradeShip,
UnitType.TransportShip,
])
.sort((a, b) => a.distSquared - b.distSquared)
.map((u) => u.unit);
.sort((a, b) => a.distSquared - b.distSquared);
if (units.length > 0) {
this.gameView.setFocusedPlayer(units[0].owner() as PlayerView);
this.gameView.setFocusedPlayer(units[0].unit.owner() as PlayerView);
} else {
this.gameView.setFocusedPlayer(null);
}
+6 -3
View File
@@ -53,9 +53,12 @@ export class UnitInfoModal extends LitElement implements Layer {
const targetRef = this.game.ref(tileX, tileY);
const allUnitTypes = Object.values(UnitType);
const matchingUnits = this.game
.nearbyUnits(targetRef, 10, allUnitTypes)
.filter(({ unit }) => unit.isActive());
const matchingUnits = this.game.nearbyUnits(
targetRef,
10,
allUnitTypes,
({ unit }) => unit.isActive(),
);
if (matchingUnits.length > 0) {
matchingUnits.sort((a, b) => a.distSquared - b.distSquared);
+4 -5
View File
@@ -543,12 +543,11 @@ export class DefaultConfig implements Config {
tileToConquer,
gm.config().defensePostRange(),
UnitType.DefensePost,
({ unit }) => unit.owner() === defender,
)) {
if (dp.unit.owner() === defender) {
mag *= this.defensePostDefenseBonus();
speed *= this.defensePostDefenseBonus();
break;
}
mag *= this.defensePostDefenseBonus();
speed *= this.defensePostDefenseBonus();
break;
}
}
+51 -47
View File
@@ -39,17 +39,15 @@ export class SAMLauncherExecution implements Execution {
private getSingleTarget(): Unit | null {
if (this.sam === null) return null;
const nukes = this.mg
.nearbyUnits(this.sam.tile(), this.searchRangeRadius, [
UnitType.AtomBomb,
UnitType.HydrogenBomb,
])
.filter(
({ unit }) =>
unit.owner() !== this.player &&
!this.player.isFriendly(unit.owner()) &&
unit.isTargetable(),
);
const nukes = this.mg.nearbyUnits(
this.sam.tile(),
this.searchRangeRadius,
[UnitType.AtomBomb, UnitType.HydrogenBomb],
({ unit }) =>
unit.owner() !== this.player &&
!this.player.isFriendly(unit.owner()) &&
unit.isTargetable(),
);
return (
nukes.sort((a, b) => {
@@ -117,18 +115,13 @@ export class SAMLauncherExecution implements Execution {
this.pseudoRandom = new PseudoRandom(this.sam.id());
}
const mirvWarheadTargets = this.mg
.nearbyUnits(
this.sam.tile(),
this.MIRVWarheadSearchRadius,
UnitType.MIRVWarhead,
)
.map(({ unit }) => unit)
.filter(
(unit) =>
unit.owner() !== this.player && !this.player.isFriendly(unit.owner()),
)
.filter((unit) => {
const mirvWarheadTargets = this.mg.nearbyUnits(
this.sam.tile(),
this.MIRVWarheadSearchRadius,
UnitType.MIRVWarhead,
({ unit }) => {
if (unit.owner() === this.player) return false;
if (this.player.isFriendly(unit.owner())) return false;
const dst = unit.targetTile();
return (
this.sam !== null &&
@@ -136,7 +129,8 @@ export class SAMLauncherExecution implements Execution {
this.mg.manhattanDist(dst, this.sam.tile()) <
this.MIRVWarheadProtectionRadius
);
});
},
);
let target: Unit | null = null;
if (mirvWarheadTargets.length === 0) {
@@ -160,31 +154,41 @@ export class SAMLauncherExecution implements Execution {
MessageType.SAM_MISS,
this.sam.owner().id(),
);
} else {
if (mirvWarheadTargets.length > 0) {
// Message
this.mg.displayMessage(
`${mirvWarheadTargets.length} MIRV warheads intercepted`,
MessageType.SAM_HIT,
this.sam.owner().id(),
);
} else if (mirvWarheadTargets.length > 0) {
const samOwner = this.sam.owner();
// Message
this.mg.displayMessage(
`${mirvWarheadTargets.length} MIRV warheads intercepted`,
MessageType.SAM_HIT,
samOwner.id(),
);
mirvWarheadTargets.forEach(({ unit: u }) => {
// Delete warheads
mirvWarheadTargets.forEach((u) => {
u.delete();
});
} else if (target !== null) {
target.setTargetedBySAM(true);
this.mg.addExecution(
new SAMMissileExecution(
this.sam.tile(),
this.sam.owner(),
this.sam,
target,
),
u.delete();
});
// Record stats
this.mg
.stats()
.bombIntercept(
samOwner,
UnitType.MIRVWarhead,
mirvWarheadTargets.length,
);
} else {
throw new Error("target is null");
}
} else if (target !== null) {
target.setTargetedBySAM(true);
this.mg.addExecution(
new SAMMissileExecution(
this.sam.tile(),
this.sam.owner(),
this.sam,
target,
),
);
} else {
throw new Error("target is null");
}
}
+1 -5
View File
@@ -72,11 +72,7 @@ export class SAMMissileExecution implements Execution {
// Record stats
this.mg
.stats()
.bombIntercept(
this._owner,
this.target.owner(),
this.target.type() as NukeType,
);
.bombIntercept(this._owner, this.target.type() as NukeType, 1);
return;
} else {
this.SAMMissile.move(result);
+1
View File
@@ -602,6 +602,7 @@ export interface Game extends GameMap {
tile: TileRef,
searchRange: number,
types: UnitType | UnitType[],
predicate?: (value: { unit: Unit; distSquared: number }) => boolean,
): Array<{ unit: Unit; distSquared: number }>;
addExecution(...exec: Execution[]): void;
+7 -1
View File
@@ -391,8 +391,14 @@ export class GameView implements GameMap {
tile: TileRef,
searchRange: number,
types: UnitType | UnitType[],
predicate?: (value: { unit: UnitView; distSquared: number }) => boolean,
): Array<{ unit: UnitView; distSquared: number }> {
return this.unitGrid.nearbyUnits(tile, searchRange, types) as Array<{
return this.unitGrid.nearbyUnits(
tile,
searchRange,
types,
predicate,
) as Array<{
unit: UnitView;
distSquared: number;
}>;
+2 -4
View File
@@ -897,9 +897,7 @@ export class PlayerImpl implements Player {
return this.mg.config().unitInfo(unitTypeValue).territoryBound;
});
const nearbyUnits = this.mg
.nearbyUnits(tile, searchRadius * 2, types)
.map((u) => u.unit);
const nearbyUnits = this.mg.nearbyUnits(tile, searchRadius * 2, types);
const nearbyTiles = this.mg.bfs(tile, (gm, t) => {
return (
this.mg.euclideanDistSquared(tile, t) < searchRadiusSquared &&
@@ -910,7 +908,7 @@ export class PlayerImpl implements Player {
const minDistSquared = this.mg.config().structureMinDist() ** 2;
for (const t of nearbyTiles) {
for (const unit of nearbyUnits) {
for (const { unit } of nearbyUnits) {
if (this.mg.euclideanDistSquared(unit.tile(), t) < minDistSquared) {
validSet.delete(t);
break;
+1 -1
View File
@@ -71,7 +71,7 @@ export interface Stats {
bombLand(player: Player, target: Player | TerraNullius, type: NukeType): void;
// Player's SAM intercepts a bomb from attacker
bombIntercept(player: Player, attacker: Player, type: NukeType): void;
bombIntercept(player: Player, type: NukeType, count: number | bigint): void;
// Player earns gold from conquering tiles or trade ships from captured
goldWar(player: Player, captured: Player, gold: number | bigint): void;
+2 -2
View File
@@ -215,8 +215,8 @@ export class StatsImpl implements Stats {
this._addBomb(player, type, BOMB_INDEX_LAND, 1);
}
bombIntercept(player: Player, attacker: Player, type: NukeType): void {
this._addBomb(player, type, BOMB_INDEX_INTERCEPT, 1);
bombIntercept(player: Player, type: NukeType, count: BigIntLike): void {
this._addBomb(player, type, BOMB_INDEX_INTERCEPT, count);
}
goldWork(player: Player, gold: BigIntLike): void {
+11 -6
View File
@@ -96,6 +96,10 @@ export class UnitGrid {
tile: TileRef,
searchRange: number,
types: UnitType | UnitType[],
predicate?: (value: {
unit: Unit | UnitView;
distSquared: number;
}) => boolean,
): Array<{ unit: Unit | UnitView; distSquared: number }> {
const nearby: Array<{ unit: Unit | UnitView; distSquared: number }> = [];
const { startGridX, endGridX, startGridY, endGridY } = this.getCellsInRange(
@@ -107,12 +111,13 @@ export class UnitGrid {
for (let cy = startGridY; cy <= endGridY; cy++) {
for (let cx = startGridX; cx <= endGridX; cx++) {
for (const unit of this.grid[cy][cx]) {
if (typeSet.has(unit.type()) && unit.isActive()) {
const distSquared = this.squaredDistanceFromTile(unit, tile);
if (distSquared <= rangeSquared) {
nearby.push({ unit, distSquared });
}
}
if (!typeSet.has(unit.type())) continue;
if (!unit.isActive()) continue;
const distSquared = this.squaredDistanceFromTile(unit, tile);
if (distSquared > rangeSquared) continue;
const value = { unit, distSquared };
if (predicate !== undefined && !predicate(value)) continue;
nearby.push(value);
}
}
}
+1 -1
View File
@@ -163,7 +163,7 @@ describe("Stats", () => {
});
test("bombIntercept", () => {
stats.bombIntercept(player1, player2, UnitType.MIRVWarhead);
stats.bombIntercept(player1, UnitType.MIRVWarhead, 1);
expect(stats.stats()).toStrictEqual({
client1: { bombs: { mirvw: [0n, 0n, 1n] } },
});