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>
This commit is contained in:
Scott Anderson
2025-04-05 13:17:29 -04:00
committed by GitHub
parent 25c18cdae2
commit 729920bdca
6 changed files with 23 additions and 31 deletions
+2 -12
View File
@@ -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;
+2 -1
View File
@@ -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) {
+6 -6
View File
@@ -52,12 +52,11 @@ export class NukeExecution implements Execution {
private tilesToDestroy(): Set<TileRef> {
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();
}
}
+2 -2
View File
@@ -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,
+9 -8
View File
@@ -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 tiles 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;
};
}
}
+2 -2
View File
@@ -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,