Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions src/core/execution/NukeExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ export class NukeExecution implements Execution {
return this.mg.owner(this.dst);
}

private tilesInRange(): Map<TileRef, number> {
if (this.nuke === null) {
throw new Error("Not initialized");
}
const tilesInRange = new Map<TileRef, number>();
const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
const inner2 = magnitude.inner * magnitude.inner;
this.mg.circleSearch(
this.dst,
magnitude.outer,
(t: TileRef, d2: number) => {
tilesInRange.set(t, d2 <= inner2 ? 1 : 0.5);
return true;
},
);
return tilesInRange;
}

private tilesToDestroy(): Set<TileRef> {
if (this.tilesToDestroyCache !== undefined) {
return this.tilesToDestroyCache;
Expand All @@ -62,27 +80,31 @@ export class NukeExecution implements Execution {
return this.tilesToDestroyCache;
}

private maybeBreakAlliances(toDestroy: Set<TileRef>) {
/**
* Break alliances based on all tiles in range.
* Tiles are weighted roughly based on their chance of being destroyed.
*/
private maybeBreakAlliances(inRange: Map<TileRef, number>) {
if (this.nuke === null) {
throw new Error("Not initialized");
}
const attacked = new Map<Player, number>();
for (const tile of toDestroy) {
for (const [tile, weight] of inRange.entries()) {
const owner = this.mg.owner(tile);
if (owner.isPlayer()) {
const prev = attacked.get(owner) ?? 0;
attacked.set(owner, prev + 1);
attacked.set(owner, prev + weight);
}
}

const threshold = this.mg.config().nukeAllianceBreakThreshold();
for (const [attackedPlayer, tilesDestroyed] of attacked) {
for (const [attackedPlayer, totalWeight] of attacked) {
if (
tilesDestroyed > threshold &&
totalWeight > threshold &&
this.nuke.type() !== UnitType.MIRVWarhead
) {
// Resolves exploit of alliance breaking in which a pending alliance request
// was accepeted in the middle of an missle attack.
// was accepted in the middle of a missile attack.
const allianceRequest = attackedPlayer
.incomingAllianceRequests()
.find((ar) => ar.requestor() === this.player);
Expand Down Expand Up @@ -120,7 +142,9 @@ export class NukeExecution implements Execution {
targetTile: this.dst,
trajectory: this.getTrajectory(this.dst),
});
this.maybeBreakAlliances(this.tilesToDestroy());
if (this.nuke.type() !== UnitType.MIRVWarhead) {
this.maybeBreakAlliances(this.tilesInRange());
}
if (this.mg.hasOwner(this.dst)) {
const target = this.mg.owner(this.dst);
if (!target.isPlayer()) {
Expand Down Expand Up @@ -233,7 +257,6 @@ export class NukeExecution implements Execution {

const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type());
const toDestroy = this.tilesToDestroy();
this.maybeBreakAlliances(toDestroy);

const maxTroops = this.target().isPlayer()
? this.mg.config().maxTroops(this.target() as Player)
Expand Down
7 changes: 7 additions & 0 deletions src/core/game/GameImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,13 @@ export class GameImpl implements Game {
euclideanDistSquared(c1: TileRef, c2: TileRef): number {
return this._map.euclideanDistSquared(c1, c2);
}
circleSearch(
tile: TileRef,
radius: number,
filter?: (tile: TileRef, d2: number) => boolean,
): Set<TileRef> {
return this._map.circleSearch(tile, radius, filter);
}
bfs(
tile: TileRef,
filter: (gm: GameMap, tile: TileRef) => boolean,
Expand Down
28 changes: 28 additions & 0 deletions src/core/game/GameMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export interface GameMap {

manhattanDist(c1: TileRef, c2: TileRef): number;
euclideanDistSquared(c1: TileRef, c2: TileRef): number;
circleSearch(
tile: TileRef,
radius: number,
filter?: (tile: TileRef, d2: number) => boolean,
): Set<TileRef>;
bfs(
tile: TileRef,
filter: (gm: GameMap, tile: TileRef) => boolean,
Expand Down Expand Up @@ -288,6 +293,29 @@ export class GameMapImpl implements GameMap {
const y = this.y(c1) - this.y(c2);
return x * x + y * y;
}
circleSearch(
tile: TileRef,
radius: number,
filter?: (tile: TileRef, d2: number) => boolean,
): Set<TileRef> {
const center = { x: this.x(tile), y: this.y(tile) };
const tiles: Set<TileRef> = new Set<TileRef>();
const minX = Math.max(0, center.x - radius);
const maxX = Math.min(this.width_ - 1, center.x + radius);
const minY = Math.max(0, center.y - radius);
const maxY = Math.min(this.height_ - 1, center.y + radius);
for (let i = minX; i <= maxX; ++i) {
for (let j = minY; j <= maxY; j++) {
const t = this.yToRef[j] + i;
const d2 = this.euclideanDistSquared(tile, t);
if (d2 > radius * radius) continue;
if (!filter || filter(t, d2)) {
tiles.add(t);
}
}
}
return tiles;
}
bfs(
tile: TileRef,
filter: (gm: GameMap, tile: TileRef) => boolean,
Expand Down
7 changes: 7 additions & 0 deletions src/core/game/GameView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,13 @@ export class GameView implements GameMap {
euclideanDistSquared(c1: TileRef, c2: TileRef): number {
return this._map.euclideanDistSquared(c1, c2);
}
circleSearch(
tile: TileRef,
radius: number,
filter?: (tile: TileRef, d2: number) => boolean,
): Set<TileRef> {
return this._map.circleSearch(tile, radius, filter);
}
bfs(
tile: TileRef,
filter: (gm: GameMap, tile: TileRef) => boolean,
Expand Down
Loading