mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 12:46:30 +00:00
Enhance game map handling with microGameMap integration
- Added `microGameMap` to support a new resolution level for compact games, allowing for more efficient map loading and pathfinding. - Updated `createGame` and `GameImpl` to incorporate `microGameMap`, ensuring proper handling of different map resolutions. - Modified `loadTerrainMap` to always expose the 16x map for coarse heuristics, improving navigation capabilities.
This commit is contained in:
@@ -13,10 +13,11 @@ Yes. The terrain loader already ships multiple resolutions per map:
|
||||
- `manifest.map4x` + `map4x.bin` (coarser)
|
||||
- `manifest.map16x` + `map16x.bin` (even coarser)
|
||||
|
||||
At runtime we already load both:
|
||||
At runtime we load:
|
||||
|
||||
- `gameMap`: full res for normal games (or `map4x` for compact games)
|
||||
- `miniGameMap`: lower res (`map4x` for normal games, or `map16x` for compact games)
|
||||
- `microGameMap`: always `map16x` (in compact games this is the same instance as `miniGameMap`)
|
||||
|
||||
So we can prototype coarse-to-fine without extending mapgen first.
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ export async function createGameRunner(
|
||||
nations,
|
||||
gameMap.gameMap,
|
||||
gameMap.miniGameMap,
|
||||
gameMap.microGameMap,
|
||||
config,
|
||||
);
|
||||
|
||||
|
||||
@@ -673,6 +673,7 @@ export interface Game extends GameMap {
|
||||
height(): number;
|
||||
map(): GameMap;
|
||||
miniMap(): GameMap;
|
||||
microMap(): GameMap;
|
||||
forEachTile(fn: (tile: TileRef) => void): void;
|
||||
// Zero-allocation neighbor iteration (cardinal only) to avoid creating arrays
|
||||
forEachNeighbor(tile: TileRef, callback: (neighbor: TileRef) => void): void;
|
||||
|
||||
@@ -49,14 +49,24 @@ export function createGame(
|
||||
nations: Nation[],
|
||||
gameMap: GameMap,
|
||||
miniGameMap: GameMap,
|
||||
microGameMap: GameMap,
|
||||
config: Config,
|
||||
): Game {
|
||||
// Precompute and cache water-component IDs once per map instance.
|
||||
getWaterComponentIds(gameMap);
|
||||
getWaterComponentIds(miniGameMap);
|
||||
getWaterComponentIds(microGameMap);
|
||||
|
||||
const stats = new StatsImpl();
|
||||
return new GameImpl(humans, nations, gameMap, miniGameMap, config, stats);
|
||||
return new GameImpl(
|
||||
humans,
|
||||
nations,
|
||||
gameMap,
|
||||
miniGameMap,
|
||||
microGameMap,
|
||||
config,
|
||||
stats,
|
||||
);
|
||||
}
|
||||
|
||||
export type CellString = string;
|
||||
@@ -95,6 +105,7 @@ export class GameImpl implements Game {
|
||||
private _nations: Nation[],
|
||||
private _map: GameMap,
|
||||
private miniGameMap: GameMap,
|
||||
private microGameMap: GameMap,
|
||||
private _config: Config,
|
||||
private _stats: Stats,
|
||||
) {
|
||||
@@ -205,6 +216,9 @@ export class GameImpl implements Game {
|
||||
miniMap(): GameMap {
|
||||
return this.miniGameMap;
|
||||
}
|
||||
microMap(): GameMap {
|
||||
return this.microGameMap;
|
||||
}
|
||||
|
||||
addUpdate(update: GameUpdate) {
|
||||
(this.updates[update.type] as GameUpdate[]).push(update);
|
||||
|
||||
@@ -6,6 +6,7 @@ export type TerrainMapData = {
|
||||
nations: Nation[];
|
||||
gameMap: GameMap;
|
||||
miniGameMap: GameMap;
|
||||
microGameMap: GameMap;
|
||||
};
|
||||
|
||||
const loadedMaps = new Map<GameMapType, TerrainMapData>();
|
||||
@@ -53,6 +54,13 @@ export async function loadTerrainMap(
|
||||
)
|
||||
: await genTerrainFromBin(manifest.map16x, await mapFiles.map16xBin());
|
||||
|
||||
// Always expose the 16x map (micro map) for coarse heuristics/corridors.
|
||||
// In compact games, miniMap already is 16x, so we can reuse it.
|
||||
const microMap =
|
||||
mapSize === GameMapSize.Normal
|
||||
? await genTerrainFromBin(manifest.map16x, await mapFiles.map16xBin())
|
||||
: miniMap;
|
||||
|
||||
if (mapSize === GameMapSize.Compact) {
|
||||
manifest.nations.forEach((nation) => {
|
||||
nation.coordinates = [
|
||||
@@ -66,6 +74,7 @@ export async function loadTerrainMap(
|
||||
nations: manifest.nations,
|
||||
gameMap: gameMap,
|
||||
miniGameMap: miniMap,
|
||||
microGameMap: microMap,
|
||||
};
|
||||
loadedMaps.set(map, result);
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { MultiSourceAnyTargetBFS } from "../../../src/core/pathfinding/MultiSourceAnyTargetBFS";
|
||||
|
||||
type TileRef = number;
|
||||
|
||||
function makeGridWaterMap(w: number, h: number, water: boolean[]) {
|
||||
const num = w * h;
|
||||
if (water.length !== num) throw new Error("bad water array");
|
||||
return {
|
||||
width: () => w,
|
||||
height: () => h,
|
||||
x: (ref: TileRef) => ref % w,
|
||||
y: (ref: TileRef) => Math.floor(ref / w),
|
||||
isWater: (ref: TileRef) => water[ref] === true,
|
||||
neighbors: (ref: TileRef) => {
|
||||
const out: TileRef[] = [];
|
||||
const x = ref % w;
|
||||
if (ref >= w) out.push(ref - w);
|
||||
if (ref < (h - 1) * w) out.push(ref + w);
|
||||
if (x !== 0) out.push(ref - 1);
|
||||
if (x !== w - 1) out.push(ref + 1);
|
||||
return out;
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe("MultiSourceAnyTargetBFS", () => {
|
||||
it("returns king-move (Chebyshev) diagonal routes when enabled", () => {
|
||||
// 3x3, all water.
|
||||
const gm = makeGridWaterMap(3, 3, new Array(9).fill(true));
|
||||
const bfs = new MultiSourceAnyTargetBFS(9);
|
||||
|
||||
const res = bfs.findWaterPath(gm, [0], [8], { kingMoves: true });
|
||||
expect(res).not.toBeNull();
|
||||
expect(res!.path).toEqual([0, 4, 8]);
|
||||
});
|
||||
|
||||
it("prevents diagonal corner cutting when enabled", () => {
|
||||
// 2x2:
|
||||
// S (water) X (land)
|
||||
// X (land) T (water)
|
||||
const gm = makeGridWaterMap(2, 2, [true, false, false, true]);
|
||||
const bfs = new MultiSourceAnyTargetBFS(4);
|
||||
|
||||
const blocked = bfs.findWaterPath(gm, [0], [3], {
|
||||
kingMoves: true,
|
||||
noCornerCutting: true,
|
||||
});
|
||||
expect(blocked).toBeNull();
|
||||
|
||||
const allowed = bfs.findWaterPath(gm, [0], [3], {
|
||||
kingMoves: true,
|
||||
noCornerCutting: false,
|
||||
});
|
||||
expect(allowed).not.toBeNull();
|
||||
expect(allowed!.path).toEqual([0, 3]);
|
||||
});
|
||||
});
|
||||
|
||||
+10
-1
@@ -39,6 +39,10 @@ export async function setup(
|
||||
currentDir,
|
||||
`../testdata/maps/${mapName}/map4x.bin`,
|
||||
);
|
||||
const microMapBinPath = path.join(
|
||||
currentDir,
|
||||
`../testdata/maps/${mapName}/map16x.bin`,
|
||||
);
|
||||
const manifestPath = path.join(
|
||||
currentDir,
|
||||
`../testdata/maps/${mapName}/manifest.json`,
|
||||
@@ -46,12 +50,17 @@ export async function setup(
|
||||
|
||||
const mapBinBuffer = fs.readFileSync(mapBinPath);
|
||||
const miniMapBinBuffer = fs.readFileSync(miniMapBinPath);
|
||||
const microMapBinBuffer = fs.readFileSync(microMapBinPath);
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(manifestPath, "utf8"),
|
||||
) satisfies MapManifest;
|
||||
|
||||
const gameMap = await genTerrainFromBin(manifest.map, mapBinBuffer);
|
||||
const miniGameMap = await genTerrainFromBin(manifest.map4x, miniMapBinBuffer);
|
||||
const microGameMap = await genTerrainFromBin(
|
||||
manifest.map16x,
|
||||
microMapBinBuffer,
|
||||
);
|
||||
|
||||
// Configure the game
|
||||
const serverConfig = new TestServerConfig();
|
||||
@@ -78,7 +87,7 @@ export async function setup(
|
||||
false,
|
||||
);
|
||||
|
||||
return createGame(humans, [], gameMap, miniGameMap, config);
|
||||
return createGame(humans, [], gameMap, miniGameMap, microGameMap, config);
|
||||
}
|
||||
|
||||
export function playerInfo(name: string, type: PlayerType): PlayerInfo {
|
||||
|
||||
Reference in New Issue
Block a user