From 2068e42982199e97313e8c32cd6b744c9d4668a9 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Tue, 14 Jan 2025 15:48:04 -0800 Subject: [PATCH] use GameMap for storage in worker thread --- src/client/ClientGameRunner.ts | 2 +- src/core/GameRunner.ts | 2 +- src/core/GameView.ts | 2 +- src/core/execution/WinCheckExecution.ts | 2 +- src/core/game/Game.ts | 4 +- src/core/game/GameImpl.ts | 97 +++++--------- src/core/game/PlayerImpl.ts | 3 +- src/core/game/TerraNulliusImpl.ts | 3 + src/core/game/TerrainMapLoader.ts | 18 +-- src/core/game/TileImpl.ts | 170 +++++++++++++----------- 10 files changed, 147 insertions(+), 156 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 2b097427a..31a9a194a 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -75,7 +75,7 @@ export async function createClientGame(lobbyConfig: LobbyConfig, gameConfig: Gam const terrainMap = await loadTerrainMap(gameConfig.gameMap); const worker = new WorkerClient(lobbyConfig.gameID, gameConfig) await worker.initialize() - const gameView = new GameView(worker, config, terrainMap.map) + const gameView = new GameView(worker, config, terrainMap.terrain) consolex.log('going to init path finder') diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index 0bdd73d63..d18ae9aae 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -14,7 +14,7 @@ import { GameUpdateViewData, packTileData } from "./GameView"; export async function createGameRunner(gameID: string, gameConfig: GameConfig, callBack: (gu: GameUpdateViewData) => void): Promise { const config = getConfig(gameConfig) const terrainMap = await loadTerrainMap(gameConfig.gameMap); - const game = createGame(terrainMap.gameMap, terrainMap.miniGameMap, terrainMap.map, terrainMap.miniMap, config) + const game = createGame(terrainMap.gameMap, terrainMap.miniGameMap, terrainMap.nationMap, config) const gr = new GameRunner(game as MutableGame, new Executor(game, gameID), callBack) gr.init() return gr diff --git a/src/core/GameView.ts b/src/core/GameView.ts index 58bb286da..b9a6c9ed0 100644 --- a/src/core/GameView.ts +++ b/src/core/GameView.ts @@ -4,7 +4,7 @@ import { Alliance, AllianceRequest, AllPlayers, Cell, DefenseBonus, EmojiMessage import { ClientID } from "./Schemas"; import { TerraNulliusImpl } from './game/TerraNulliusImpl'; import { WorkerClient } from './worker/WorkerClient'; -import { TileRef } from './game/GameMap'; +import { GameMap, TileRef } from './game/GameMap'; export class TileView { diff --git a/src/core/execution/WinCheckExecution.ts b/src/core/execution/WinCheckExecution.ts index 512ce4ddd..bd3f9372d 100644 --- a/src/core/execution/WinCheckExecution.ts +++ b/src/core/execution/WinCheckExecution.ts @@ -27,7 +27,7 @@ export class WinCheckExecution implements Execution { return } const max = sorted[0] - if (max.numTilesOwned() / this.mg.terrainMap().numLandTiles() * 100 > this.mg.config().percentageTilesOwnedToWin()) { + if (max.numTilesOwned() / this.mg.map().numLandTiles() * 100 > this.mg.config().percentageTilesOwnedToWin()) { this.mg.setWinner(max) this.active = false } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 21650f158..9895a80cd 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -203,7 +203,6 @@ export interface TerrainTile { cell(): Cell neighbors(): TerrainTile[] cost(): number - key(): TerrainTileKey } export interface DefenseBonus { @@ -260,6 +259,7 @@ export interface TerraNullius { isPlayer(): false id(): PlayerID // always zero, maybe make it TerraNulliusID? clientID(): ClientID + smallID(): number } export interface Player { @@ -376,8 +376,6 @@ export interface Game { displayMessage(message: string, type: MessageType, playerID: PlayerID | null): void units(...types: UnitType[]): Unit[] unitInfo(type: UnitType): UnitInfo - terrainMap(): TerrainMap - terrainMiniMap(): TerrainMap map(): GameMap miniMap(): GameMap diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 70256ca3e..efa2bb7fc 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -1,6 +1,6 @@ import { Config } from "../configuration/Config"; import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerID, PlayerInfo, Player, TerraNullius, Tile, Unit, MutableAllianceRequest, Alliance, Nation, UnitType, UnitInfo, TerrainMap, DefenseBonus, MutableTile, GameUpdate, GameUpdateType, AllPlayers, GameUpdates } from "./Game"; -import { TerrainMapImpl } from "./TerrainMapLoader"; +import { NationMap, TerrainMapImpl } from "./TerrainMapLoader"; import { PlayerImpl } from "./PlayerImpl"; import { TerraNulliusImpl } from "./TerraNulliusImpl"; import { TileImpl } from "./TileImpl"; @@ -10,11 +10,10 @@ import { ClientID, GameConfig } from "../Schemas"; import { MessageType } from './Game'; import { UnitImpl } from "./UnitImpl"; import { consolex } from "../Consolex"; -import { string } from "zod"; -import { GameMap } from "./GameMap"; +import { GameMap, TileRef } from "./GameMap"; -export function createGame(gameMap: GameMap, miniGameMap: GameMap, terrainMap: TerrainMapImpl, miniMap: TerrainMap, config: Config): Game { - return new GameImpl(terrainMap, miniMap, gameMap, miniGameMap, config) +export function createGame(gameMap: GameMap, miniGameMap: GameMap, nationMap: NationMap, config: Config): Game { + return new GameImpl(gameMap, miniGameMap, nationMap, config) } export type CellString = string @@ -24,7 +23,6 @@ export class GameImpl implements MutableGame { private unInitExecs: Execution[] = [] - _map: TileImpl[][] private nations_: Nation[] = [] @@ -32,7 +30,6 @@ export class GameImpl implements MutableGame { private execs: Execution[] = [] private _width: number private _height: number - private _numLandTiles: number _terraNullius: TerraNulliusImpl allianceRequests: AllianceRequestImpl[] = [] @@ -44,24 +41,15 @@ export class GameImpl implements MutableGame { private updates: GameUpdates = createGameUpdatesMap() constructor( - private _terrainMap: TerrainMapImpl, - private _miniMap: TerrainMap, private gameMap: GameMap, private miniGameMap: GameMap, + nationMap: NationMap, private _config: Config, ) { this._terraNullius = new TerraNulliusImpl() - this._width = _terrainMap.width(); - this._height = _terrainMap.height(); - this._map = new Array(this._width); - for (let x = 0; x < this._width; x++) { - this._map[x] = new Array(this._height); - for (let y = 0; y < this._height; y++) { - let cell = new Cell(x, y); - this._map[x][y] = new TileImpl(this, this._terraNullius, cell, _terrainMap); - } - } - this.nations_ = _terrainMap.nationMap.nations + this._width = gameMap.width(); + this._height = gameMap.height(); + this.nations_ = nationMap.nations .map(n => new Nation( n.name, new Cell(n.coordinates[0], n.coordinates[1]), @@ -91,21 +79,23 @@ export class GameImpl implements MutableGame { if (tile.hasOwner()) { throw Error(`cannot set fallout, tile ${tile} has owner`) } - ti._hasFallout = true + this.gameMap.setFallout(tile.ref(), true) this.addUpdate(ti.toUpdate()) } addTileDefenseBonus(tile: Tile, unit: Unit, amount: number): DefenseBonus { + // TODO!! const df = { unit: unit, tile: tile, amount: amount }; - (tile as TileImpl)._defenseBonuses.push(df) - this.addUpdate((tile as TileImpl).toUpdate()) + // (tile as TileImpl)._defenseBonuses.push(df) + // this.addUpdate((tile as TileImpl).toUpdate()) return df } removeTileDefenseBonus(bonus: DefenseBonus): void { - const t = bonus.tile as TileImpl - t._defenseBonuses = t._defenseBonuses.filter(db => db != bonus) - this.addUpdate(t.toUpdate()) + // TODO!! + // const t = bonus.tile as TileImpl + // t._defenseBonuses = t._defenseBonuses.filter(db => db != bonus) + // this.addUpdate(t.toUpdate()) } units(...types: UnitType[]): UnitImpl[] { @@ -305,7 +295,7 @@ export class GameImpl implements MutableGame { tile(cell: Cell): MutableTile { this.assertIsOnMap(cell) - return this._map[cell.x][cell.y] as MutableTile + return new TileImpl(this, this.gameMap.ref(cell.x, cell.y)) } isOnMap(cell: Cell): boolean { @@ -315,36 +305,25 @@ export class GameImpl implements MutableGame { && cell.y < this._height } + fromRef(ref: TileRef): Tile { + return new TileImpl(this, ref) + } + neighbors(tile: Tile): Tile[] { - const x = tile.cell().x - const y = tile.cell().y - const ns: TileImpl[] = [] - if (y > 0) { - ns.push(this._map[x][y - 1]) - } - if (y < this._height - 1) { - ns.push(this._map[x][y + 1]) - } - if (x > 0) { - ns.push(this._map[x - 1][y]) - } - if (x < this._width - 1) { - ns.push(this._map[x + 1][y]) - } - return ns + return this.gameMap.neighbors(tile.ref()).map(tr => new TileImpl(this, tr)) } neighborsWithDiag(tile: Tile): Tile[] { const x = tile.cell().x const y = tile.cell().y - const ns: TileImpl[] = [] + const ns: Tile[] = [] for (let dx = -1; dx <= 1; dx++) { for (let dy = -1; dy <= 1; dy++) { if (dx === 0 && dy === 0) continue // Skip the center tile const newX = x + dx const newY = y + dy if (newX >= 0 && newX < this._width && newY >= 0 && newY < this._height) { - ns.push(this._map[newX][newY]) + ns.push(this.fromRef(this.gameMap.ref(newX, newY))) } } } @@ -362,18 +341,18 @@ export class GameImpl implements MutableGame { throw Error(`cannot conquer water`) } const tileImpl = tile as TileImpl - let previousOwner = tileImpl._owner + let previousOwner = tileImpl.owner() as TerraNullius | PlayerImpl if (previousOwner.isPlayer()) { previousOwner._lastTileChange = this._ticks previousOwner._tiles.delete(tile.cell().toString()) previousOwner._borderTiles.delete(tileImpl) - tileImpl._isBorder = false + this.gameMap.setBorder(tileImpl.ref(), false) } - tileImpl._owner = owner + this.gameMap.setPlayerId(tileImpl.ref(), owner.smallID()) owner._tiles.set(tile.cell().toString(), tile) owner._lastTileChange = this._ticks this.updateBorders(tile) - tileImpl._hasFallout = false + this.gameMap.setFallout(tileImpl.ref(), false) this.addUpdate((tile as TileImpl).toUpdate()) } @@ -386,13 +365,13 @@ export class GameImpl implements MutableGame { } const tileImpl = tile as TileImpl - let previousOwner = tileImpl._owner as PlayerImpl + let previousOwner = tileImpl.owner() as PlayerImpl previousOwner._lastTileChange = this._ticks previousOwner._tiles.delete(tile.cell().toString()) previousOwner._borderTiles.delete(tileImpl) - tileImpl._isBorder = false + this.gameMap.setBorder(tileImpl.ref(), false) - tileImpl._owner = this._terraNullius + this.gameMap.setPlayerId(tileImpl.ref(), 0) this.updateBorders(tile) this.addUpdate( (tile as TileImpl).toUpdate() @@ -406,15 +385,15 @@ export class GameImpl implements MutableGame { for (const t of tiles) { if (!t.hasOwner()) { - t._isBorder = false + this.gameMap.setBorder(t.ref(), false) continue } if (this.isBorder(t)) { (t.owner() as PlayerImpl)._borderTiles.add(t); - t._isBorder = true + this.gameMap.setBorder(t.ref(), true) } else { (t.owner() as PlayerImpl)._borderTiles.delete(t); - t._isBorder = false + this.gameMap.setBorder(t.ref(), false) } // this.updates.push(t.toUpdate()) } @@ -506,14 +485,6 @@ export class GameImpl implements MutableGame { }) } - public terrainMap(): TerrainMapImpl { - return this._terrainMap - } - - public terrainMiniMap(): TerrainMap { - return this._miniMap - } - displayMessage(message: string, type: MessageType, playerID: PlayerID | null): void { let id = null if (playerID != null) { diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 58a2bd202..3538fb039 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -6,6 +6,7 @@ import { UnitImpl } from "./UnitImpl"; import { TileImpl } from "./TileImpl"; import { MessageType } from './Game'; import { renderTroops } from "../../client/Utils"; +import { TerraNulliusImpl } from "./TerraNulliusImpl"; interface Target { tick: Tick @@ -135,7 +136,7 @@ export class PlayerImpl implements MutablePlayer { for (const border of this.borderTiles()) { for (const neighbor of border.neighbors()) { if (neighbor.terrain().isLand() && neighbor.owner() != this) { - ns.add((neighbor as TileImpl)._owner); + ns.add(neighbor.owner() as PlayerImpl | TerraNulliusImpl); } } } diff --git a/src/core/game/TerraNulliusImpl.ts b/src/core/game/TerraNulliusImpl.ts index a37bbf3b7..f40a341b5 100644 --- a/src/core/game/TerraNulliusImpl.ts +++ b/src/core/game/TerraNulliusImpl.ts @@ -9,6 +9,9 @@ export class TerraNulliusImpl implements TerraNullius { constructor() { } + smallID(): number { + return 0 + } clientID(): ClientID { return "TERRA_NULLIUS_CLIENT_ID" } diff --git a/src/core/game/TerrainMapLoader.ts b/src/core/game/TerrainMapLoader.ts index c236255ea..23158f70d 100644 --- a/src/core/game/TerrainMapLoader.ts +++ b/src/core/game/TerrainMapLoader.ts @@ -3,7 +3,7 @@ import { Cell, GameMapType, TerrainMap, TerrainTile, TerrainType } from './Game' import { GameMap } from './GameMap'; import { terrainMapFileLoader } from './TerrainMapFileLoader'; -const loadedMaps = new Map() +const loadedMaps = new Map() export interface NationMap { name: string; @@ -149,20 +149,20 @@ export class TerrainMapImpl implements TerrainMap { } -export async function loadTerrainMap(map: GameMapType): Promise<{ map: TerrainMapImpl, miniMap: TerrainMapImpl, gameMap: GameMap, miniGameMap: GameMap }> { +export async function loadTerrainMap(map: GameMapType): Promise<{ nationMap: NationMap, gameMap: GameMap, miniGameMap: GameMap, terrain: TerrainMap }> { if (loadedMaps.has(map)) { return loadedMaps.get(map) } const mapFiles = await terrainMapFileLoader.getMapData(map) - const { terrain: mainMap, gameMap: mainGameMap } = await loadTerrainFromFile(mapFiles.mapBin) - mainMap.nationMap = mapFiles.nationMap - const { terrain: mini, gameMap: miniMap } = await loadTerrainFromFile(mapFiles.miniMapBin) - loadedMaps.set(map, { map: mainMap, miniMap: mini, gameMap: mainGameMap, miniGameMap: miniMap }) - return { map: mainMap, miniMap: mini, gameMap: mainGameMap, miniGameMap: miniMap } + const gameMap = await loadTerrainFromFile(mapFiles.mapBin) + const miniGameMap = await loadTerrainFromFile(mapFiles.miniMapBin) + const result = { nationMap: mapFiles.nationMap, gameMap: gameMap.map, miniGameMap: miniGameMap.map, terrain: gameMap.terrain } + loadedMaps.set(map, result) + return result } -export async function loadTerrainFromFile(fileData: string): Promise<{ terrain: TerrainMapImpl, gameMap: GameMap }> { +export async function loadTerrainFromFile(fileData: string): Promise<{ map: GameMap, terrain: TerrainMap }> { const width = (fileData.charCodeAt(1) << 8) | fileData.charCodeAt(0); const height = (fileData.charCodeAt(3) << 8) | fileData.charCodeAt(2); @@ -187,7 +187,7 @@ export async function loadTerrainFromFile(fileData: string): Promise<{ terrain: const gm = new GameMap(width, height, m.rawData, numLand) m._numLandTiles = numLand; - return { terrain: m, gameMap: gm }; + return { map: gm, terrain: m } } diff --git a/src/core/game/TileImpl.ts b/src/core/game/TileImpl.ts index f443e1927..469d147a3 100644 --- a/src/core/game/TileImpl.ts +++ b/src/core/game/TileImpl.ts @@ -1,51 +1,51 @@ -import { Tile, Cell, TerrainType, Player, TerraNullius, MutablePlayer, TerrainTile, DefenseBonus, MutableTile, TileUpdate, GameUpdateType } from "./Game"; +import { Tile, Cell, TerrainType, Player, TerraNullius, MutablePlayer, TerrainTile, DefenseBonus, MutableTile, TileUpdate, GameUpdateType, TerrainTileKey } from "./Game"; import { TerrainMapImpl, TerrainTileImpl } from "./TerrainMapLoader"; import { GameImpl } from "./GameImpl"; import { PlayerImpl } from "./PlayerImpl"; import { TerraNulliusImpl } from "./TerraNulliusImpl"; -import { TileRef } from "./GameMap"; +import { GameMap, TileRef } from "./GameMap"; export class TileImpl implements MutableTile { - public _isBorder = false; - private _neighbors: Tile[] = null; - - public _defenseBonuses: DefenseBonus[] = [] - - public _hasFallout = false constructor( private readonly gs: GameImpl, - public _owner: PlayerImpl | TerraNulliusImpl, - private readonly _cell: Cell, - private terrainMap: TerrainMapImpl + private ref_: TileRef ) { } + terrain(): TerrainTile { + return new TerrainRef(this.gs.map(), this.ref_) + } + + neighborsWrapped(): Tile[] { + // TODO: implement! + return this.neighbors() + } ref(): TileRef { - return this.gs.map().ref(this._cell.x, this._cell.y) + return this.ref_ } toUpdate(): TileUpdate { return { type: GameUpdateType.Tile, pos: { - x: this._cell.x, - y: this._cell.y + x: this.x(), + y: this.y() }, - ownerID: this._owner.isPlayer() ? this._owner.smallID() : 0, - hasFallout: this._hasFallout, + ownerID: this.owner().smallID(), + hasFallout: this.hasFallout(), hasDefenseBonus: this.hasDefenseBonus(), isBorder: this.isBorder(), } } hasFallout(): boolean { - return this._hasFallout + return this.gs.map().hasFallout(this.ref_) } type(): TerrainType { - return this.terrainMap.terrain(this._cell)._type + return this.gs.map().getTerrainType(this.ref_) } hasDefenseBonus(): boolean { @@ -53,54 +53,23 @@ export class TileImpl implements MutableTile { } defenseBonus(player: Player): number { - if (this.owner() == player) { - throw Error(`cannot get defense bonus of tile already owned by player, ${player}`) - } - let bonusAmount = 0 - for (const bonus of this._defenseBonuses) { - if (bonus.unit.owner() != player) { - bonusAmount += bonus.amount - } - } - return Math.max(bonusAmount, 1) + // TODO! + return 0 + // if (this.owner() == player) { + // throw Error(`cannot get defense bonus of tile already owned by player, ${player}`) + // } + // let bonusAmount = 0 + // for (const bonus of this._defenseBonuses) { + // if (bonus.unit.owner() != player) { + // bonusAmount += bonus.amount + // } + // } + // return Math.max(bonusAmount, 1) } defenseBonuses(): DefenseBonus[] { - return this._defenseBonuses - } - - neighborsWrapped(): Tile[] { - const x = this._cell.x; - const y = this._cell.y; - const ns: Tile[] = []; - - // Check top neighbor - if (y > 0) { - ns.push(this.gs._map[x][y - 1]); - } - - // Check bottom neighbor - if (y < this.gs.height() - 1) { - ns.push(this.gs._map[x][y + 1]); - } - - // Check left neighbor (wrap around) - if (x > 0) { - ns.push(this.gs._map[x - 1][y]); - } else { - ns.push(this.gs._map[this.gs.width() - 1][y]); - } - - // Check right neighbor (wrap around) - if (x < this.gs.width() - 1) { - ns.push(this.gs._map[x + 1][y]); - } else { - ns.push(this.gs._map[0][y]); - } - return ns; - } - terrain(): TerrainTile { - return this.terrainMap.terrain(this._cell) + // TODO! + return [] } borders(other: Player | TerraNullius): boolean { @@ -112,25 +81,74 @@ export class TileImpl implements MutableTile { return false; } - hasOwner(): boolean { return this._owner != this.gs._terraNullius; } - owner(): MutablePlayer | TerraNullius { return this._owner; } - isBorder(): boolean { return this._isBorder; } - cell(): Cell { return this._cell; } + hasOwner(): boolean { return this.owner().smallID() != 0 } + owner(): MutablePlayer | TerraNullius { + const ownerID = this.gs.map().playerId(this.ref_) + if (ownerID == 0) { + return this.gs.terraNullius() + } + } + isBorder(): boolean { return this.gs.map().isBorder(this.ref_); } + + cell(): Cell { return new Cell(this.x(), this.y()); } + x(): number { - return this._cell.x + return this.gs.map().x(this.ref_) } y(): number { - return this._cell.y + return this.gs.map().y(this.ref_) } neighbors(): Tile[] { - if (this._neighbors == null) { - this._neighbors = this.gs.neighbors(this); - } - return this._neighbors; + return this.gs.neighbors(this) } - cost(): number { - return this.terrain().magnitude() < 10 ? 2 : 1 - }; } + +export class TerrainRef implements TerrainTile { + + constructor(private map: GameMap, private ref: TileRef) { } + + isLand(): boolean { + return this.map.isLand(this.ref) + } + isShore(): boolean { + return this.map.isShore(this.ref) + } + + isOceanShore(): boolean { + return this.isShore() && this.neighbors().filter(n => n.isOcean()).length > 0; + } + + isWater(): boolean { + return !this.map.isLand(this.ref) + } + isShorelineWater(): boolean { + return this.isWater() && this.isShore() + } + isOcean(): boolean { + return this.map.isOcean(this.ref) + } + isLake(): boolean { + return this.isWater() && !this.isOcean() + } + type(): TerrainType { + return this.map.getTerrainType(this.ref) + } + magnitude(): number { + return this.map.magnitude(this.ref) + } + equals(other: TerrainTile): boolean { + return this.ref == (other as TerrainRef).ref + } + cell(): Cell { + return this.map.cell(this.ref) + } + neighbors(): TerrainTile[] { + return this.map.neighbors(this.ref).map(tr => new TerrainRef(this.map, tr)) + } + cost(): number { + return this.map.cost(this.ref) + } + +} \ No newline at end of file