mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 00:28:14 +00:00
Created mini map for a star search
This commit is contained in:
@@ -202,9 +202,10 @@
|
|||||||
* create async pathfinder DONE 11/29/2024
|
* create async pathfinder DONE 11/29/2024
|
||||||
* captured tradeships use async pathfinder DONE 11/29/2024
|
* captured tradeships use async pathfinder DONE 11/29/2024
|
||||||
* BUG: trade ships not building DONE 11/29/2024
|
* BUG: trade ships not building DONE 11/29/2024
|
||||||
|
* make mini map for path finding DONE 11/29/2024
|
||||||
* have NPCs build destroyers and battleships
|
* have NPCs build destroyers and battleships
|
||||||
* spread out calculate clusters
|
|
||||||
* add radiation from nuke
|
* add radiation from nuke
|
||||||
|
* spread out calculate clusters
|
||||||
* add defense post
|
* add defense post
|
||||||
* NPC has relations
|
* NPC has relations
|
||||||
* only show units you can build in the build menu
|
* only show units you can build in the build menu
|
||||||
|
|||||||
@@ -143,13 +143,17 @@ export class UnitLayer implements Layer {
|
|||||||
this.clearCell(t.cell());
|
this.clearCell(t.cell());
|
||||||
});
|
});
|
||||||
if (event.unit.isActive()) {
|
if (event.unit.isActive()) {
|
||||||
bfs(event.unit.tile(), dist(event.unit.tile(), 4)).forEach(
|
try {
|
||||||
t => {
|
bfs(event.unit.tile(), dist(event.unit.tile(), 4)).forEach(
|
||||||
if (trail.has(t)) {
|
t => {
|
||||||
this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 150);
|
if (trail.has(t)) {
|
||||||
|
this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 150);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
);
|
} catch {
|
||||||
|
console.log('uh oh')
|
||||||
|
}
|
||||||
bfs(event.unit.tile(), dist(event.unit.tile(), 2))
|
bfs(event.unit.tile(), dist(event.unit.tile(), 2))
|
||||||
.forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.unit.owner().info()), 255));
|
.forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.unit.owner().info()), 255));
|
||||||
bfs(event.unit.tile(), dist(event.unit.tile(), 1))
|
bfs(event.unit.tile(), dist(event.unit.tile(), 1))
|
||||||
|
|||||||
@@ -96,30 +96,26 @@ export class TransportShipExecution implements Execution {
|
|||||||
}
|
}
|
||||||
this.lastMove = ticks
|
this.lastMove = ticks
|
||||||
|
|
||||||
|
|
||||||
if (this.boat.tile() == this.dst) {
|
|
||||||
if (this.dst.owner() == this.attacker) {
|
|
||||||
this.attacker.addTroops(this.troops)
|
|
||||||
this.boat.delete()
|
|
||||||
this.active = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.target.isPlayer() && this.attacker.isAlliedWith(this.target)) {
|
|
||||||
this.target.addTroops(this.troops)
|
|
||||||
} else {
|
|
||||||
this.attacker.conquer(this.dst)
|
|
||||||
this.mg.addExecution(
|
|
||||||
new AttackExecution(this.troops, this.attacker.id(), this.targetID, this.dst.cell(), null, false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
this.boat.delete()
|
|
||||||
this.active = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = this.pathFinder.nextTile(this.boat.tile(), this.dst)
|
const result = this.pathFinder.nextTile(this.boat.tile(), this.dst)
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case PathFindResultType.Completed:
|
case PathFindResultType.Completed:
|
||||||
|
if (this.dst.owner() == this.attacker) {
|
||||||
|
this.attacker.addTroops(this.troops)
|
||||||
|
this.boat.delete()
|
||||||
|
this.active = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.target.isPlayer() && this.attacker.isAlliedWith(this.target)) {
|
||||||
|
this.target.addTroops(this.troops)
|
||||||
|
} else {
|
||||||
|
this.attacker.conquer(this.dst)
|
||||||
|
this.mg.addExecution(
|
||||||
|
new AttackExecution(this.troops, this.attacker.id(), this.targetID, this.dst.cell(), null, false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.boat.delete()
|
||||||
|
this.active = false
|
||||||
|
return
|
||||||
case PathFindResultType.NextTile:
|
case PathFindResultType.NextTile:
|
||||||
this.boat.move(result.tile)
|
this.boat.move(result.tile)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ export class PlayerInfo {
|
|||||||
export interface TerrainMap {
|
export interface TerrainMap {
|
||||||
terrain(cell: Cell): TerrainTile
|
terrain(cell: Cell): TerrainTile
|
||||||
neighbors(terrainTile: TerrainTile): TerrainTile[]
|
neighbors(terrainTile: TerrainTile): TerrainTile[]
|
||||||
|
width(): number
|
||||||
|
height(): number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TerrainTile extends SearchNode {
|
export interface TerrainTile extends SearchNode {
|
||||||
|
|||||||
@@ -167,6 +167,33 @@ export async function loadTerrainMap(map: GameMap): Promise<TerrainMapImpl> {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function createMiniMap(tm: TerrainMap): TerrainMap {
|
||||||
|
// Create 2D array properly with correct dimensions
|
||||||
|
const miniMap: TerrainTileImpl[][] = Array(Math.floor(tm.width() / 2))
|
||||||
|
.fill(null)
|
||||||
|
.map(() => Array(Math.floor(tm.height() / 2)).fill(null));
|
||||||
|
|
||||||
|
for (let x = 0; x < tm.width(); x++) {
|
||||||
|
for (let y = 0; y < tm.height(); y++) {
|
||||||
|
const tile = tm.terrain(new Cell(x, y)) as TerrainTileImpl;
|
||||||
|
const miniX = Math.floor(x / 2);
|
||||||
|
const miniY = Math.floor(y / 2);
|
||||||
|
|
||||||
|
if (miniMap[miniX][miniY] == null || miniMap[miniX][miniY].terrainType() != TerrainType.Ocean) {
|
||||||
|
miniMap[miniX][miniY] = new TerrainTileImpl(tile.terrainType(), new Cell(miniX, miniY));
|
||||||
|
miniMap[miniX][miniY].shoreline = tile.shoreline;
|
||||||
|
miniMap[miniX][miniY].magnitude = tile.magnitude;
|
||||||
|
miniMap[miniX][miniY].ocean = tile.ocean;
|
||||||
|
miniMap[miniX][miniY].land = tile.land;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TerrainMapImpl(miniMap, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function logBinaryAsAscii(data: string, length: number = 8) {
|
function logBinaryAsAscii(data: string, length: number = 8) {
|
||||||
console.log('Binary data (1 = set bit, 0 = unset bit):');
|
console.log('Binary data (1 = set bit, 0 = unset bit):');
|
||||||
for (let i = 0; i < Math.min(length, data.length); i++) {
|
for (let i = 0; i < Math.min(length, data.length); i++) {
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export class UnitImpl implements MutableUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
move(tile: Tile): void {
|
move(tile: Tile): void {
|
||||||
|
if(tile == null) {
|
||||||
|
throw new Error("tile cannot be null")
|
||||||
|
}
|
||||||
const oldTile = this._tile;
|
const oldTile = this._tile;
|
||||||
this._tile = tile;
|
this._tile = tile;
|
||||||
this.g.fireUnitUpdateEvent(this, oldTile);
|
this.g.fireUnitUpdateEvent(this, oldTile);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// pathfinding.ts
|
// pathfinding.ts
|
||||||
import { Cell, GameMap, TerrainMap, TerrainTile, TerrainType } from "../game/Game";
|
import { Cell, GameMap, TerrainMap, TerrainTile, TerrainType } from "../game/Game";
|
||||||
import { loadTerrainMap } from "../game/TerrainMapLoader";
|
import { createMiniMap, loadTerrainMap } from "../game/TerrainMapLoader";
|
||||||
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
||||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
import { SerialAStar } from "../pathfinding/SerialAStar";
|
||||||
import { PathFindResultType, SearchNode } from "../pathfinding/AStar";
|
import { PathFindResultType, SearchNode } from "../pathfinding/AStar";
|
||||||
@@ -10,11 +10,16 @@ let searches = new PriorityQueue<Search>((a: Search, b: Search) => (a.deadline -
|
|||||||
let processingInterval: number | null = null;
|
let processingInterval: number | null = null;
|
||||||
let isProcessingSearch = false
|
let isProcessingSearch = false
|
||||||
|
|
||||||
|
interface Point {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
interface Search {
|
interface Search {
|
||||||
aStar: SerialAStar,
|
aStar: SerialAStar,
|
||||||
deadline: number
|
deadline: number
|
||||||
requestId: string
|
requestId: string,
|
||||||
|
end: Point
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SearchRequest {
|
interface SearchRequest {
|
||||||
@@ -22,8 +27,8 @@ interface SearchRequest {
|
|||||||
currentTick: number
|
currentTick: number
|
||||||
// duration in ticks
|
// duration in ticks
|
||||||
duration: number
|
duration: number
|
||||||
start: { x: number, y: number },
|
start: Point
|
||||||
end: { x: number, y: number }
|
end: Point
|
||||||
}
|
}
|
||||||
|
|
||||||
self.onmessage = (e) => {
|
self.onmessage = (e) => {
|
||||||
@@ -38,15 +43,16 @@ self.onmessage = (e) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function initializeMap(data: { gameMap: GameMap }) {
|
function initializeMap(data: { gameMap: GameMap }) {
|
||||||
terrainMapPromise = loadTerrainMap(data.gameMap)
|
terrainMapPromise = loadTerrainMap(data.gameMap).then(tm => createMiniMap(tm))
|
||||||
self.postMessage({ type: 'initialized' });
|
self.postMessage({ type: 'initialized' });
|
||||||
processingInterval = setInterval(computeSearches, .1) as unknown as number;
|
processingInterval = setInterval(computeSearches, .1) as unknown as number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPath(terrainMap: TerrainMap, req: SearchRequest) {
|
function findPath(terrainMap: TerrainMap, req: SearchRequest) {
|
||||||
|
console.log(`terrain map height: ${terrainMap.height()}`)
|
||||||
const aStar = new SerialAStar(
|
const aStar = new SerialAStar(
|
||||||
terrainMap.terrain(new Cell(req.start.x, req.start.y)),
|
terrainMap.terrain(new Cell(Math.floor(req.start.x / 2), Math.floor(req.start.y / 2))),
|
||||||
terrainMap.terrain(new Cell(req.end.x, req.end.y)),
|
terrainMap.terrain(new Cell(Math.floor(req.end.x / 2), Math.floor(req.end.y / 2))),
|
||||||
(sn: SearchNode) => (sn as TerrainTile).terrainType() == TerrainType.Ocean,
|
(sn: SearchNode) => (sn as TerrainTile).terrainType() == TerrainType.Ocean,
|
||||||
(sn: SearchNode): SearchNode[] => terrainMap.neighbors((sn as TerrainTile)),
|
(sn: SearchNode): SearchNode[] => terrainMap.neighbors((sn as TerrainTile)),
|
||||||
10_000,
|
10_000,
|
||||||
@@ -56,7 +62,8 @@ function findPath(terrainMap: TerrainMap, req: SearchRequest) {
|
|||||||
searches.enqueue({
|
searches.enqueue({
|
||||||
aStar: aStar,
|
aStar: aStar,
|
||||||
deadline: req.currentTick + req.duration,
|
deadline: req.currentTick + req.duration,
|
||||||
requestId: req.requestId
|
requestId: req.requestId,
|
||||||
|
end: req.end
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +82,12 @@ function computeSearches() {
|
|||||||
const search = searches.dequeue()
|
const search = searches.dequeue()
|
||||||
switch (search.aStar.compute()) {
|
switch (search.aStar.compute()) {
|
||||||
case PathFindResultType.Completed:
|
case PathFindResultType.Completed:
|
||||||
|
const path = upscalePath(search.aStar.reconstructPath().map(sn => ({ x: sn.cell().x, y: sn.cell().y })))
|
||||||
|
path.push(search.end)
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
type: 'pathFound',
|
type: 'pathFound',
|
||||||
requestId: search.requestId,
|
requestId: search.requestId,
|
||||||
path: search.aStar.reconstructPath().map(sn => ({ x: sn.cell().x, y: sn.cell().y }))
|
path: path
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -97,4 +106,45 @@ function computeSearches() {
|
|||||||
} finally {
|
} finally {
|
||||||
isProcessingSearch = false
|
isProcessingSearch = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function upscalePath(path: Point[], scaleFactor: number = 2): Point[] {
|
||||||
|
// Scale up each point
|
||||||
|
const scaledPath = path.map(point => ({
|
||||||
|
x: point.x * scaleFactor,
|
||||||
|
y: point.y * scaleFactor
|
||||||
|
}));
|
||||||
|
|
||||||
|
const smoothPath: Point[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < scaledPath.length - 1; i++) {
|
||||||
|
const current = scaledPath[i];
|
||||||
|
const next = scaledPath[i + 1];
|
||||||
|
|
||||||
|
// Add the current point
|
||||||
|
smoothPath.push(current);
|
||||||
|
|
||||||
|
// Always interpolate between scaled points
|
||||||
|
const dx = next.x - current.x;
|
||||||
|
const dy = next.y - current.y;
|
||||||
|
|
||||||
|
// Calculate number of steps needed
|
||||||
|
const distance = Math.max(Math.abs(dx), Math.abs(dy));
|
||||||
|
const steps = distance;
|
||||||
|
|
||||||
|
// Add intermediate points
|
||||||
|
for (let step = 1; step < steps; step++) {
|
||||||
|
smoothPath.push({
|
||||||
|
x: Math.round(current.x + (dx * step) / steps),
|
||||||
|
y: Math.round(current.y + (dy * step) / steps)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last point
|
||||||
|
if (scaledPath.length > 0) {
|
||||||
|
smoothPath.push(scaledPath[scaledPath.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return smoothPath;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user