From 2249771555cfb432f1b9c7573cd92c934a30dc2a Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 18 Aug 2024 19:54:23 -0700 Subject: [PATCH] add Cloudflare analytics improve a* --- TODO.txt | 6 +- src/client/Client.ts | 2 +- src/client/index.html | 7 +- src/core/configuration/DefaultConfig.ts | 3 +- src/core/configuration/DevConfig.ts | 19 ++++- src/core/execution/BoatAttackExecution.ts | 95 +++++++++++++++-------- src/global.d.ts | 2 +- 7 files changed, 92 insertions(+), 42 deletions(-) diff --git a/TODO.txt b/TODO.txt index 24cd5650e..c01df192e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -28,11 +28,11 @@ * Create separate game config dev vs prod DONE 8/17/2024 * improve front page, only one game at a time every 30s DONE 8/17/2024 * create dev server DONE 8/18/2024 -* BUG: boats not going to destination, coast not being recognized -* BUG: boats freeze game on path calculation +* BUG: boats freeze game on path calculation DONE 8/18/2024 * better algorithm for name render placement * make boats larger * have boats not get close to shore +* use analyitics * Allow boats to attack TerraNullius * make coasts look better * add shader to dim border @@ -41,3 +41,5 @@ * BUG: ocean is considered TerraNullius * on websocket connect server only send missing turns not all turns * BUG: fix hotreload (priority queue breaks it) +* BUG: boat doesn't work if on lake on other player not on lake +* PERF: use hierarchical a* search for boats \ No newline at end of file diff --git a/src/client/Client.ts b/src/client/Client.ts index 1f87691bf..fdc578f29 100644 --- a/src/client/Client.ts +++ b/src/client/Client.ts @@ -7,7 +7,7 @@ import {GameID, Lobby, ServerMessage, ServerMessageSchema} from "../core/Schemas import {loadTerrainMap} from "../core/TerrainMapLoader"; import {ClientGame, createClientGame} from "./ClientGame"; import {v4 as uuidv4} from 'uuid'; -import backgroundImage from '../../resources/images/World.png'; +import backgroundImage from '../../resources/images/empty_map.png'; // import WebSocket from 'ws'; diff --git a/src/client/index.html b/src/client/index.html index 1c366dd75..3427a27e0 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -11,7 +11,7 @@ margin: 0; padding: 0; min-height: 100vh; - background-color: #404040; + background-color: #15151500; /* Dark grey background */ background-image: url('/resources/images/watercolor_worldmap.jpg'); background-size: cover; @@ -130,8 +130,9 @@ - - + + \ No newline at end of file diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 82333b4dc..c87eb7d2e 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -27,7 +27,7 @@ export class DefaultConfig implements Config { theme(): Theme {return pastelTheme;} } -export const defaultPlayerConfig = new class implements PlayerConfig { +export class DefaultPlayerConfig implements PlayerConfig { attackLogic(attacker: Player, defender: Player | TerraNullius, tileToConquer: Tile): {attackerTroopLoss: number; defenderTroopLoss: number; tilesPerTickUsed: number} { if (defender.isPlayer()) { @@ -80,3 +80,4 @@ export const defaultPlayerConfig = new class implements PlayerConfig { } export const defaultConfig = new DefaultConfig() +export const defaultPlayerConfig = new DefaultPlayerConfig() \ No newline at end of file diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 5dda7df8c..8a108525f 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -1,4 +1,6 @@ -import {DefaultConfig} from "./DefaultConfig"; +import {PlayerInfo} from "../Game"; +import {PlayerConfig} from "./Config"; +import {DefaultConfig, DefaultPlayerConfig, defaultPlayerConfig} from "./DefaultConfig"; export const devConfig = new class extends DefaultConfig { gameCreationRate(): number { @@ -7,4 +9,19 @@ export const devConfig = new class extends DefaultConfig { lobbyLifetime(): number { return 10 * 1000 } + turnIntervalMs(): number { + return 100 + } + player(): PlayerConfig { + return devPlayerConfig + } +} + +export const devPlayerConfig = new class extends DefaultPlayerConfig { + startTroops(playerInfo: PlayerInfo): number { + if (playerInfo.isBot) { + return 10 + } + return 5000 + } } \ No newline at end of file diff --git a/src/core/execution/BoatAttackExecution.ts b/src/core/execution/BoatAttackExecution.ts index a855deb16..fcb7f753d 100644 --- a/src/core/execution/BoatAttackExecution.ts +++ b/src/core/execution/BoatAttackExecution.ts @@ -1,5 +1,5 @@ import PriorityQueue from "priority-queue-typescript"; -import {Boat, Cell, Execution, MutableBoat, MutableGame, MutablePlayer, Player, PlayerID, Tile} from "../Game"; +import {Boat, Cell, Execution, MutableBoat, MutableGame, MutablePlayer, Player, PlayerID, TerrainTypes, Tile} from "../Game"; import {manhattanDist} from "../Util"; import {AttackExecution} from "./AttackExecution"; import {Config, PlayerConfig} from "../configuration/Config"; @@ -26,6 +26,11 @@ export class BoatAttackExecution implements Execution { private boat: MutableBoat + private aStarPre: AStar + private aStarComplete: AStar + + private finalPath = false + constructor( private attackerID: PlayerID, private targetID: PlayerID | null, @@ -54,8 +59,9 @@ export class BoatAttackExecution implements Execution { this.active = false return } - - this.path = this.computePath(this.src, this.dst) + this.aStarPre = new AStar(this.src, this.dst) + this.aStarPre.compute(10000) + this.path = this.aStarPre.reconstructPath() if (this.path != null) { console.log(`got path ${this.path.map(t => t.cell().toString())}`) this.boat = this.attacker.addBoat(1000, this.src, this.target) @@ -63,6 +69,7 @@ export class BoatAttackExecution implements Execution { console.log('got null path') this.active = false } + this.aStarComplete = new AStar(this.path[this.path.length - 1], this.dst) } tick(ticks: number) { @@ -73,9 +80,16 @@ export class BoatAttackExecution implements Execution { return } this.lastMove = ticks - this.currTileIndex++ + + if (!this.finalPath && this.aStarComplete.compute(10000)) { + this.path.push(...this.aStarComplete.reconstructPath()) + this.finalPath = true + } if (this.currTileIndex >= this.path.length) { + if (!this.finalPath) { + return + } if (this.dst.owner() == this.attacker) { this.attacker.addTroops(this.troops) this.active = false @@ -89,6 +103,7 @@ export class BoatAttackExecution implements Execution { const nextTile = this.path[this.currTileIndex] this.boat.move(nextTile) + this.currTileIndex++ } owner(): MutablePlayer { @@ -111,45 +126,60 @@ export class BoatAttackExecution implements Execution { return currentDistance < closestDistance ? current : closest; }); } +} - private computePath(src: Tile, dst: Tile): Tile[] { - if (!src.onShore() || !dst.onShore()) { - return null; // Both source and destination must be on water - } +export class AStar { + private openSet: PriorityQueue<{tile: Tile, fScore: number}>; + private cameFrom: Map; + private gScore: Map; + private current: Tile | null; + public completed: boolean; - const openSet = new PriorityQueue<{tile: Tile, fScore: number}>( - 11, + constructor(private src: Tile, private dst: Tile) { + this.openSet = new PriorityQueue<{tile: Tile, fScore: number}>( + 500, (a, b) => a.fScore - b.fScore ); - const cameFrom = new Map(); - const gScore = new Map(); + this.cameFrom = new Map(); + this.gScore = new Map(); + this.current = null; + this.completed = false; - gScore.set(src, 0); - openSet.add({tile: src, fScore: this.heuristic(src, dst)}); + this.gScore.set(src, 0); + this.openSet.add({tile: src, fScore: this.heuristic(src, dst)}); + } - while (!openSet.empty()) { - const current = openSet.poll()!.tile; + compute(iterations: number): boolean { + if (this.completed) return true; - if (current === dst) { - return this.reconstructPath(cameFrom, current); + while (!this.openSet.empty()) { + iterations-- + this.current = this.openSet.poll()!.tile; + if (iterations <= 0) { + return false } - for (const neighbor of current.neighbors()) { - if (!neighbor.onShore()) continue; // Skip non-water tiles + if (this.current === this.dst) { + this.completed = true; + return true; + } - const tentativeGScore = gScore.get(current)! + 1; // Assuming uniform cost + for (const neighbor of this.current.neighbors()) { + if (neighbor != this.dst && neighbor.terrain() != TerrainTypes.Water) continue; // Skip non-water tiles - if (!gScore.has(neighbor) || tentativeGScore < gScore.get(neighbor)!) { - cameFrom.set(neighbor, current); - gScore.set(neighbor, tentativeGScore); - const fScore = tentativeGScore + this.heuristic(neighbor, dst); + const tentativeGScore = this.gScore.get(this.current)! + 1; // Assuming uniform cost - openSet.add({tile: neighbor, fScore: fScore}); + if (!this.gScore.has(neighbor) || tentativeGScore < this.gScore.get(neighbor)!) { + this.cameFrom.set(neighbor, this.current); + this.gScore.set(neighbor, tentativeGScore); + const fScore = tentativeGScore + this.heuristic(neighbor, this.dst); + + this.openSet.add({tile: neighbor, fScore: fScore}); } } } - return null; // No path found + return this.completed; } private heuristic(a: Tile, b: Tile): number { @@ -157,13 +187,12 @@ export class BoatAttackExecution implements Execution { return Math.abs(a.cell().x - b.cell().x) + Math.abs(a.cell().y - b.cell().y); } - private reconstructPath(cameFrom: Map, current: Tile): Tile[] { - const path = [current]; - while (cameFrom.has(current)) { - current = cameFrom.get(current)!; - path.unshift(current); + public reconstructPath(): Tile[] { + const path = [this.current!]; + while (this.cameFrom.has(this.current!)) { + this.current = this.cameFrom.get(this.current!)!; + path.unshift(this.current); } return path; } - } \ No newline at end of file diff --git a/src/global.d.ts b/src/global.d.ts index 648a061c2..5e5acff41 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,5 +1,5 @@ declare module '*.png' { - const content: string; + const content: string; export default content; } declare module '*.jpg' {