[Cleanup] Pass Player into execution constructor instead of PlayerID (#1022)

## Description:
Answering issue:  #1017 
[Cleanup] Pass Player into the execution constructor instead of PlayerID

I have tested the changes running and playing a full game. I do not know
other way to test the changes, please inform me ❤️

## 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

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

Lele

---------

Co-authored-by: lva <lva@rovsing.dk>
This commit is contained in:
Léo Joly
2025-06-06 20:58:15 +02:00
committed by evanpelle
parent 080cf8f3f8
commit 55206ca41f
32 changed files with 120 additions and 361 deletions
+1 -8
View File
@@ -22,7 +22,6 @@ export class AttackExecution implements Execution {
private random = new PseudoRandom(123);
private _owner: Player;
private target: Player | TerraNullius;
private mg: Game;
@@ -31,7 +30,7 @@ export class AttackExecution implements Execution {
constructor(
private startTroops: number | null = null,
private _ownerID: PlayerID,
private _owner: Player,
private _targetID: PlayerID | null,
private sourceTile: TileRef | null = null,
private removeTroops: boolean = true,
@@ -51,18 +50,12 @@ export class AttackExecution implements Execution {
}
this.mg = mg;
if (!mg.hasPlayer(this._ownerID)) {
console.warn(`player ${this._ownerID} not found`);
this.active = false;
return;
}
if (this._targetID !== null && !mg.hasPlayer(this._targetID)) {
console.warn(`target ${this._targetID} not found`);
this.active = false;
return;
}
this._owner = mg.player(this._ownerID);
this.target =
this._targetID === this.mg.terraNullius().id()
? mg.terraNullius()
+3 -20
View File
@@ -1,29 +1,15 @@
import { Execution, Game, Player, PlayerID, UnitType } from "../game/Game";
import { Execution, Game, Player, UnitType } from "../game/Game";
export class BoatRetreatExecution implements Execution {
private active = true;
private player: Player | undefined;
constructor(
private playerID: PlayerID,
private player: Player,
private unitID: number,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.playerID)) {
console.warn(`BoatRetreatExecution: Player ${this.playerID} not found`);
this.active = false;
return;
}
this.player = mg.player(this.playerID);
}
init(mg: Game, ticks: number): void {}
tick(ticks: number): void {
if (!this.player) {
console.warn(`BoatRetreatExecution: Player ${this.playerID} not found`);
this.active = false;
return;
}
const unit = this.player
.units()
.find(
@@ -42,9 +28,6 @@ export class BoatRetreatExecution implements Execution {
}
owner(): Player {
if (this.player === undefined) {
throw new Error("Not initialized");
}
return this.player;
}
+1 -9
View File
@@ -2,31 +2,23 @@ import {
Execution,
Game,
Player,
PlayerID,
Unit,
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
export class CityExecution implements Execution {
private player: Player;
private mg: Game;
private city: Unit | null = null;
private active: boolean = true;
constructor(
private ownerId: PlayerID,
private player: Player,
private tile: TileRef,
) {}
init(mg: Game, ticks: number): void {
this.mg = mg;
if (!mg.hasPlayer(this.ownerId)) {
console.warn(`CityExecution: player ${this.ownerId} not found`);
this.active = false;
return;
}
this.player = mg.player(this.ownerId);
}
tick(ticks: number): void {
+8 -16
View File
@@ -3,7 +3,6 @@ import {
Game,
Gold,
Player,
PlayerID,
Tick,
Unit,
UnitType,
@@ -19,7 +18,6 @@ import { SAMLauncherExecution } from "./SAMLauncherExecution";
import { WarshipExecution } from "./WarshipExecution";
export class ConstructionExecution implements Execution {
private player: Player;
private construction: Unit | null = null;
private active: boolean = true;
private mg: Game;
@@ -29,19 +27,13 @@ export class ConstructionExecution implements Execution {
private cost: Gold;
constructor(
private ownerId: PlayerID,
private player: Player,
private tile: TileRef,
private constructionType: UnitType,
) {}
init(mg: Game, ticks: number): void {
this.mg = mg;
if (!mg.hasPlayer(this.ownerId)) {
console.warn(`ConstructionExecution: owner ${this.ownerId} not found`);
this.active = false;
return;
}
this.player = mg.player(this.ownerId);
}
tick(ticks: number): void {
@@ -97,11 +89,11 @@ export class ConstructionExecution implements Execution {
case UnitType.AtomBomb:
case UnitType.HydrogenBomb:
this.mg.addExecution(
new NukeExecution(this.constructionType, player.id(), this.tile),
new NukeExecution(this.constructionType, player, this.tile),
);
break;
case UnitType.MIRV:
this.mg.addExecution(new MirvExecution(player.id(), this.tile));
this.mg.addExecution(new MirvExecution(player, this.tile));
break;
case UnitType.Warship:
this.mg.addExecution(
@@ -109,19 +101,19 @@ export class ConstructionExecution implements Execution {
);
break;
case UnitType.Port:
this.mg.addExecution(new PortExecution(player.id(), this.tile));
this.mg.addExecution(new PortExecution(player, this.tile));
break;
case UnitType.MissileSilo:
this.mg.addExecution(new MissileSiloExecution(player.id(), this.tile));
this.mg.addExecution(new MissileSiloExecution(player, this.tile));
break;
case UnitType.DefensePost:
this.mg.addExecution(new DefensePostExecution(player.id(), this.tile));
this.mg.addExecution(new DefensePostExecution(player, this.tile));
break;
case UnitType.SAMLauncher:
this.mg.addExecution(new SAMLauncherExecution(player.id(), this.tile));
this.mg.addExecution(new SAMLauncherExecution(player, this.tile));
break;
case UnitType.City:
this.mg.addExecution(new CityExecution(player.id(), this.tile));
this.mg.addExecution(new CityExecution(player, this.tile));
break;
default:
throw Error(`unit type ${this.constructionType} not supported`);
+1 -9
View File
@@ -2,7 +2,6 @@ import {
Execution,
Game,
Player,
PlayerID,
Unit,
UnitType,
} from "../game/Game";
@@ -10,7 +9,6 @@ import { TileRef } from "../game/GameMap";
import { ShellExecution } from "./ShellExecution";
export class DefensePostExecution implements Execution {
private player: Player;
private mg: Game;
private post: Unit | null = null;
private active: boolean = true;
@@ -21,18 +19,12 @@ export class DefensePostExecution implements Execution {
private alreadySentShell = new Set<Unit>();
constructor(
private ownerId: PlayerID,
private player: Player,
private tile: TileRef,
) {}
init(mg: Game, ticks: number): void {
this.mg = mg;
if (!mg.hasPlayer(this.ownerId)) {
console.warn(`DefensePostExectuion: owner ${this.ownerId} not found`);
this.active = false;
return;
}
this.player = mg.player(this.ownerId);
}
private shoot() {
+2 -8
View File
@@ -1,30 +1,24 @@
import { Execution, Game, Gold, Player, PlayerID } from "../game/Game";
export class DonateGoldExecution implements Execution {
private sender: Player;
private recipient: Player;
private active = true;
constructor(
private senderID: PlayerID,
private sender: Player,
private recipientID: PlayerID,
private gold: Gold | null,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`DonateExecution: sender ${this.senderID} not found`);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(`DonateExecution recipient ${this.recipientID} not found`);
this.active = false;
return;
}
this.sender = mg.player(this.senderID);
this.recipient = mg.player(this.recipientID);
if (this.gold === null) {
this.gold = this.sender.gold() / 3n;
+2 -8
View File
@@ -1,30 +1,24 @@
import { Execution, Game, Player, PlayerID } from "../game/Game";
export class DonateTroopsExecution implements Execution {
private sender: Player;
private recipient: Player;
private active = true;
constructor(
private senderID: PlayerID,
private sender: Player,
private recipientID: PlayerID,
private troops: number | null,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`DonateExecution: sender ${this.senderID} not found`);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(`DonateExecution recipient ${this.recipientID} not found`);
this.active = false;
return;
}
this.sender = mg.player(this.senderID);
this.recipient = mg.player(this.recipientID);
if (this.troops === null) {
this.troops = mg.config().defaultDonationAmount(this.sender);
-5
View File
@@ -10,11 +10,6 @@ export class EmbargoExecution implements Execution {
) {}
init(mg: Game, _: number): void {
if (!mg.hasPlayer(this.player.id())) {
console.warn(`EmbargoExecution: sender ${this.player.id()} not found`);
this.active = false;
return;
}
if (!mg.hasPlayer(this.targetID)) {
console.warn(`EmbargoExecution recipient ${this.targetID} not found`);
this.active = false;
+1 -8
View File
@@ -9,30 +9,23 @@ import {
import { flattenedEmojiTable } from "../Util";
export class EmojiExecution implements Execution {
private requestor: Player;
private recipient: Player | typeof AllPlayers;
private active = true;
constructor(
private senderID: PlayerID,
private requestor: Player,
private recipientID: PlayerID | typeof AllPlayers,
private emoji: number,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`EmojiExecution: sender ${this.senderID} not found`);
this.active = false;
return;
}
if (this.recipientID !== AllPlayers && !mg.hasPlayer(this.recipientID)) {
console.warn(`EmojiExecution: recipient ${this.recipientID} not found`);
this.active = false;
return;
}
this.requestor = mg.player(this.senderID);
this.recipient =
this.recipientID === AllPlayers
? AllPlayers
+15 -15
View File
@@ -47,21 +47,21 @@ export class Executor {
console.warn(`player with clientID ${intent.clientID} not found`);
return new NoOpExecution();
}
const playerID = player.id();
// create execution
switch (intent.type) {
case "attack": {
return new AttackExecution(
intent.troops,
playerID,
player,
intent.targetID,
null,
);
}
case "cancel_attack":
return new RetreatExecution(playerID, intent.attackID);
return new RetreatExecution(player, intent.attackID);
case "cancel_boat":
return new BoatRetreatExecution(playerID, intent.unitID);
return new BoatRetreatExecution(player, intent.unitID);
case "move_warship":
return new MoveWarshipExecution(player, intent.unitId, intent.tile);
case "spawn":
@@ -75,47 +75,47 @@ export class Executor {
src = this.mg.ref(intent.srcX, intent.srcY);
}
return new TransportShipExecution(
playerID,
player,
intent.targetID,
this.mg.ref(intent.dstX, intent.dstY),
intent.troops,
src,
);
case "allianceRequest":
return new AllianceRequestExecution(playerID, intent.recipient);
return new AllianceRequestExecution(player, intent.recipient);
case "allianceRequestReply":
return new AllianceRequestReplyExecution(
intent.requestor,
playerID,
player,
intent.accept,
);
case "breakAlliance":
return new BreakAllianceExecution(playerID, intent.recipient);
return new BreakAllianceExecution(player, intent.recipient);
case "targetPlayer":
return new TargetPlayerExecution(playerID, intent.target);
return new TargetPlayerExecution(player, intent.target);
case "emoji":
return new EmojiExecution(playerID, intent.recipient, intent.emoji);
return new EmojiExecution(player, intent.recipient, intent.emoji);
case "donate_troops":
return new DonateTroopsExecution(
playerID,
player,
intent.recipient,
intent.troops,
);
case "donate_gold":
return new DonateGoldExecution(playerID, intent.recipient, intent.gold);
return new DonateGoldExecution(player, intent.recipient, intent.gold);
case "troop_ratio":
return new SetTargetTroopRatioExecution(playerID, intent.ratio);
return new SetTargetTroopRatioExecution(player, intent.ratio);
case "embargo":
return new EmbargoExecution(player, intent.targetID, intent.action);
case "build_unit":
return new ConstructionExecution(
playerID,
player,
this.mg.ref(intent.x, intent.y),
intent.unit,
);
case "quick_chat":
return new QuickChatExecution(
playerID,
player,
intent.recipient,
intent.quickChatKey,
intent.variables ?? {},
+7 -13
View File
@@ -282,7 +282,7 @@ export class FakeHumanExecution implements Execution {
this.lastEmojiSent.set(enemy, this.mg.ticks());
this.mg.addExecution(
new EmojiExecution(
this.player.id(),
this.player,
enemy.id(),
this.random.randElement(this.heckleEmoji),
),
@@ -354,7 +354,7 @@ export class FakeHumanExecution implements Execution {
const tick = this.mg.ticks();
this.lastNukeSent.push([tick, tile]);
this.mg.addExecution(
new NukeExecution(UnitType.AtomBomb, this.player.id(), tile),
new NukeExecution(UnitType.AtomBomb, this.player, tile),
);
}
@@ -421,7 +421,7 @@ export class FakeHumanExecution implements Execution {
}
this.mg.addExecution(
new TransportShipExecution(
this.player.id(),
this.player,
other.id(),
closest.y,
this.player.troops() / 5,
@@ -441,7 +441,7 @@ export class FakeHumanExecution implements Execution {
if (oceanTiles.length > 0) {
const buildTile = this.random.randElement(oceanTiles);
this.mg.addExecution(
new ConstructionExecution(player.id(), buildTile, UnitType.Port),
new ConstructionExecution(player, buildTile, UnitType.Port),
);
}
return;
@@ -470,9 +470,7 @@ export class FakeHumanExecution implements Execution {
if (canBuild === false) {
return;
}
this.mg.addExecution(
new ConstructionExecution(this.player.id(), tile, type),
);
this.mg.addExecution(new ConstructionExecution(this.player, tile, type));
}
private maybeSpawnWarship(): boolean {
@@ -498,11 +496,7 @@ export class FakeHumanExecution implements Execution {
return false;
}
this.mg.addExecution(
new ConstructionExecution(
this.player.id(),
targetTile,
UnitType.Warship,
),
new ConstructionExecution(this.player, targetTile, UnitType.Warship),
);
return true;
}
@@ -573,7 +567,7 @@ export class FakeHumanExecution implements Execution {
this.mg.addExecution(
new TransportShipExecution(
this.player.id(),
this.player,
this.mg.owner(dst).id(),
dst,
this.player.troops() / 5,
+3 -13
View File
@@ -3,7 +3,6 @@ import {
Game,
MessageType,
Player,
PlayerID,
TerraNullius,
Unit,
UnitType,
@@ -15,8 +14,6 @@ import { simpleHash } from "../Util";
import { NukeExecution } from "./NukeExecution";
export class MirvExecution implements Execution {
private player: Player;
private active = true;
private mg: Game;
@@ -37,21 +34,14 @@ export class MirvExecution implements Execution {
private speed: number = -1;
constructor(
private senderID: PlayerID,
private player: Player,
private dst: TileRef,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`MIRVExecution: player ${this.senderID} not found`);
this.active = false;
return;
}
this.random = new PseudoRandom(mg.ticks() + simpleHash(this.senderID));
this.random = new PseudoRandom(mg.ticks() + simpleHash(this.player.id()));
this.mg = mg;
this.pathFinder = new ParabolaPathFinder(mg);
this.player = mg.player(this.senderID);
this.targetPlayer = this.mg.owner(this.dst);
this.speed = this.mg.config().defaultNukeSpeed();
@@ -118,7 +108,7 @@ export class MirvExecution implements Execution {
this.mg.addExecution(
new NukeExecution(
UnitType.MIRVWarhead,
this.senderID,
this.player,
dst,
this.nuke.tile(),
15 + Math.floor((i / this.warheadCount) * 5),
+2 -14
View File
@@ -2,7 +2,6 @@ import {
Execution,
Game,
Player,
PlayerID,
Unit,
UnitType,
} from "../game/Game";
@@ -10,30 +9,19 @@ import { TileRef } from "../game/GameMap";
export class MissileSiloExecution implements Execution {
private active = true;
private mg: Game | null = null;
private player: Player | null = null;
private mg: Game;
private silo: Unit | null = null;
constructor(
private _owner: PlayerID,
private player: Player,
private tile: TileRef,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this._owner)) {
console.warn(`MissileSiloExecution: owner ${this._owner} not found`);
this.active = false;
return;
}
this.mg = mg;
this.player = mg.player(this._owner);
}
tick(ticks: number): void {
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);
if (spawn === false) {
+12 -33
View File
@@ -3,7 +3,6 @@ import {
Game,
MessageType,
Player,
PlayerID,
TerraNullius,
Unit,
UnitType,
@@ -15,8 +14,7 @@ import { NukeType } from "../StatsSchemas";
export class NukeExecution implements Execution {
private active = true;
private player: Player | null = null;
private mg: Game | null = null;
private mg: Game;
private nuke: Unit | null = null;
private tilesToDestroyCache: Set<TileRef> | undefined;
@@ -24,8 +22,8 @@ export class NukeExecution implements Execution {
private pathFinder: ParabolaPathFinder;
constructor(
private type: NukeType,
private senderID: PlayerID,
private nukeType: NukeType,
private player: Player,
private dst: TileRef,
private src?: TileRef | null,
private speed: number = -1,
@@ -33,14 +31,7 @@ export class NukeExecution implements Execution {
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`NukeExecution: sender ${this.senderID} not found`);
this.active = false;
return;
}
this.mg = mg;
this.player = mg.player(this.senderID);
this.random = new PseudoRandom(ticks);
if (this.speed === -1) {
this.speed = this.mg.config().defaultNukeSpeed();
@@ -49,9 +40,6 @@ export class NukeExecution implements Execution {
}
public target(): Player | TerraNullius {
if (this.mg === null) {
throw new Error("Not initialized");
}
return this.mg.owner(this.dst);
}
@@ -59,7 +47,7 @@ export class NukeExecution implements Execution {
if (this.tilesToDestroyCache !== undefined) {
return this.tilesToDestroyCache;
}
if (this.mg === null || this.nuke === null) {
if (this.nuke === null) {
throw new Error("Not initialized");
}
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
@@ -74,7 +62,7 @@ export class NukeExecution implements Execution {
}
private breakAlliances(toDestroy: Set<TileRef>) {
if (this.mg === null || this.player === null || this.nuke === null) {
if (this.nuke === null) {
throw new Error("Not initialized");
}
const attacked = new Map<Player, number>();
@@ -101,12 +89,8 @@ export class NukeExecution implements Execution {
}
tick(ticks: number): void {
if (this.mg === null || this.player === null) {
throw new Error("Not initialized");
}
if (this.nuke === null) {
const spawn = this.src ?? this.player.canBuild(this.type, this.dst);
const spawn = this.src ?? this.player.canBuild(this.nukeType, this.dst);
if (spawn === false) {
console.warn(`cannot build Nuke`);
this.active = false;
@@ -115,16 +99,16 @@ export class NukeExecution implements Execution {
this.pathFinder.computeControlPoints(
spawn,
this.dst,
this.type !== UnitType.MIRVWarhead,
this.nukeType !== UnitType.MIRVWarhead,
);
this.nuke = this.player.buildUnit(this.type, spawn, {
this.nuke = this.player.buildUnit(this.nukeType, spawn, {
targetTile: this.dst,
});
if (this.mg.hasOwner(this.dst)) {
const target = this.mg.owner(this.dst);
if (!target.isPlayer()) {
// Ignore terra nullius
} else if (this.type === UnitType.AtomBomb) {
} else if (this.nukeType === UnitType.AtomBomb) {
this.mg.displayIncomingUnit(
this.nuke.id(),
`${this.player.name()} - atom bomb inbound`,
@@ -132,7 +116,7 @@ export class NukeExecution implements Execution {
target.id(),
);
this.breakAlliances(this.tilesToDestroy());
} else if (this.type === UnitType.HydrogenBomb) {
} else if (this.nukeType === UnitType.HydrogenBomb) {
this.mg.displayIncomingUnit(
this.nuke.id(),
`${this.player.name()} - hydrogen bomb inbound`,
@@ -143,9 +127,7 @@ export class NukeExecution implements Execution {
}
// Record stats
this.mg
.stats()
.bombLaunch(this.player, target, this.nuke.type() as NukeType);
this.mg.stats().bombLaunch(this.player, target, this.nukeType);
}
// after sending a nuke set the missilesilo on cooldown
@@ -181,7 +163,7 @@ export class NukeExecution implements Execution {
}
private detonate() {
if (this.mg === null || this.nuke === null || this.player === null) {
if (this.nuke === null) {
throw new Error("Not initialized");
}
@@ -248,9 +230,6 @@ export class NukeExecution implements Execution {
}
owner(): Player {
if (this.player === null) {
throw new Error("Not initialized");
}
return this.player;
}
+3 -32
View File
@@ -5,7 +5,6 @@ import {
Game,
MessageType,
Player,
PlayerID,
UnitType,
} from "../game/Game";
import { GameImpl } from "../game/GameImpl";
@@ -15,35 +14,25 @@ import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util";
export class PlayerExecution implements Execution {
private readonly ticksPerClusterCalc = 20;
private player: Player | null = null;
private config: Config | null = null;
private config: Config;
private lastCalc = 0;
private mg: Game | null = null;
private mg: Game;
private active = true;
constructor(private playerID: PlayerID) {}
constructor(private player: Player) {}
activeDuringSpawnPhase(): boolean {
return false;
}
init(mg: Game, ticks: number) {
if (!mg.hasPlayer(this.playerID)) {
console.warn(`PlayerExecution: player ${this.playerID} not found`);
this.active = false;
return;
}
this.mg = mg;
this.config = mg.config();
this.player = mg.player(this.playerID);
this.lastCalc =
ticks + (simpleHash(this.player.name()) % this.ticksPerClusterCalc);
}
tick(ticks: number) {
if (this.mg === null || this.config === null || this.player === null) {
throw new Error("Not initialized");
}
this.player.decayRelations();
this.player.units().forEach((u) => {
const tileOwner = this.mg!.owner(u.tile());
@@ -122,9 +111,6 @@ export class PlayerExecution implements Execution {
}
private removeClusters() {
if (this.mg === null || this.player === null) {
throw new Error("Not initialized");
}
const clusters = this.calculateClusters();
clusters.sort((a, b) => b.size - a.size);
@@ -144,9 +130,6 @@ export class PlayerExecution implements Execution {
}
private surroundedBySamePlayer(cluster: Set<TileRef>): false | Player {
if (this.mg === null || this.player === null) {
throw new Error("Not initialized");
}
const enemies = new Set<number>();
for (const tile of cluster) {
const isOceanShore = this.mg.isOceanShore(tile);
@@ -181,9 +164,6 @@ export class PlayerExecution implements Execution {
}
private isSurrounded(cluster: Set<TileRef>): boolean {
if (this.mg === null || this.player === null) {
throw new Error("Not initialized");
}
const enemyTiles = new Set<TileRef>();
for (const tr of cluster) {
if (this.mg.isShore(tr) || this.mg.isOnEdgeOfMap(tr)) {
@@ -207,9 +187,6 @@ export class PlayerExecution implements Execution {
}
private removeCluster(cluster: Set<TileRef>) {
if (this.mg === null || this.player === null) {
throw new Error("Not initialized");
}
if (
Array.from(cluster).some(
(t) => this.mg?.ownerID(t) !== this.player?.smallID(),
@@ -252,9 +229,6 @@ export class PlayerExecution implements Execution {
}
private getCapturingPlayer(cluster: Set<TileRef>): Player | null {
if (this.mg === null || this.player === null) {
throw new Error("Not initialized");
}
const neighborsIDs = new Set<number>();
for (const t of cluster) {
for (const neighbor of this.mg.neighbors(t)) {
@@ -297,9 +271,6 @@ export class PlayerExecution implements Execution {
}
private calculateClusters(): Set<TileRef>[] {
if (this.mg === null || this.player === null) {
throw new Error("Not initialized");
}
const seen = new Set<TileRef>();
const border = this.player.borderTiles();
const clusters: Set<TileRef>[] = [];
+11 -29
View File
@@ -1,11 +1,4 @@
import {
Execution,
Game,
Player,
PlayerID,
Unit,
UnitType,
} from "../game/Game";
import { Execution, Game, Player, Unit, UnitType } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { PseudoRandom } from "../PseudoRandom";
import { TradeShipExecution } from "./TradeShipExecution";
@@ -18,16 +11,11 @@ export class PortExecution implements Execution {
private checkOffset: number | null = null;
constructor(
private _owner: PlayerID,
private player: Player,
private tile: TileRef,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this._owner)) {
console.warn(`PortExecution: player ${this._owner} not found`);
this.active = false;
return;
}
this.mg = mg;
this.random = new PseudoRandom(mg.ticks());
this.checkOffset = mg.ticks() % 10;
@@ -39,14 +27,15 @@ export class PortExecution implements Execution {
}
if (this.port === null) {
const tile = this.tile;
const player = this.mg.player(this._owner);
const spawn = player.canBuild(UnitType.Port, tile);
const spawn = this.player.canBuild(UnitType.Port, tile);
if (spawn === false) {
console.warn(`player ${player} cannot build port at ${this.tile}`);
console.warn(
`player ${this.player.id()} cannot build port at ${this.tile}`,
);
this.active = false;
return;
}
this.port = player.buildUnit(UnitType.Port, spawn, {});
this.port = this.player.buildUnit(UnitType.Port, spawn, {});
}
if (!this.port.isActive()) {
@@ -54,8 +43,8 @@ export class PortExecution implements Execution {
return;
}
if (this._owner !== this.port.owner().id()) {
this._owner = this.port.owner().id();
if (this.player.id() !== this.port.owner().id()) {
this.player = this.port.owner();
}
// Only check every 10 ticks for performance.
@@ -70,7 +59,7 @@ export class PortExecution implements Execution {
return;
}
const ports = this.player().tradingPorts(this.port);
const ports = this.player.tradingPorts(this.port);
if (ports.length === 0) {
return;
@@ -78,7 +67,7 @@ export class PortExecution implements Execution {
const port = this.random.randElement(ports);
this.mg.addExecution(
new TradeShipExecution(this.player().id(), this.port, port),
new TradeShipExecution(this.player, this.port, port),
);
}
@@ -89,11 +78,4 @@ export class PortExecution implements Execution {
activeDuringSpawnPhase(): boolean {
return false;
}
player(): Player {
if (this.port === null) {
throw new Error("Not initialized");
}
return this.port.owner();
}
}
+2 -8
View File
@@ -1,14 +1,14 @@
import { Execution, Game, Player, PlayerID } from "../game/Game";
export class QuickChatExecution implements Execution {
private sender: Player;
private recipient: Player;
private mg: Game;
private active = true;
constructor(
private senderID: PlayerID,
private sender: Player,
private recipientID: PlayerID,
private quickChatKey: string,
private variables: Record<string, string>,
@@ -16,11 +16,6 @@ export class QuickChatExecution implements Execution {
init(mg: Game, ticks: number): void {
this.mg = mg;
if (!mg.hasPlayer(this.senderID)) {
console.warn(`QuickChatExecution: sender ${this.senderID} not found`);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`QuickChatExecution: recipient ${this.recipientID} not found`,
@@ -29,7 +24,6 @@ export class QuickChatExecution implements Execution {
return;
}
this.sender = mg.player(this.senderID);
this.recipient = mg.player(this.recipientID);
}
+2 -9
View File
@@ -1,26 +1,19 @@
import { Execution, Game, Player, PlayerID } from "../game/Game";
import { Execution, Game, Player } from "../game/Game";
const cancelDelay = 20;
export class RetreatExecution implements Execution {
private active = true;
private retreatOrdered = false;
private player: Player;
private startTick: number;
private mg: Game;
constructor(
private playerID: PlayerID,
private player: Player,
private attackID: string,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.playerID)) {
console.warn(`RetreatExecution: player ${this.playerID} not found`);
return;
}
this.mg = mg;
this.player = mg.player(this.playerID);
this.startTick = mg.ticks();
}
+1 -9
View File
@@ -3,7 +3,6 @@ import {
Game,
MessageType,
Player,
PlayerID,
Unit,
UnitType,
} from "../game/Game";
@@ -12,7 +11,6 @@ import { PseudoRandom } from "../PseudoRandom";
import { SAMMissileExecution } from "./SAMMissileExecution";
export class SAMLauncherExecution implements Execution {
private player: Player;
private mg: Game;
private active: boolean = true;
@@ -26,7 +24,7 @@ export class SAMLauncherExecution implements Execution {
private pseudoRandom: PseudoRandom | undefined;
constructor(
private ownerId: PlayerID,
private player: Player,
private tile: TileRef | null,
private sam: Unit | null = null,
) {
@@ -37,12 +35,6 @@ export class SAMLauncherExecution implements Execution {
init(mg: Game, ticks: number): void {
this.mg = mg;
if (!mg.hasPlayer(this.ownerId)) {
console.warn(`SAMLauncherExecution: owner ${this.ownerId} not found`);
this.active = false;
return;
}
this.player = mg.player(this.ownerId);
}
private nukeTargetInRange(nuke: Unit) {
@@ -1,23 +1,14 @@
import { Execution, Game, Player, PlayerID } from "../game/Game";
import { Execution, Game, Player } from "../game/Game";
export class SetTargetTroopRatioExecution implements Execution {
private player: Player;
private active = true;
constructor(
private playerID: PlayerID,
private player: Player,
private targetTroopsRatio: number,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.playerID)) {
console.warn(
`SetTargetTRoopRatioExecution: player ${this.playerID} not found`,
);
}
this.player = mg.player(this.playerID);
}
init(mg: Game, ticks: number): void {}
tick(ticks: number): void {
if (this.targetTroopsRatio < 0 || this.targetTroopsRatio > 1) {
+1 -1
View File
@@ -38,7 +38,7 @@ export class SpawnExecution implements Execution {
});
if (!player.hasSpawned()) {
this.mg.addExecution(new PlayerExecution(player.id()));
this.mg.addExecution(new PlayerExecution(player));
if (player.type() === PlayerType.Bot) {
this.mg.addExecution(new BotExecution(player));
}
+1 -10
View File
@@ -1,31 +1,22 @@
import { Execution, Game, Player, PlayerID } from "../game/Game";
export class TargetPlayerExecution implements Execution {
private requestor: Player;
private target: Player;
private active = true;
constructor(
private requestorID: PlayerID,
private requestor: Player,
private targetID: PlayerID,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`TargetPlayerExecution: requestor ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.targetID)) {
console.warn(`TargetPlayerExecution: target ${this.targetID} not found`);
this.active = false;
return;
}
this.requestor = mg.player(this.requestorID);
this.target = mg.player(this.targetID);
}
+1 -4
View File
@@ -4,7 +4,6 @@ import {
Game,
MessageType,
Player,
PlayerID,
Unit,
UnitType,
} from "../game/Game";
@@ -16,20 +15,18 @@ import { distSortUnit } from "../Util";
export class TradeShipExecution implements Execution {
private active = true;
private mg: Game;
private origOwner: Player;
private tradeShip: Unit | undefined;
private wasCaptured = false;
private pathFinder: PathFinder;
constructor(
private _owner: PlayerID,
private origOwner: Player,
private srcPort: Unit,
private _dstPort: Unit,
) {}
init(mg: Game, ticks: number): void {
this.mg = mg;
this.origOwner = mg.player(this._owner);
this.pathFinder = PathFinder.Mini(mg, 2500);
}
+3 -13
View File
@@ -23,7 +23,6 @@ export class TransportShipExecution implements Execution {
private active = true;
private mg: Game;
private attacker: Player;
private target: Player | TerraNullius;
// TODO make private
@@ -35,7 +34,7 @@ export class TransportShipExecution implements Execution {
private pathFinder: PathFinder;
constructor(
private attackerID: PlayerID,
private attacker: Player,
private targetID: PlayerID | null,
private ref: TileRef,
private troops: number,
@@ -47,13 +46,6 @@ export class TransportShipExecution implements Execution {
}
init(mg: Game, ticks: number) {
if (!mg.hasPlayer(this.attackerID)) {
console.warn(
`TransportShipExecution: attacker ${this.attackerID} not found`,
);
this.active = false;
return;
}
if (this.targetID !== null && !mg.hasPlayer(this.targetID)) {
console.warn(`TransportShipExecution: target ${this.targetID} not found`);
this.active = false;
@@ -64,8 +56,6 @@ export class TransportShipExecution implements Execution {
this.mg = mg;
this.pathFinder = PathFinder.Mini(mg, 10_000, 10);
this.attacker = mg.player(this.attackerID);
if (
this.attacker.units(UnitType.TransportShip).length >=
mg.config().boatMaxNumber()
@@ -73,7 +63,7 @@ export class TransportShipExecution implements Execution {
mg.displayMessage(
`No boats available, max ${mg.config().boatMaxNumber()}`,
MessageType.WARN,
this.attackerID,
this.attacker.id(),
);
this.active = false;
this.attacker.addTroops(this.troops);
@@ -192,7 +182,7 @@ export class TransportShipExecution implements Execution {
this.mg.addExecution(
new AttackExecution(
this.troops,
this.attacker.id(),
this.attacker,
this.targetID,
this.dst,
false,
@@ -2,22 +2,14 @@ import { Execution, Game, Player, PlayerID } from "../../game/Game";
export class AllianceRequestExecution implements Execution {
private active = true;
private requestor: Player | null = null;
private recipient: Player | null = null;
constructor(
private requestorID: PlayerID,
private requestor: Player,
private recipientID: PlayerID,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`AllianceRequestExecution requester ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`AllianceRequestExecution recipient ${this.recipientID} not found`,
@@ -26,12 +18,11 @@ export class AllianceRequestExecution implements Execution {
return;
}
this.requestor = mg.player(this.requestorID);
this.recipient = mg.player(this.recipientID);
}
tick(ticks: number): void {
if (this.requestor === null || this.recipient === null) {
if (this.recipient === null) {
throw new Error("Not initialized");
}
if (this.requestor.isFriendly(this.recipient)) {
@@ -3,11 +3,10 @@ import { Execution, Game, Player, PlayerID } from "../../game/Game";
export class AllianceRequestReplyExecution implements Execution {
private active = true;
private requestor: Player | null = null;
private recipient: Player | null = null;
constructor(
private requestorID: PlayerID,
private recipientID: PlayerID,
private recipient: Player,
private accept: boolean,
) {}
@@ -19,19 +18,11 @@ export class AllianceRequestReplyExecution implements Execution {
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`AllianceRequestReplyExecution recipient ${this.recipientID} not found`,
);
this.active = false;
return;
}
this.requestor = mg.player(this.requestorID);
this.recipient = mg.player(this.recipientID);
}
tick(ticks: number): void {
if (this.requestor === null || this.recipient === null) {
if (this.requestor === null) {
throw new Error("Not initialized");
}
if (this.requestor.isFriendly(this.recipient)) {
@@ -2,23 +2,15 @@ import { Execution, Game, Player, PlayerID } from "../../game/Game";
export class BreakAllianceExecution implements Execution {
private active = true;
private requestor: Player | null = null;
private recipient: Player | null = null;
private mg: Game | null = null;
constructor(
private requestorID: PlayerID,
private requestor: Player,
private recipientID: PlayerID,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.requestorID)) {
console.warn(
`BreakAllianceExecution requester ${this.requestorID} not found`,
);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(
`BreakAllianceExecution: recipient ${this.recipientID} not found`,
@@ -26,7 +18,6 @@ export class BreakAllianceExecution implements Execution {
this.active = false;
return;
}
this.requestor = mg.player(this.requestorID);
this.recipient = mg.player(this.recipientID);
this.mg = mg;
}
+2 -2
View File
@@ -41,7 +41,7 @@ export class BotBehavior {
private emoji(player: Player, emoji: number) {
if (player.type() !== PlayerType.Human) return;
this.game.addExecution(
new EmojiExecution(this.player.id(), player.id(), emoji),
new EmojiExecution(this.player, player.id(), emoji),
);
}
@@ -194,7 +194,7 @@ export class BotBehavior {
this.game.addExecution(
new AttackExecution(
troops,
this.player.id(),
this.player,
target.isPlayer() ? target.id() : null,
),
);
+13 -7
View File
@@ -21,7 +21,13 @@ let attackerSpawn: TileRef;
function sendBoat(target: TileRef, source: TileRef, troops: number) {
game.addExecution(
new TransportShipExecution(defender.id(), null, target, troops, source),
new TransportShipExecution(
defender,
null,
target,
troops,
source,
),
);
}
@@ -64,7 +70,7 @@ describe("Attack", () => {
attacker = game.player(attackerInfo.id);
defender = game.player(defenderInfo.id);
game.addExecution(new AttackExecution(100, defender.id(), null));
game.addExecution(new AttackExecution(100, defender, game.terraNullius().id()));
game.executeNextTick();
while (defender.outgoingAttacks().length > 0) {
game.executeNextTick();
@@ -76,10 +82,10 @@ describe("Attack", () => {
test("Nuke reduce attacking troop counts", async () => {
// Not building exactly spawn to it's better protected from attacks (but still
// on defender territory)
constructionExecution(game, defender.id(), 1, 1, UnitType.MissileSilo);
constructionExecution(game, defender, 1, 1, UnitType.MissileSilo);
expect(defender.units(UnitType.MissileSilo)).toHaveLength(1);
game.addExecution(new AttackExecution(100, attacker.id(), defender.id()));
constructionExecution(game, defender.id(), 0, 15, UnitType.AtomBomb, 3);
game.addExecution(new AttackExecution(100, attacker, defender.id()));
constructionExecution(game, defender, 0, 15, UnitType.AtomBomb, 3);
const nuke = defender.units(UnitType.AtomBomb)[0];
expect(nuke.isActive()).toBe(true);
@@ -94,12 +100,12 @@ describe("Attack", () => {
});
test("Nuke reduce attacking boat troop count", async () => {
constructionExecution(game, defender.id(), 1, 1, UnitType.MissileSilo);
constructionExecution(game, defender, 1, 1, UnitType.MissileSilo);
expect(defender.units(UnitType.MissileSilo)).toHaveLength(1);
sendBoat(game.ref(15, 8), game.ref(10, 5), 100);
constructionExecution(game, defender.id(), 0, 15, UnitType.AtomBomb, 3);
constructionExecution(game, defender, 0, 15, UnitType.AtomBomb, 3);
const nuke = defender.units(UnitType.AtomBomb)[0];
expect(nuke.isActive()).toBe(true);
+2 -2
View File
@@ -20,7 +20,7 @@ function attackerBuildsNuke(
initialize = true,
) {
game.addExecution(
new NukeExecution(UnitType.AtomBomb, attacker.id(), target, source),
new NukeExecution(UnitType.AtomBomb, attacker, target, source),
);
if (initialize) {
game.executeNextTick();
@@ -50,7 +50,7 @@ describe("MissileSilo", () => {
attacker = game.player("attacker_id");
constructionExecution(game, attacker.id(), 1, 1, UnitType.MissileSilo);
constructionExecution(game, attacker, 1, 1, UnitType.MissileSilo);
});
test("missilesilo should launch nuke", async () => {
+9 -9
View File
@@ -61,12 +61,12 @@ describe("SAM", () => {
defender = game.player("defender_id");
far_defender = game.player("far_defender_id");
constructionExecution(game, attacker.id(), 7, 7, UnitType.MissileSilo);
constructionExecution(game, attacker, 7, 7, UnitType.MissileSilo);
});
test("one sam should take down one nuke", async () => {
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam));
game.addExecution(new SAMLauncherExecution(defender, null, sam));
attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 1), {
targetTile: game.ref(2, 1),
});
@@ -78,7 +78,7 @@ describe("SAM", () => {
test("sam should only get one nuke at a time", async () => {
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam));
game.addExecution(new SAMLauncherExecution(defender, null, sam));
attacker.buildUnit(UnitType.AtomBomb, game.ref(2, 1), {
targetTile: game.ref(2, 1),
});
@@ -94,7 +94,7 @@ describe("SAM", () => {
test("sam should cooldown as long as configured", async () => {
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam));
game.addExecution(new SAMLauncherExecution(defender, null, sam));
expect(sam.isInCooldown()).toBeFalsy();
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), {
targetTile: game.ref(1, 2),
@@ -117,9 +117,9 @@ describe("SAM", () => {
const sam1 = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {
cooldownDuration: 10,
});
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam1));
game.addExecution(new SAMLauncherExecution(defender, null, sam1));
const sam2 = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 2), {});
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam2));
game.addExecution(new SAMLauncherExecution(defender, null, sam2));
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(2, 2), {
targetTile: game.ref(2, 2),
});
@@ -134,7 +134,7 @@ describe("SAM", () => {
const targetDistance = 199;
// Close SAM: should not intercept anything
const sam1 = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam1));
game.addExecution(new SAMLauncherExecution(defender, null, sam1));
// Far SAM: Should intercept the nuke. Use the far_defender so the SAM can be built
const sam2 = far_defender.buildUnit(
@@ -142,11 +142,11 @@ describe("SAM", () => {
game.ref(targetDistance, 1),
{},
);
game.addExecution(new SAMLauncherExecution(far_defender.id(), null, sam2));
game.addExecution(new SAMLauncherExecution(far_defender, null, sam2));
const nukeExecution = new NukeExecution(
UnitType.AtomBomb,
attacker.id(),
attacker,
game.ref(targetDistance, 1),
null,
);
+3 -3
View File
@@ -4,18 +4,18 @@
// If you also need execution use function below. Does not work with things not
import { ConstructionExecution } from "../../src/core/execution/ConstructionExecution";
import { Game, PlayerID, UnitType } from "../../src/core/game/Game";
import { Game, Player, UnitType } from "../../src/core/game/Game";
// built via UI (e.g.: trade ships)
export function constructionExecution(
game: Game,
playerID: PlayerID,
_owner: Player,
x: number,
y: number,
unit: UnitType,
ticks = 4,
) {
game.addExecution(new ConstructionExecution(playerID, game.ref(x, y), unit));
game.addExecution(new ConstructionExecution(_owner, game.ref(x, y), unit));
// 4 ticks by default as it usually goes like this
// Init of construction execution