This commit is contained in:
Scott Anderson
2025-05-14 01:43:20 -04:00
parent 2b4d28bb34
commit 9a7d6283f8
17 changed files with 104 additions and 73 deletions
+3 -3
View File
@@ -226,11 +226,11 @@ export const SpawnIntentSchema = BaseIntentSchema.extend({
export const BoatAttackIntentSchema = BaseIntentSchema.extend({ export const BoatAttackIntentSchema = BaseIntentSchema.extend({
type: z.literal("boat"), type: z.literal("boat"),
targetID: ID.nullable(), targetID: ID.nullable(),
troops: z.number().nullable(), troops: z.number(),
dstX: z.number(), dstX: z.number(),
dstY: z.number(), dstY: z.number(),
srcX: z.number().nullable().optional(), srcX: z.number().nullable(),
srcY: z.number().nullable().optional(), srcY: z.number().nullable(),
}); });
export const AllianceRequestIntentSchema = BaseIntentSchema.extend({ export const AllianceRequestIntentSchema = BaseIntentSchema.extend({
+1 -1
View File
@@ -283,7 +283,7 @@ export class AttackExecution implements Execution {
this.border.add(neighbor); this.border.add(neighbor);
const numOwnedByMe = this.mg const numOwnedByMe = this.mg
.neighbors(neighbor) .neighbors(neighbor)
.filter((t) => this.mg.owner(t) == this._owner).length; .filter((t) => this.mg.owner(t) === this._owner).length;
let mag = 0; let mag = 0;
switch (this.mg.terrainType(tile)) { switch (this.mg.terrainType(tile)) {
case TerrainType.Plains: case TerrainType.Plains:
+1 -1
View File
@@ -33,7 +33,7 @@ export class BotExecution implements Execution {
} }
tick(ticks: number) { tick(ticks: number) {
if (ticks % this.attackRate != this.attackTick) return; if (ticks % this.attackRate !== this.attackTick) return;
if (!this.bot.isAlive()) { if (!this.bot.isAlive()) {
this.active = false; this.active = false;
+5 -3
View File
@@ -16,7 +16,7 @@ export class DefensePostExecution implements Execution {
private post: Unit | null = null; private post: Unit | null = null;
private active: boolean = true; private active: boolean = true;
private target: Unit = null; private target: Unit | null = null;
private lastShellAttack = 0; private lastShellAttack = 0;
private alreadySentShell = new Set<Unit>(); private alreadySentShell = new Set<Unit>();
@@ -37,6 +37,8 @@ export class DefensePostExecution implements Execution {
} }
private shoot() { private shoot() {
if (this.post === null) return;
if (this.target === null) return;
const shellAttackRate = this.mg.config().defensePostShellAttackRate(); const shellAttackRate = this.mg.config().defensePostShellAttackRate();
if (this.mg.ticks() - this.lastShellAttack > shellAttackRate) { if (this.mg.ticks() - this.lastShellAttack > shellAttackRate) {
this.lastShellAttack = this.mg.ticks(); this.lastShellAttack = this.mg.ticks();
@@ -76,7 +78,7 @@ export class DefensePostExecution implements Execution {
this.player = this.post.owner(); this.player = this.post.owner();
} }
if (this.target != null && !this.target.isActive()) { if (this.target !== null && !this.target.isActive()) {
this.target = null; this.target = null;
} }
@@ -117,7 +119,7 @@ export class DefensePostExecution implements Execution {
return distA - distB; return distA - distB;
})[0]?.unit ?? null; })[0]?.unit ?? null;
if (this.target == null || !this.target.isActive()) { if (this.target === null || !this.target.isActive()) {
this.target = null; this.target = null;
return; return;
} else { } else {
+8 -5
View File
@@ -42,13 +42,16 @@ export class EmojiExecution implements Execution {
tick(ticks: number): void { tick(ticks: number): void {
const emojiString = flattenedEmojiTable.at(this.emoji); const emojiString = flattenedEmojiTable.at(this.emoji);
if (emojiString === undefined) {
if (this.requestor.canSendEmoji(this.recipient)) { consolex.warn(
`cannot send emoji ${this.emoji} from ${this.requestor} to ${this.recipient}`,
);
} else if (this.requestor.canSendEmoji(this.recipient)) {
this.requestor.sendEmoji(this.recipient, emojiString); this.requestor.sendEmoji(this.recipient, emojiString);
if ( if (
emojiString == "🖕" && emojiString === "🖕" &&
this.recipient != AllPlayers && this.recipient !== AllPlayers &&
this.recipient.type() == PlayerType.FakeHuman this.recipient.type() === PlayerType.FakeHuman
) { ) {
this.recipient.updateRelation(this.requestor, -100); this.recipient.updateRelation(this.requestor, -100);
} }
+3 -2
View File
@@ -1,4 +1,5 @@
import { Execution, Game } from "../game/Game"; import { Execution, Game } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { PseudoRandom } from "../PseudoRandom"; import { PseudoRandom } from "../PseudoRandom";
import { ClientID, GameID, Intent, Turn } from "../Schemas"; import { ClientID, GameID, Intent, Turn } from "../Schemas";
import { simpleHash } from "../Util"; import { simpleHash } from "../Util";
@@ -66,8 +67,8 @@ export class Executor {
this.mg.ref(intent.x, intent.y), this.mg.ref(intent.x, intent.y),
); );
case "boat": case "boat":
let src = null; let src: TileRef | null = null;
if (intent.srcX != null || intent.srcY != null) { if (intent.srcX !== null && intent.srcY !== null) {
src = this.mg.ref(intent.srcX, intent.srcY); src = this.mg.ref(intent.srcX, intent.srcY);
} }
return new TransportShipExecution( return new TransportShipExecution(
+28 -16
View File
@@ -111,11 +111,11 @@ export class FakeHumanExecution implements Execution {
} }
tick(ticks: number) { tick(ticks: number) {
if (ticks % this.attackRate != this.attackTick) return; if (ticks % this.attackRate !== this.attackTick) return;
if (this.mg.inSpawnPhase()) { if (this.mg.inSpawnPhase()) {
const rl = this.randomLand(); const rl = this.randomLand();
if (rl == null) { if (rl === null) {
consolex.warn(`cannot spawn ${this.nation.playerInfo.name}`); consolex.warn(`cannot spawn ${this.nation.playerInfo.name}`);
return; return;
} }
@@ -123,11 +123,11 @@ export class FakeHumanExecution implements Execution {
return; return;
} }
if (this.player == null) { if (this.player === null) {
this.player = this.mg this.player =
.players() this.mg.players().find((p) => p.id() === this.nation.playerInfo.id) ??
.find((p) => p.id() == this.nation.playerInfo.id); null;
if (this.player == null) { if (this.player === null) {
return; return;
} }
} }
@@ -170,6 +170,9 @@ export class FakeHumanExecution implements Execution {
} }
private maybeAttack() { private maybeAttack() {
if (this.player === null || this.behavior === null) {
throw new Error("not initialized");
}
const enemyborder = Array.from(this.player.borderTiles()) const enemyborder = Array.from(this.player.borderTiles())
.flatMap((t) => this.mg.neighbors(t)) .flatMap((t) => this.mg.neighbors(t))
.filter( .filter(
@@ -255,6 +258,9 @@ export class FakeHumanExecution implements Execution {
} }
handleEnemies() { handleEnemies() {
if (this.player === null || this.behavior === null) {
throw new Error("not initialized");
}
this.behavior.forgetOldEnemies(); this.behavior.forgetOldEnemies();
this.behavior.checkIncomingAttacks(); this.behavior.checkIncomingAttacks();
this.behavior.assistAllies(); this.behavior.assistAllies();
@@ -270,7 +276,8 @@ export class FakeHumanExecution implements Execution {
} }
private maybeSendEmoji(enemy: Player) { private maybeSendEmoji(enemy: Player) {
if (enemy.type() != PlayerType.Human) return; if (this.player === null) throw new Error("not initialized");
if (enemy.type() !== PlayerType.Human) return;
const lastSent = this.lastEmojiSent.get(enemy) ?? -300; const lastSent = this.lastEmojiSent.get(enemy) ?? -300;
if (this.mg.ticks() - lastSent <= 300) return; if (this.mg.ticks() - lastSent <= 300) return;
this.lastEmojiSent.set(enemy, this.mg.ticks()); this.lastEmojiSent.set(enemy, this.mg.ticks());
@@ -284,11 +291,12 @@ export class FakeHumanExecution implements Execution {
} }
private maybeSendNuke(other: Player) { private maybeSendNuke(other: Player) {
if (this.player === null) throw new Error("not initialized");
const silos = this.player.units(UnitType.MissileSilo); const silos = this.player.units(UnitType.MissileSilo);
if ( if (
silos.length == 0 || silos.length === 0 ||
this.player.gold() < this.cost(UnitType.AtomBomb) || this.player.gold() < this.cost(UnitType.AtomBomb) ||
other.type() == PlayerType.Bot || other.type() === PlayerType.Bot ||
this.player.isOnSameTeam(other) this.player.isOnSameTeam(other)
) { ) {
return; return;
@@ -302,17 +310,17 @@ export class FakeHumanExecution implements Execution {
UnitType.SAMLauncher, UnitType.SAMLauncher,
); );
const structureTiles = structures.map((u) => u.tile()); const structureTiles = structures.map((u) => u.tile());
const randomTiles: TileRef[] = new Array(10); const randomTiles: (TileRef | null)[] = new Array(10);
for (let i = 0; i < randomTiles.length; i++) { for (let i = 0; i < randomTiles.length; i++) {
randomTiles[i] = this.randTerritoryTile(other); randomTiles[i] = this.randTerritoryTile(other);
} }
const allTiles = randomTiles.concat(structureTiles); const allTiles = randomTiles.concat(structureTiles);
let bestTile = null; let bestTile: TileRef | null = null;
let bestValue = 0; let bestValue = 0;
this.removeOldNukeEvents(); this.removeOldNukeEvents();
outer: for (const tile of new Set(allTiles)) { outer: for (const tile of new Set(allTiles)) {
if (tile == null) continue; if (tile === null) continue;
for (const t of this.mg.bfs(tile, manhattanDistFN(tile, 15))) { for (const t of this.mg.bfs(tile, manhattanDistFN(tile, 15))) {
// Make sure we nuke at least 15 tiles in border // Make sure we nuke at least 15 tiles in border
if (this.mg.owner(t) !== other) { if (this.mg.owner(t) !== other) {
@@ -321,12 +329,12 @@ export class FakeHumanExecution implements Execution {
} }
if (!this.player.canBuild(UnitType.AtomBomb, tile)) continue; if (!this.player.canBuild(UnitType.AtomBomb, tile)) continue;
const value = this.nukeTileScore(tile, silos, structures); const value = this.nukeTileScore(tile, silos, structures);
if (value > bestTile) { if (value > bestValue) {
bestTile = tile; bestTile = tile;
bestValue = value; bestValue = value;
} }
} }
if (bestTile != null) { if (bestTile !== null) {
this.sendNuke(bestTile); this.sendNuke(bestTile);
} }
} }
@@ -343,6 +351,7 @@ export class FakeHumanExecution implements Execution {
} }
private sendNuke(tile: TileRef) { private sendNuke(tile: TileRef) {
if (this.player === null) throw new Error("not initialized");
const tick = this.mg.ticks(); const tick = this.mg.ticks();
this.lastNukeSent.push([tick, tile]); this.lastNukeSent.push([tick, tile]);
this.mg.addExecution( this.mg.addExecution(
@@ -375,7 +384,9 @@ export class FakeHumanExecution implements Execution {
// Prefer tiles that are closer to a silo // Prefer tiles that are closer to a silo
const siloTiles = silos.map((u) => u.tile()); const siloTiles = silos.map((u) => u.tile());
const { x: closestSilo } = closestTwoTiles(this.mg, siloTiles, [tile]); const result = closestTwoTiles(this.mg, siloTiles, [tile]);
if (result === null) throw new Error("Missing result");
const { x: closestSilo } = result;
const distanceSquared = this.mg.euclideanDistSquared(tile, closestSilo); const distanceSquared = this.mg.euclideanDistSquared(tile, closestSilo);
const distanceToClosestSilo = Math.sqrt(distanceSquared); const distanceToClosestSilo = Math.sqrt(distanceSquared);
tileValue -= distanceToClosestSilo * 30; tileValue -= distanceToClosestSilo * 30;
@@ -437,6 +448,7 @@ export class FakeHumanExecution implements Execution {
} }
private maybeSpawnStructure(type: UnitType, maxNum: number) { private maybeSpawnStructure(type: UnitType, maxNum: number) {
if (this.player === null) throw new Error("not initialized");
const units = this.player.units(type); const units = this.player.units(type);
if (units.length >= maxNum) { if (units.length >= maxNum) {
return; return;
+4 -1
View File
@@ -32,7 +32,10 @@ export class MissileSiloExecution implements Execution {
} }
tick(ticks: number): void { tick(ticks: number): void {
if (this.silo == null) { if (this.player === null || this.mg === null) {
throw new Error("Not initialized");
}
if (this.silo === null) {
const spawn = this.player.canBuild(UnitType.MissileSilo, this.tile); const spawn = this.player.canBuild(UnitType.MissileSilo, this.tile);
if (spawn === false) { if (spawn === false) {
consolex.warn( consolex.warn(
+1 -1
View File
@@ -111,7 +111,7 @@ export class NukeExecution implements Execution {
this.pathFinder.computeControlPoints( this.pathFinder.computeControlPoints(
spawn, spawn,
this.dst, this.dst,
this.type != UnitType.MIRVWarhead, this.type !== UnitType.MIRVWarhead,
); );
this.nuke = this.player.buildUnit(this.type, spawn, { this.nuke = this.player.buildUnit(this.type, spawn, {
detonationDst: this.dst, detonationDst: this.dst,
+19 -11
View File
@@ -30,7 +30,7 @@ export class SAMLauncherExecution implements Execution {
private tile: TileRef, private tile: TileRef,
private sam: Unit | null = null, private sam: Unit | null = null,
) { ) {
if (sam != null) { if (sam !== null) {
this.tile = sam.tile(); this.tile = sam.tile();
} }
} }
@@ -46,6 +46,7 @@ export class SAMLauncherExecution implements Execution {
} }
private getSingleTarget(): Unit | null { private getSingleTarget(): Unit | null {
if (this.sam === null) return null;
const nukes = this.mg const nukes = this.mg
.nearbyUnits(this.sam.tile(), this.searchRangeRadius, [ .nearbyUnits(this.sam.tile(), this.searchRangeRadius, [
UnitType.AtomBomb, UnitType.AtomBomb,
@@ -80,11 +81,11 @@ export class SAMLauncherExecution implements Execution {
} }
private isHit(type: UnitType, random: number): boolean { private isHit(type: UnitType, random: number): boolean {
if (type == UnitType.AtomBomb) { if (type === UnitType.AtomBomb) {
return true; return true;
} }
if (type == UnitType.MIRVWarhead) { if (type === UnitType.MIRVWarhead) {
return random < this.mg.config().samWarheadHittingChance(); return random < this.mg.config().samWarheadHittingChance();
} }
@@ -130,14 +131,18 @@ export class SAMLauncherExecution implements Execution {
(unit) => (unit) =>
unit.owner() !== this.player && !this.player.isFriendly(unit.owner()), unit.owner() !== this.player && !this.player.isFriendly(unit.owner()),
) )
.filter( .filter((unit) => {
(unit) => const dst = unit.detonationDst();
this.mg.manhattanDist(unit.detonationDst(), this.sam.tile()) < return (
this.MIRVWarheadProtectionRadius, this.sam !== null &&
); dst !== null &&
this.mg.manhattanDist(dst, this.sam.tile()) <
this.MIRVWarheadProtectionRadius
);
});
let target: Unit | null = null; let target: Unit | null = null;
if (mirvWarheadTargets.length == 0) { if (mirvWarheadTargets.length === 0) {
target = this.getSingleTarget(); target = this.getSingleTarget();
} }
@@ -155,7 +160,8 @@ export class SAMLauncherExecution implements Execution {
) { ) {
this.sam.setCooldown(true); this.sam.setCooldown(true);
const type = const type =
mirvWarheadTargets.length > 0 ? UnitType.MIRVWarhead : target.type(); mirvWarheadTargets.length > 0 ? UnitType.MIRVWarhead : target?.type();
if (type === undefined) throw new Error("Unknown unit type");
const random = this.pseudoRandom.next(); const random = this.pseudoRandom.next();
const hit = this.isHit(type, random); const hit = this.isHit(type, random);
if (!hit) { if (!hit) {
@@ -174,7 +180,7 @@ export class SAMLauncherExecution implements Execution {
); );
// Delete warheads // Delete warheads
mirvWarheadTargets.forEach((u) => u.delete()); mirvWarheadTargets.forEach((u) => u.delete());
} else { } else if (target !== null) {
target.setTargetedBySAM(true); target.setTargetedBySAM(true);
this.mg.addExecution( this.mg.addExecution(
new SAMMissileExecution( new SAMMissileExecution(
@@ -184,6 +190,8 @@ export class SAMLauncherExecution implements Execution {
target, target,
), ),
); );
} else {
throw new Error("target is null");
} }
} }
} }
+5 -5
View File
@@ -6,7 +6,7 @@ import { PseudoRandom } from "../PseudoRandom";
export class ShellExecution implements Execution { export class ShellExecution implements Execution {
private active = true; private active = true;
private pathFinder: AirPathFinder; private pathFinder: AirPathFinder;
private shell: Unit; private shell: Unit | undefined;
private mg: Game; private mg: Game;
private destroyAtTick: number = -1; private destroyAtTick: number = -1;
@@ -23,7 +23,7 @@ export class ShellExecution implements Execution {
} }
tick(ticks: number): void { tick(ticks: number): void {
if (this.shell == null) { if (this.shell === undefined) {
this.shell = this._owner.buildUnit(UnitType.Shell, this.spawn, {}); this.shell = this._owner.buildUnit(UnitType.Shell, this.spawn, {});
} }
if (!this.shell.isActive()) { if (!this.shell.isActive()) {
@@ -40,7 +40,7 @@ export class ShellExecution implements Execution {
return; return;
} }
if (this.destroyAtTick == -1 && !this.ownerUnit.isActive()) { if (this.destroyAtTick === -1 && !this.ownerUnit.isActive()) {
this.destroyAtTick = this.mg.ticks() + this.mg.config().shellLifetime(); this.destroyAtTick = this.mg.ticks() + this.mg.config().shellLifetime();
} }
@@ -61,8 +61,8 @@ export class ShellExecution implements Execution {
} }
private effectOnTarget(): number { private effectOnTarget(): number {
const baseDamage: number = this.mg.config().unitInfo(UnitType.Shell).damage; const { damage } = this.mg.config().unitInfo(UnitType.Shell);
return baseDamage; return damage ?? 0;
} }
isActive(): boolean { isActive(): boolean {
+1 -1
View File
@@ -59,7 +59,7 @@ export class TradeShipExecution implements Execution {
return; return;
} }
if (this.origOwner != this.tradeShip.owner()) { if (this.origOwner !== this.tradeShip.owner()) {
// Store as variable in case ship is recaptured by previous owner // Store as variable in case ship is recaptured by previous owner
this.wasCaptured = true; this.wasCaptured = true;
} }
+4 -4
View File
@@ -39,7 +39,7 @@ export class TransportShipExecution implements Execution {
private attackerID: PlayerID, private attackerID: PlayerID,
private targetID: PlayerID | null, private targetID: PlayerID | null,
private ref: TileRef, private ref: TileRef,
private troops: number | null, private troops: number,
private src: TileRef | null, private src: TileRef | null,
) {} ) {}
@@ -120,19 +120,19 @@ export class TransportShipExecution implements Execution {
UnitType.TransportShip, UnitType.TransportShip,
this.dst, this.dst,
); );
if (closestTileSrc == false) { if (closestTileSrc === false) {
consolex.warn(`can't build transport ship`); consolex.warn(`can't build transport ship`);
this.active = false; this.active = false;
return; return;
} }
if (this.src == null) { if (this.src === null) {
// Only update the src if it's not already set // Only update the src if it's not already set
// because we assume that the src is set to the best spawn tile // because we assume that the src is set to the best spawn tile
this.src = closestTileSrc; this.src = closestTileSrc;
} else { } else {
if ( if (
this.mg.owner(this.src) != this.attacker || this.mg.owner(this.src) !== this.attacker ||
!this.mg.isShore(this.src) !this.mg.isShore(this.src)
) { ) {
console.warn( console.warn(
+1 -1
View File
@@ -79,7 +79,7 @@ export class WinCheckExecution implements Execution {
this.mg.numLandTiles() - this.mg.numTilesWithFallout(); this.mg.numLandTiles() - this.mg.numTilesWithFallout();
const percentage = (max[1] / numTilesWithoutFallout) * 100; const percentage = (max[1] / numTilesWithoutFallout) * 100;
if (percentage > this.mg.config().percentageTilesOwnedToWin()) { if (percentage > this.mg.config().percentageTilesOwnedToWin()) {
if (max[0] == ColoredTeams.Bot) return; if (max[0] === ColoredTeams.Bot) return;
this.mg.setWinner(max[0], this.mg.stats().stats()); this.mg.setWinner(max[0], this.mg.stats().stats());
console.log(`${max[0]} has won the game`); console.log(`${max[0]} has won the game`);
this.active = false; this.active = false;
+3 -3
View File
@@ -111,8 +111,8 @@ export class BotBehavior {
// Select the most hated player // Select the most hated player
if (this.enemy === null) { if (this.enemy === null) {
const mostHated = this.player.allRelationsSorted()[0] ?? null; const mostHated = this.player.allRelationsSorted()[0];
if (mostHated != null && mostHated.relation === Relation.Hostile) { if (mostHated !== undefined && mostHated.relation === Relation.Hostile) {
this.enemy = mostHated.player; this.enemy = mostHated.player;
this.enemyUpdated = this.game.ticks(); this.enemyUpdated = this.game.ticks();
} }
@@ -137,7 +137,7 @@ export class BotBehavior {
for (const neighbor of this.random.shuffleArray(neighbors)) { for (const neighbor of this.random.shuffleArray(neighbors)) {
if (!neighbor.isPlayer()) continue; if (!neighbor.isPlayer()) continue;
if (this.player.isFriendly(neighbor)) continue; if (this.player.isFriendly(neighbor)) continue;
if (neighbor.type() == PlayerType.FakeHuman) { if (neighbor.type() === PlayerType.FakeHuman) {
if (this.random.chance(2)) { if (this.random.chance(2)) {
continue; continue;
} }
+1
View File
@@ -762,6 +762,7 @@ export class PlayerImpl implements Player {
case UnitType.MIRVWarhead: case UnitType.MIRVWarhead:
return targetTile; return targetTile;
case UnitType.Port: case UnitType.Port:
if (validTiles === null) throw new Error("validTiles is required");
return this.portSpawn(targetTile, validTiles); return this.portSpawn(targetTile, validTiles);
case UnitType.Warship: case UnitType.Warship:
return this.warshipSpawn(targetTile); return this.warshipSpawn(targetTile);
+16 -15
View File
@@ -16,13 +16,12 @@ import { PlayerImpl } from "./PlayerImpl";
export class UnitImpl implements Unit { export class UnitImpl implements Unit {
private _active = true; private _active = true;
private _health: bigint; private _health: bigint;
private _lastTile: TileRef = null; private _lastTile: TileRef;
private _target: Unit = null; private _moveTarget: TileRef | null = null;
private _moveTarget: TileRef = null;
private _targetedBySAM = false; private _targetedBySAM = false;
private _safeFromPiratesCooldown: number; // Only for trade ships private _safeFromPiratesCooldown: number; // Only for trade ships
private _lastSetSafeFromPirates: number; // Only for trade ships private _lastSetSafeFromPirates: number; // Only for trade ships
private _constructionType: UnitType = undefined; private _constructionType: UnitType | undefined;
private _troops: number; private _troops: number;
private _cooldownTick: Tick | null = null; private _cooldownTick: Tick | null = null;
@@ -45,12 +44,14 @@ export class UnitImpl implements Unit {
.config() .config()
.safeFromPiratesCooldownMax(); .safeFromPiratesCooldownMax();
this._troops = "troops" in params ? params.troops : 0; this._troops = "troops" in params ? (params.troops ?? 0) : 0;
this._dstPort = "dstPort" in params ? params.dstPort : null; this._dstPort = "dstPort" in params ? params.dstPort : undefined;
this._cooldownDuration = this._cooldownDuration =
"cooldownDuration" in params ? params.cooldownDuration : null; "cooldownDuration" in params ? params.cooldownDuration : undefined;
this._lastSetSafeFromPirates = this._lastSetSafeFromPirates =
"lastSetSafeFromPirates" in params ? params.lastSetSafeFromPirates : 0; "lastSetSafeFromPirates" in params
? (params.lastSetSafeFromPirates ?? 0)
: 0;
} }
id() { id() {
@@ -93,7 +94,7 @@ export class UnitImpl implements Unit {
} }
move(tile: TileRef): void { move(tile: TileRef): void {
if (tile == null) { if (tile === null) {
throw new Error("tile cannot be null"); throw new Error("tile cannot be null");
} }
this.mg.removeUnit(this); this.mg.removeUnit(this);
@@ -112,7 +113,7 @@ export class UnitImpl implements Unit {
return Number(this._health); return Number(this._health);
} }
hasHealth(): boolean { hasHealth(): boolean {
return this.info().maxHealth != undefined; return this.info().maxHealth !== undefined;
} }
tile(): TileRef { tile(): TileRef {
return this._tile; return this._tile;
@@ -127,7 +128,7 @@ export class UnitImpl implements Unit {
setOwner(newOwner: Player): void { setOwner(newOwner: Player): void {
const oldOwner = this._owner; const oldOwner = this._owner;
oldOwner._units = oldOwner._units.filter((u) => u != this); oldOwner._units = oldOwner._units.filter((u) => u !== this);
this._owner = newOwner as PlayerImpl; this._owner = newOwner as PlayerImpl;
this.mg.addUpdate(this.toUpdate()); this.mg.addUpdate(this.toUpdate());
this.mg.displayMessage( this.mg.displayMessage(
@@ -149,11 +150,11 @@ export class UnitImpl implements Unit {
if (!this.isActive()) { if (!this.isActive()) {
throw new Error(`cannot delete ${this} not active`); throw new Error(`cannot delete ${this} not active`);
} }
this._owner._units = this._owner._units.filter((b) => b != this); this._owner._units = this._owner._units.filter((b) => b !== this);
this._active = false; this._active = false;
this.mg.addUpdate(this.toUpdate()); this.mg.addUpdate(this.toUpdate());
this.mg.removeUnit(this); this.mg.removeUnit(this);
if (displayMessage && this.type() != UnitType.MIRVWarhead) { if (displayMessage && this.type() !== UnitType.MIRVWarhead) {
this.mg.displayMessage( this.mg.displayMessage(
`Your ${this.type()} was destroyed`, `Your ${this.type()} was destroyed`,
MessageType.ERROR, MessageType.ERROR,
@@ -166,14 +167,14 @@ export class UnitImpl implements Unit {
} }
constructionType(): UnitType | null { constructionType(): UnitType | null {
if (this.type() != UnitType.Construction) { if (this.type() !== UnitType.Construction) {
throw new Error(`Cannot get construction type on ${this.type()}`); throw new Error(`Cannot get construction type on ${this.type()}`);
} }
return this._constructionType ?? null; return this._constructionType ?? null;
} }
setConstructionType(type: UnitType): void { setConstructionType(type: UnitType): void {
if (this.type() != UnitType.Construction) { if (this.type() !== UnitType.Construction) {
throw new Error(`Cannot set construction type on ${this.type()}`); throw new Error(`Cannot set construction type on ${this.type()}`);
} }
this._constructionType = type; this._constructionType = type;