Use JS classes for Terain-Analysis and InfoMap

`TerrainAnalysis` and `Accessibility` used a costum form of inheritance.

Refs: #6285
This commit is contained in:
phosit 2026-07-14 15:13:32 +02:00
parent ea1026a1cc
commit f9de339f85
No known key found for this signature in database
GPG key ID: C9430B600671C268
3 changed files with 517 additions and 537 deletions

View file

@ -2,8 +2,10 @@
* The map module.
* Copied with changes from QuantumState's original for qBot, it's a component for storing 8 bit values.
*/
export function InfoMap(sharedScript, type, originalMap, actualCopy)
export class InfoMap
{
constructor(sharedScript, type, originalMap, actualCopy)
{
// get the correct dimensions according to the map type
const map = type == "territory" || type == "resource" ? sharedScript.territoryMap : sharedScript.passabilityMap;
this.width = map.width;
@ -27,28 +29,28 @@ export function InfoMap(sharedScript, type, originalMap, actualCopy)
this.map = originalMap;
else
this.map = new Uint8Array(this.length);
}
}
InfoMap.prototype.setMaxVal = function(val)
{
setMaxVal(val)
{
this.maxVal = val;
};
}
InfoMap.prototype.gamePosToMapPos = function(p)
{
gamePosToMapPos(p)
{
return [Math.floor(p[0]/this.cellSize), Math.floor(p[1]/this.cellSize)];
};
}
InfoMap.prototype.point = function(p)
{
point(p)
{
const q = this.gamePosToMapPos(p);
q[0] = q[0] >= this.width ? this.width-1 : q[0] < 0 ? 0 : q[0];
q[1] = q[1] >= this.width ? this.width-1 : q[1] < 0 ? 0 : q[1];
return this.map[q[0] + this.width * q[1]];
};
}
InfoMap.prototype.runLoop = function(x0, x1, y0, y1, cx, cy, maxDist2, func)
{
runLoop(x0, x1, y0, y1, cx, cy, maxDist2, func)
{
for (let y = y0; y < y1; ++y)
{
const dy2 = (y - cy) * (y - cy);
@ -63,10 +65,10 @@ InfoMap.prototype.runLoop = function(x0, x1, y0, y1, cx, cy, maxDist2, func)
this.set(w, func(w, r2));
}
}
};
}
InfoMap.prototype.addInfluence = function(cx, cy, maxDist, strength, type = "linear")
{
addInfluence(cx, cy, maxDist, strength, type = "linear")
{
strength = strength ? strength : maxDist;
const x0 = Math.floor(Math.max(0, cx - maxDist));
@ -87,11 +89,10 @@ InfoMap.prototype.addInfluence = function(cx, cy, maxDist, strength, type = "lin
}
else
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] + strength);
}
};
InfoMap.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type = "constant")
{
multiplyInfluence(cx, cy, maxDist, strength, type = "constant")
{
strength = strength ? +strength : +maxDist;
const x0 = Math.max(0, cx - maxDist);
@ -112,24 +113,24 @@ InfoMap.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type =
}
else
this.runLoop(x0, x1, y0, y1, cx, cy, maxDist2, (w, r2) => this.map[w] * strength);
};
}
/** add to current map by the parameter map pixelwise */
InfoMap.prototype.add = function(map)
{
/** add to current map by the parameter map pixelwise */
add(map)
{
for (let i = 0; i < this.length; ++i)
this.set(i, this.map[i] + map.map[i]);
};
}
/** Set the value taking overflow into account */
InfoMap.prototype.set = function(i, value)
{
/** Set the value taking overflow into account */
set(i, value)
{
this.map[i] = value < 0 ? 0 : value > this.maxVal ? this.maxVal : value;
};
}
/** Find the best non-obstructed tile */
InfoMap.prototype.findBestTile = function(radius, obstruction)
{
/** Find the best non-obstructed tile */
findBestTile(radius, obstruction)
{
let bestIdx;
let bestVal = 0;
for (let j = 0; j < this.length; ++j)
@ -144,11 +145,11 @@ InfoMap.prototype.findBestTile = function(radius, obstruction)
}
return { "idx": bestIdx, "val": bestVal };
};
}
/** return any non obstructed (small) tile inside the (big) tile i from obstruction map */
InfoMap.prototype.getNonObstructedTile = function(i, radius, obstruction)
{
/** return any non obstructed (small) tile inside the (big) tile i from obstruction map */
getNonObstructedTile(i, radius, obstruction)
{
const ratio = this.cellSize / obstruction.cellSize;
const ix = (i % this.width) * ratio;
const iy = Math.floor(i / this.width) * ratio;
@ -171,11 +172,11 @@ InfoMap.prototype.getNonObstructedTile = function(i, radius, obstruction)
}
}
return -1;
};
}
/** return true if the area centered on tile kx-ky and with radius is obstructed */
InfoMap.prototype.isObstructedTile = function(kx, ky, radius)
{
/** return true if the area centered on tile kx-ky and with radius is obstructed */
isObstructedTile(kx, ky, radius)
{
const w = this.width;
if (kx < radius || kx >= w - radius || ky < radius || ky >= w - radius || this.map[kx+ky*w] == 0)
return { "x": kx, "y": ky };
@ -204,9 +205,10 @@ InfoMap.prototype.isObstructedTile = function(kx, ky, radius)
}
}
return null;
};
}
InfoMap.prototype.dumpIm = function(name = "default.png", threshold = this.maxVal)
{
dumpIm(name = "default.png", threshold = this.maxVal)
{
Engine.DumpImage(name, this.map, this.width, this.height, threshold);
};
}
}

