Fix remaining errors and enable strict mode (#1628)

## Description:

#1075 

Fixing all remaining type errors caused by strict mode and enable it.

## 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 have read and accepted the CLA agreement (only required once).

## Please put your Discord username so you can be contacted if a bug or
regression is found:

azlod

---------

Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
This commit is contained in:
Antoine
2025-08-04 01:06:31 +02:00
committed by GitHub
parent 7eb1bf732d
commit ad2598361b
18 changed files with 63 additions and 43 deletions
+1
View File
@@ -63,6 +63,7 @@ declare global {
// Extend the global interfaces to include your custom events
interface DocumentEventMap {
"join-lobby": CustomEvent<JoinLobbyEvent>;
"kick-player": CustomEvent;
}
}
+3 -2
View File
@@ -4,6 +4,7 @@ import { Executor } from "./execution/ExecutionManager";
import { WinCheckExecution } from "./execution/WinCheckExecution";
import {
AllPlayers,
Attack,
Cell,
Game,
GameUpdates,
@@ -35,7 +36,7 @@ export async function createGameRunner(
gameStart: GameStartInfo,
clientID: ClientID,
mapLoader: GameMapLoader,
callBack: (gu: GameUpdateViewData) => void,
callBack: (gu: GameUpdateViewData | ErrorUpdate) => void,
): Promise<GameRunner> {
const config = await getConfig(gameStart.config, null);
const gameMap = await loadGameMap(gameStart.config.gameMap, mapLoader);
@@ -231,7 +232,7 @@ export class GameRunner {
throw new Error(`player with id ${playerID} not found`);
}
const condition = (a) => a.id() === attackID;
const condition = (a: Attack) => a.id() === attackID;
const attack =
player.outgoingAttacks().find(condition) ??
player.incomingAttacks().find(condition);
+6 -2
View File
@@ -171,7 +171,7 @@ const TokenSchema = z
.string()
.refine(
(v) =>
PersistentIdSchema.safeParse(v).success ||
PersistentIdSchema.safeParse(v).success ??
JwtTokenSchema.safeParse(v).success,
{
message: "Token must be a valid UUID or JWT",
@@ -213,7 +213,11 @@ export const RequiredPatternSchema = z
new PatternDecoder(val, base64url.decode);
return true;
} catch (e) {
console.error(JSON.stringify(e.message, null, 2));
if (e instanceof Error) {
console.error(JSON.stringify(e.message, null, 2));
} else {
console.error(String(e));
}
return false;
}
},
+1 -1
View File
@@ -350,7 +350,7 @@ export class FakeHumanExecution implements Execution {
const dist = euclDistFN(tile, 25, false);
let tileValue = targets
.filter((unit) => dist(this.mg, unit.tile()))
.map((unit) => {
.map((unit): number => {
switch (unit.type()) {
case UnitType.City:
return 25_000;
+6 -2
View File
@@ -1,7 +1,7 @@
import { Config } from "../configuration/Config";
import { Execution, Game, Player, UnitType } from "../game/Game";
import { GameImpl } from "../game/GameImpl";
import { TileRef } from "../game/GameMap";
import { GameMap, TileRef } from "../game/GameMap";
import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util";
export class PlayerExecution implements Execution {
@@ -190,7 +190,11 @@ export class PlayerExecution implements Execution {
}
const firstTile = cluster.values().next().value;
const filter = (_, t: TileRef): boolean =>
if (!firstTile) {
return;
}
const filter = (_: GameMap, t: TileRef): boolean =>
this.mg?.ownerID(t) === this.player?.smallID();
const tiles = this.mg.bfs(firstTile, filter);
+5 -1
View File
@@ -1,6 +1,7 @@
import {
Execution,
Game,
isUnit,
MessageType,
Player,
Unit,
@@ -80,7 +81,9 @@ class SAMTargetingSystem {
[UnitType.AtomBomb, UnitType.HydrogenBomb],
({ unit }) => {
return (
unit.owner() !== this.player && !this.player.isFriendly(unit.owner())
isUnit(unit) &&
unit.owner() !== this.player &&
!this.player.isFriendly(unit.owner())
);
},
);
@@ -212,6 +215,7 @@ export class SAMLauncherExecution implements Execution {
this.MIRVWarheadSearchRadius,
UnitType.MIRVWarhead,
({ unit }) => {
if (!isUnit(unit)) return false;
if (unit.owner() === this.player) return false;
if (this.player.isFriendly(unit.owner())) return false;
const dst = unit.targetTile();
+3 -1
View File
@@ -31,7 +31,9 @@ export class BinaryLoaderGameMapLoader implements GameMapLoader {
return cachedMap;
}
const key = Object.keys(GameMapType).find((k) => GameMapType[k] === map);
const key = Object.keys(GameMapType).find(
(k) => GameMapType[k as keyof typeof GameMapType] === map,
);
const fileName = key?.toLowerCase();
const mapData = {
+3 -1
View File
@@ -17,7 +17,9 @@ export class FetchGameMapLoader implements GameMapLoader {
return cachedMap;
}
const key = Object.keys(GameMapType).find((k) => GameMapType[k] === map);
const key = Object.keys(GameMapType).find(
(k) => GameMapType[k as keyof typeof GameMapType] === map,
);
const fileName = key?.toLowerCase();
if (!fileName) {
+7 -5
View File
@@ -9,6 +9,7 @@ import {
} from "./GameUpdates";
import { RailNetwork } from "./RailNetwork";
import { Stats } from "./Stats";
import { UnitPredicate } from "./UnitGrid";
export type PlayerID = string;
export type Tick = number;
@@ -389,9 +390,10 @@ export class PlayerInfo {
}
}
export function isUnit(unit: Unit | UnitParams<UnitType>): unit is Unit {
export function isUnit(unit: unknown): unit is Unit {
return (
unit !== undefined &&
unit &&
typeof unit === "object" &&
"isUnit" in unit &&
typeof unit.isUnit === "function" &&
unit.isUnit()
@@ -662,12 +664,12 @@ export interface Game extends GameMap {
searchRange: number,
type: UnitType,
playerId: PlayerID,
);
): boolean;
nearbyUnits(
tile: TileRef,
searchRange: number,
types: UnitType | UnitType[],
predicate?: (value: { unit: Unit; distSquared: number }) => boolean,
predicate?: UnitPredicate,
): Array<{ unit: Unit; distSquared: number }>;
addExecution(...exec: Execution[]): void;
@@ -703,7 +705,7 @@ export interface Game extends GameMap {
addUpdate(update: GameUpdate): void;
railNetwork(): RailNetwork;
conquerPlayer(conqueror: Player, conquered: Player);
conquerPlayer(conqueror: Player, conquered: Player): void;
}
export interface PlayerActions {
+2 -2
View File
@@ -40,7 +40,7 @@ import { Stats } from "./Stats";
import { StatsImpl } from "./StatsImpl";
import { assignTeams } from "./TeamAssignment";
import { TerraNulliusImpl } from "./TerraNulliusImpl";
import { UnitGrid } from "./UnitGrid";
import { UnitGrid, UnitPredicate } from "./UnitGrid";
export function createGame(
humans: PlayerInfo[],
@@ -758,7 +758,7 @@ export class GameImpl implements Game {
tile: TileRef,
searchRange: number,
types: UnitType | UnitType[],
predicate?: (value: { unit: Unit; distSquared: number }) => boolean,
predicate?: UnitPredicate,
): Array<{ unit: Unit; distSquared: number }> {
return this.unitGrid.nearbyUnits(
tile,
+1 -1
View File
@@ -18,7 +18,7 @@ export interface GameUpdateViewData {
tick: number;
updates: GameUpdates;
packedTileUpdates: BigUint64Array;
playerNameViewData: Record<number, NameViewData>;
playerNameViewData: Record<string, NameViewData>;
}
export interface ErrorUpdate {
+2 -2
View File
@@ -34,7 +34,7 @@ import {
} from "./GameUpdates";
import { TerrainMapData } from "./TerrainMapLoader";
import { TerraNulliusImpl } from "./TerraNulliusImpl";
import { UnitGrid } from "./UnitGrid";
import { UnitGrid, UnitPredicate } from "./UnitGrid";
import { UserSettings } from "./UserSettings";
const userSettings: UserSettings = new UserSettings();
@@ -472,7 +472,7 @@ export class GameView implements GameMap {
tile: TileRef,
searchRange: number,
types: UnitType | UnitType[],
predicate?: (value: { unit: UnitView; distSquared: number }) => boolean,
predicate?: UnitPredicate,
): Array<{ unit: UnitView; distSquared: number }> {
return this.unitGrid.nearbyUnits(
tile,
+2 -2
View File
@@ -211,9 +211,9 @@ export class PlayerImpl implements Player {
return this._units.filter((u) => ts.has(u.type()));
}
private numUnitsConstructed: number[] = [];
private numUnitsConstructed: Partial<Record<UnitType, number>> = {};
private recordUnitConstructed(type: UnitType): void {
if (type in this.numUnitsConstructed) {
if (this.numUnitsConstructed[type] !== undefined) {
this.numUnitsConstructed[type]++;
} else {
this.numUnitsConstructed[type] = 1;
+7 -5
View File
@@ -2,6 +2,11 @@ import { PlayerID, Unit, UnitType } from "./Game";
import { GameMap, TileRef } from "./GameMap";
import { UnitView } from "./GameView";
export type UnitPredicate = (value: {
unit: Unit | UnitView;
distSquared: number;
}) => boolean;
export class UnitGrid {
private grid: Map<UnitType, Set<Unit | UnitView>>[][];
private readonly cellSize = 100;
@@ -130,11 +135,8 @@ export class UnitGrid {
nearbyUnits(
tile: TileRef,
searchRange: number,
types: UnitType | UnitType[],
predicate?: (value: {
unit: Unit | UnitView;
distSquared: number;
}) => boolean,
types: readonly UnitType[] | UnitType,
predicate?: UnitPredicate,
): Array<{ unit: Unit | UnitView; distSquared: number }> {
const nearby: Array<{ unit: Unit | UnitView; distSquared: number }> = [];
const { startGridX, endGridX, startGridY, endGridY } = this.getCellsInRange(
+1 -1
View File
@@ -124,7 +124,7 @@ export class DistanceBasedBezierCurve extends CubicBezierCurve {
/**
* Precompute all points spaced @p pixelSpacing apart
*/
computeAllPoints(pixelSpacing: number, precision): void {
computeAllPoints(pixelSpacing: number, precision: number): void {
this.cachedPoints = [];
this.totalDistance = 0;
this.currentIndex = 0;
+6 -2
View File
@@ -1,7 +1,7 @@
import version from "../../../resources/version.txt";
import { createGameRunner, GameRunner } from "../GameRunner";
import { FetchGameMapLoader } from "../game/FetchGameMapLoader";
import { GameUpdateViewData } from "../game/GameUpdates";
import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates";
import {
AttackAveragePositionResultMessage,
InitializedMessage,
@@ -17,7 +17,11 @@ const ctx: Worker = self as any;
let gameRunner: Promise<GameRunner> | null = null;
const mapLoader = new FetchGameMapLoader(`/maps`, version);
function gameUpdate(gu: GameUpdateViewData) {
function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) {
// skip if ErrorUpdate
if (!("updates" in gu)) {
return;
}
sendMessage({
type: "game_update",
gameUpdate: gu,
+5 -12
View File
@@ -29,7 +29,7 @@ async function nearbyUnits(
unitPosX: number,
rangeCheck: number,
range: number,
unitTypes: UnitType[],
unitTypes: readonly UnitType[],
) {
const game = await setup(mapName, { infiniteGold: true, instantBuild: true });
const grid = new UnitGrid(game.map());
@@ -51,7 +51,7 @@ describe("Unit Grid range tests", () => {
["plains", 0, 10, 11, false], // Exactly 1px outside
["big_plains", 0, 198, 42, true], // Inside huge range
["big_plains", 0, 198, 199, false], // Exactly 1px outside huge range
];
] as const;
describe("Is unit in range", () => {
test.each(hasUnitCases)(
@@ -77,25 +77,18 @@ describe("Unit Grid range tests", () => {
["plains", 0, 10, 11, [UnitType.DefensePost], 0], // 1px outside
["big_plains", 0, 198, 42, [UnitType.TradeShip], 1], // Inside huge range
["big_plains", 0, 198, 199, [UnitType.TransportShip], 0], // 1px outside
];
] as const;
describe("Retrieve all units in range", () => {
test.each(unitsInRangeCases)(
"on %p map, look if unit at position %p with a range of %p is in range of %p position, returns %p",
async (
mapName: string,
unitPosX: number,
range: number,
rangeCheck: number,
units: UnitType[],
expectedResult: number,
) => {
async (mapName, unitPosX, range, rangeCheck, units, expectedResult) => {
const result = await nearbyUnits(
mapName,
unitPosX,
rangeCheck,
range,
units,
units, // remove readonly
);
expect(result.length).toBe(expectedResult);
},
+2 -1
View File
@@ -21,7 +21,8 @@
"resolveJsonModule": true,
"strictNullChecks": true,
"useDefineForClassFields": false,
"strictPropertyInitialization": false
"strictPropertyInitialization": false,
"strict": true
},
"include": [
"src/**/*",