From 729920bdcac6dac6574db758beed7d5a7df63917 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Sat, 5 Apr 2025 13:17:29 -0400 Subject: [PATCH] Avoid sqrt for euclideanDist function (#395) ## Description: In most cases we do not need to solve for the hypotenuse length and can do math directly on the square of the hypotenuse. For example, the common use case for calculating euclidean distance is to check if two points are within a certain distance of each other. In this case, `Math.sqrt(x*x + y*y) < d` can be rewritten as `x*x + y*y < d*d`. ## Please complete the following: - [x] I have added screenshots for all UI updates - [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: fake.neo --------- Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com> --- src/client/ClientGameRunner.ts | 14 ++------------ src/core/execution/MIRVExecution.ts | 3 ++- src/core/execution/NukeExecution.ts | 12 ++++++------ src/core/game/GameImpl.ts | 4 ++-- src/core/game/GameMap.ts | 17 +++++++++-------- src/core/game/GameView.ts | 4 ++-- 6 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 555272e8e..1082b8bdb 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -11,8 +11,7 @@ import { import { createGameRecord } from "../core/Util"; import { ServerConfig } from "../core/configuration/Config"; import { getConfig } from "../core/configuration/ConfigLoader"; -import { TeamName, Unit, UnitType } from "../core/game/Game"; -import { TileRef } from "../core/game/GameMap"; +import { TeamName, UnitType } from "../core/game/Game"; import { ErrorUpdate, GameUpdateType, @@ -20,7 +19,7 @@ import { HashUpdate, WinUpdate, } from "../core/game/GameUpdates"; -import { GameView, PlayerView, UnitView } from "../core/game/GameView"; +import { GameView, PlayerView } from "../core/game/GameView"; import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader"; import { UserSettings } from "../core/game/UserSettings"; import { WorkerClient } from "../core/worker/WorkerClient"; @@ -36,15 +35,6 @@ import { import { createCanvas } from "./Utils"; import { createRenderer, GameRenderer } from "./graphics/GameRenderer"; -export // Is this function needed? -function distSortUnitWorld(tile: TileRef, game: GameView) { - return (a: Unit | UnitView, b: Unit | UnitView) => { - return ( - game.euclideanDist(tile, a.tile()) - game.euclideanDist(tile, b.tile()) - ); - }; -} - export interface LobbyConfig { serverConfig: ServerConfig; flag: string; diff --git a/src/core/execution/MIRVExecution.ts b/src/core/execution/MIRVExecution.ts index 59d292527..bd0120c22 100644 --- a/src/core/execution/MIRVExecution.ts +++ b/src/core/execution/MIRVExecution.ts @@ -156,6 +156,7 @@ export class MirvExecution implements Execution { randomLand(ref: TileRef, taken: TileRef[]): TileRef | null { let tries = 0; + const mirvRange2 = this.mirvRange * this.mirvRange; while (tries < 100) { tries++; const x = this.random.nextInt( @@ -174,7 +175,7 @@ export class MirvExecution implements Execution { if (!this.mg.isLand(tile)) { continue; } - if (this.mg.euclideanDist(tile, ref) > this.mirvRange) { + if (this.mg.euclideanDistSquared(tile, ref) > mirvRange2) { continue; } if (this.mg.owner(tile) != this.targetPlayer) { diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index 1ef305d22..913338e62 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -52,12 +52,11 @@ export class NukeExecution implements Execution { private tilesToDestroy(): Set { const magnitude = this.mg.config().nukeMagnitudes(this.nuke.type()); const rand = new PseudoRandom(this.mg.ticks()); + const inner2 = magnitude.inner * magnitude.inner; + const outer2 = magnitude.outer * magnitude.outer; return this.mg.bfs(this.dst, (_, n: TileRef) => { - const d = this.mg.euclideanDist(this.dst, n); - if (d > magnitude.outer) { - return false; - } - return d <= magnitude.inner || rand.chance(2); + const d2 = this.mg.euclideanDistSquared(this.dst, n); + return d2 <= outer2 && (d2 <= inner2 || rand.chance(2)); }); } @@ -232,6 +231,7 @@ export class NukeExecution implements Execution { } } + const outer2 = magnitude.outer * magnitude.outer; for (const unit of this.mg.units()) { if ( unit.type() != UnitType.AtomBomb && @@ -239,7 +239,7 @@ export class NukeExecution implements Execution { unit.type() != UnitType.MIRVWarhead && unit.type() != UnitType.MIRV ) { - if (this.mg.euclideanDist(this.dst, unit.tile()) < magnitude.outer) { + if (this.mg.euclideanDistSquared(this.dst, unit.tile()) < outer2) { unit.delete(); } } diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index bd13ceeac..b3ccc7eb9 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -684,8 +684,8 @@ export class GameImpl implements Game { manhattanDist(c1: TileRef, c2: TileRef): number { return this._map.manhattanDist(c1, c2); } - euclideanDist(c1: TileRef, c2: TileRef): number { - return this._map.euclideanDist(c1, c2); + euclideanDistSquared(c1: TileRef, c2: TileRef): number { + return this._map.euclideanDistSquared(c1, c2); } bfs( tile: TileRef, diff --git a/src/core/game/GameMap.ts b/src/core/game/GameMap.ts index e9392fe5f..65c6b5bae 100644 --- a/src/core/game/GameMap.ts +++ b/src/core/game/GameMap.ts @@ -38,7 +38,7 @@ export interface GameMap { forEachTile(fn: (tile: TileRef) => void): void; manhattanDist(c1: TileRef, c2: TileRef): number; - euclideanDist(c1: TileRef, c2: TileRef): number; + euclideanDistSquared(c1: TileRef, c2: TileRef): number; bfs( tile: TileRef, filter: (gm: GameMap, tile: TileRef) => boolean, @@ -266,11 +266,10 @@ export class GameMapImpl implements GameMap { Math.abs(this.x(c1) - this.x(c2)) + Math.abs(this.y(c1) - this.y(c2)) ); } - euclideanDist(c1: TileRef, c2: TileRef): number { - return Math.sqrt( - Math.pow(this.x(c1) - this.x(c2), 2) + - Math.pow(this.y(c1) - this.y(c2), 2), - ); + euclideanDistSquared(c1: TileRef, c2: TileRef): number { + const x = this.x(c1) - this.x(c2); + const y = this.y(c1) - this.y(c2); + return x * x + y * y; } bfs( tile: TileRef, @@ -322,8 +321,10 @@ export function euclDistFN( dist: number, center: boolean = false, ): (gm: GameMap, tile: TileRef) => boolean { + const dist2 = dist * dist; if (!center) { - return (gm: GameMap, n: TileRef) => gm.euclideanDist(root, n) <= dist; + return (gm: GameMap, n: TileRef) => + gm.euclideanDistSquared(root, n) <= dist2; } else { return (gm: GameMap, n: TileRef) => { // shifts the root tile’s coordinates by -0.5 so that its “center” @@ -333,7 +334,7 @@ export function euclDistFN( const rootY = gm.y(root) - 0.5; const dx = gm.x(n) - rootX; const dy = gm.y(n) - rootY; - return Math.sqrt(dx * dx + dy * dy) <= dist; + return dx * dx + dy * dy <= dist2; }; } } diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index 6bd58eb79..e5423010e 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -529,8 +529,8 @@ export class GameView implements GameMap { manhattanDist(c1: TileRef, c2: TileRef): number { return this._map.manhattanDist(c1, c2); } - euclideanDist(c1: TileRef, c2: TileRef): number { - return this._map.euclideanDist(c1, c2); + euclideanDistSquared(c1: TileRef, c2: TileRef): number { + return this._map.euclideanDistSquared(c1, c2); } bfs( tile: TileRef,