View file

@ -113,10 +113,8 @@ SharedScript.prototype.init = function(state, deserialization)
this.entities = new EntityCollection(this, this._entities);
// create the terrain analyzer
this.terrainAnalyzer = new TerrainAnalysis();
this.terrainAnalyzer.init(this, state);
this.accessibility = new Accessibility();
this.accessibility.init(state, this.terrainAnalyzer);
this.terrainAnalyzer = new TerrainAnalysis(this, state);
this.accessibility = new Accessibility(state, this.terrainAnalyzer);
// Resource types: ignore = not used for resource maps
// abundant = abundant resource with small amount each
@ -379,18 +377,6 @@ SharedScript.prototype.deleteMetadata = function(player, ent, key)
return true;
};
export function copyPrototype(descendant, parent)
{
const sConstructor = parent.toString();
const aMatch = sConstructor.match(/\s*function (.*)\(/);
if (aMatch != null)
descendant.prototype[aMatch[1]] = parent;
for (const p in parent.prototype)
descendant.prototype[p] = parent.prototype[p];
}
/** creates a map of resource density */
SharedScript.prototype.createResourceMaps = function()
{

View file

@ -1,5 +1,4 @@
import { InfoMap } from "simulation/ai/common-api/map-module.js";
import { copyPrototype } from "simulation/ai/common-api/shared.js";
import * as terrainStates from "simulation/ai/common-api/terrain-states.js";
/**
@ -12,18 +11,11 @@ import * as terrainStates from "simulation/ai/common-api/terrain-states.js";
* This is intended for use with 8 bit maps for reduced memory usage.
* Upgraded from QuantumState's original TerrainAnalysis for qBot.
*/
export function TerrainAnalysis()
{
}
copyPrototype(TerrainAnalysis, InfoMap);
TerrainAnalysis.prototype.init = function(sharedScript, rawState)
export class TerrainAnalysis extends InfoMap
{
constructor(sharedScript, rawState)
{
const passabilityMap = rawState.passabilityMap;
this.width = passabilityMap.width;
this.height = passabilityMap.height;
this.cellSize = passabilityMap.cellSize;
const obstructionMaskLand = rawState.passabilityClasses["default-terrain-only"];
const obstructionMaskWater = rawState.passabilityClasses["ship-terrain-only"];
@ -48,8 +40,12 @@ TerrainAnalysis.prototype.init = function(sharedScript, rawState)
}
}
this.InfoMap(rawState, "passability", obstructionTiles);
};
super(rawState, "passability", obstructionTiles);
this.width = passabilityMap.width;
this.height = passabilityMap.height;
this.cellSize = passabilityMap.cellSize;
}
}
/**
* Accessibility inherits from TerrainAnalysis
@ -59,18 +55,13 @@ TerrainAnalysis.prototype.init = function(sharedScript, rawState)
* for optimizations it's called after the TerrainAnalyser has finished initializing his map
* so this can use the land regions already.
*/
export function Accessibility()
export class Accessibility extends InfoMap
{
}
copyPrototype(Accessibility, TerrainAnalysis);
Accessibility.prototype.init = function(rawState, terrainAnalyser)
{
this.InfoMap(rawState, "passability", terrainAnalyser.map);
constructor(rawState, terrainAnalyser)
{
super(rawState, "passability", terrainAnalyser.map);
this.landPassMap = new Uint16Array(terrainAnalyser.length);
this.navalPassMap = new Uint16Array(terrainAnalyser.length);
this.maxRegions = 65535;
this.regionSize = [];
this.regionType = []; // "inaccessible", "land" or "water";
@ -138,10 +129,10 @@ Accessibility.prototype.init = function(rawState, terrainAnalyser)
// Engine.DumpImage("LandPassMap.png", this.landPassMap, this.width, this.height, 255);
// Engine.DumpImage("NavalPassMap.png", this.navalPassMap, this.width, this.height, 255);
};
}
Accessibility.prototype.getAccessValue = function(position, onWater)
{
getAccessValue(position, onWater)
{
const gamePos = this.gamePosToMapPos(position);
if (onWater)
return this.navalPassMap[gamePos[0] + this.width*gamePos[1]];
@ -162,10 +153,10 @@ Accessibility.prototype.getAccessValue = function(position, onWater)
}
}
return ret;
};
}
Accessibility.prototype.getTrajectTo = function(start, end)
{
getTrajectTo(start, end)
{
const pstart = this.gamePosToMapPos(start);
const istart = pstart[0] + pstart[1]*this.width;
const pend = this.gamePosToMapPos(end);
@ -185,15 +176,15 @@ Accessibility.prototype.getTrajectTo = function(start, end)
const startRegion = onLand ? this.landPassMap[istart] : this.navalPassMap[istart];
return this.getTrajectToIndex(startRegion, endRegion);
};
}
/**
/**
* Return a "path" of accessibility indexes from one point to another, including the start and the end indexes
* this can tell you what sea zone you need to have a dock on, for example.
* assumes a land unit unless start point is over deep water.
*/
Accessibility.prototype.getTrajectToIndex = function(istart, iend)
{
getTrajectToIndex(istart, iend)
{
if (istart === iend)
return [istart];
@ -219,30 +210,30 @@ Accessibility.prototype.getTrajectToIndex = function(istart, iend)
}
}
return undefined;
};
}
Accessibility.prototype.getRegionSize = function(position, onWater)
{
getRegionSize(position, onWater)
{
const pos = this.gamePosToMapPos(position);
const index = pos[0] + pos[1]*this.width;
const ID = onWater === true ? this.navalPassMap[index] : this.landPassMap[index];
if (this.regionSize[ID] === undefined)
return 0;
return this.regionSize[ID];
};
}
Accessibility.prototype.getRegionSizei = function(index, onWater)
{
getRegionSizei(index, onWater)
{
if (this.regionSize[this.landPassMap[index]] === undefined && (!onWater || this.regionSize[this.navalPassMap[index]] === undefined))
return 0;
if (onWater && this.regionSize[this.navalPassMap[index]] > this.regionSize[this.landPassMap[index]])
return this.regionSize[this.navalPassMap[index]];
return this.regionSize[this.landPassMap[index]];
};
}
/** Implementation of a fast flood fill. Reasonably good performances for JS. */
Accessibility.prototype.floodFill = function(startIndex, value, onWater)
{
/** Implementation of a fast flood fill. Reasonably good performances for JS. */
floodFill(startIndex, value, onWater)
{
if (value > this.maxRegions)
{
error("AI accessibility map: too many regions.");
@ -397,4 +388,5 @@ Accessibility.prototype.floodFill = function(startIndex, value, onWater)
} while (index/w < h-1); // should actually break
}
return true;
};
}
}