diff --git a/src/core/GameView.ts b/src/core/GameView.ts index f30b17831..f0de9c088 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 { GameMap, TileRef } from './game/GameMap'; +import { GameMapImpl, TileRef } from './game/GameMap'; export class TileView { diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index 3001de81f..4597e01e9 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -400,7 +400,7 @@ export class FakeHumanExecution implements Execution { } if (oceanShore == null) { - oceanShore = Array.from(this.player.borderTileRefs()).filter(t => this.mg.M.isOceanShore(t)).map(tr => this.mg.fromRef(tr)) + oceanShore = Array.from(this.player.borderTileRefs()).filter(t => this.mg.isOceanShore(t)).map(tr => this.mg.fromRef(tr)) } if (oceanShore.length == 0) { return diff --git a/src/core/execution/PlayerExecution.ts b/src/core/execution/PlayerExecution.ts index 13fb0934b..8207cd3ed 100644 --- a/src/core/execution/PlayerExecution.ts +++ b/src/core/execution/PlayerExecution.ts @@ -114,12 +114,12 @@ export class PlayerExecution implements Execution { private surroundedBySamePlayer(cluster: Set): false | Player { const enemies = new Set() for (const ref of cluster) { - if (this.mg.M.isOceanShore(ref) || this.mg.M.neighbors(ref).some(n => !this.mg.M.hasOwner(n))) { + if (this.mg.isOceanShore(ref) || this.mg.neighbors(ref).some(n => !this.mg.hasOwner(n))) { return false } - this.mg.M.neighbors(ref) - .filter(n => this.mg.M.ownerID(n) != this.player.smallID()) - .forEach(p => enemies.add(this.mg.M.ownerID(p))) + this.mg.neighbors(ref) + .filter(n => this.mg.ownerID(n) != this.player.smallID()) + .forEach(p => enemies.add(this.mg.ownerID(p))) if (enemies.size != 1) { return false } @@ -133,11 +133,11 @@ export class PlayerExecution implements Execution { private isSurrounded(cluster: Set): boolean { let enemyTiles = new Set() for (const tr of cluster) { - if (this.mg.M.isOceanShore(tr)) { + if (this.mg.isOceanShore(tr)) { return false } - this.mg.M.neighbors(tr) - .filter(n => this.mg.M.ownerID(n) != this.player.smallID()) + this.mg.neighbors(tr) + .filter(n => this.mg.ownerID(n) != this.player.smallID()) .forEach(n => enemyTiles.add(n)) } if (enemyTiles.size == 0) { @@ -152,16 +152,16 @@ export class PlayerExecution implements Execution { const arr = Array.from(cluster) const mode = getMode( arr. - flatMap(t => this.mg.M.neighbors(t)) - .filter(t => this.mg.M.ownerID(t) != this.player.smallID()) - .map(t => this.mg.M.ownerID(t)) + flatMap(t => this.mg.neighbors(t)) + .filter(t => this.mg.ownerID(t) != this.player.smallID()) + .map(t => this.mg.ownerID(t)) ) if (!this.mg.playerBySmallID(mode).isPlayer()) { consolex.warn('mode is not found') return } const firstTile = arr[0] - const filter = (n: Tile): boolean => n.owner().smallID() == this.mg.M.ownerID(firstTile) + const filter = (n: Tile): boolean => n.owner().smallID() == this.mg.ownerID(firstTile) const tiles = bfs(this.mg.fromRef(firstTile), filter) const modePlayer = this.mg.playerBySmallID(mode) diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index ca66c9e8c..256d2fed5 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -1,7 +1,7 @@ import { Config } from "../configuration/Config" import { GameEvent } from "../EventBus" import { ClientID, GameConfig, GameID } from "../Schemas" -import { GameMap, TileRef } from "./GameMap" +import { GameMap, GameMapImpl, TileRef } from "./GameMap" export type PlayerID = string export type Tick = number @@ -354,8 +354,7 @@ export interface MutablePlayer extends Player { toUpdate(): PlayerUpdate } -export interface Game { - M: GameMap +export interface Game extends GameMap { // Throws exception is player not found player(id: PlayerID): Player playerByClientID(id: ClientID): Player | null @@ -363,7 +362,6 @@ export interface Game { players(): Player[] tile(cell: Cell): Tile isOnMap(cell: Cell): boolean - neighbors(cell: Cell | Tile): Tile[] width(): number height(): number forEachTile(fn: (tile: Tile) => void): void @@ -380,8 +378,8 @@ export interface Game { unitInfo(type: UnitType): UnitInfo playerBySmallID(id: number): Player | TerraNullius fromRef(ref: TileRef): Tile - map(): GameMap - miniMap(): GameMap + map(): GameMapImpl + miniMap(): GameMapImpl } export interface MutableGame extends Game { diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index a170fe780..d27e15a9c 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -10,9 +10,9 @@ import { ClientID, GameConfig } from "../Schemas"; import { MessageType } from './Game'; import { UnitImpl } from "./UnitImpl"; import { consolex } from "../Consolex"; -import { GameMap, TileRef } from "./GameMap"; +import { GameMapImpl, TileRef } from "./GameMap"; -export function createGame(gameMap: GameMap, miniGameMap: GameMap, nationMap: NationMap, config: Config): Game { +export function createGame(gameMap: GameMapImpl, miniGameMap: GameMapImpl, nationMap: NationMap, config: Config): Game { return new GameImpl(gameMap, miniGameMap, nationMap, config) } @@ -43,14 +43,14 @@ export class GameImpl implements MutableGame { private updates: GameUpdates = createGameUpdatesMap() constructor( - public readonly M: GameMap, - private miniGameMap: GameMap, + private _map: GameMapImpl, + private miniGameMap: GameMapImpl, nationMap: NationMap, private _config: Config, ) { this._terraNullius = new TerraNulliusImpl() - this._width = M.width(); - this._height = M.height(); + this._width = _map.width(); + this._height = _map.height(); this.nations_ = nationMap.nations .map(n => new Nation( n.name, @@ -64,10 +64,10 @@ export class GameImpl implements MutableGame { } return this._playersBySmallID[id - 1] } - map(): GameMap { - return this.M + map(): GameMapImpl { + return this._map } - miniMap(): GameMap { + miniMap(): GameMapImpl { return this.miniGameMap } @@ -87,7 +87,7 @@ export class GameImpl implements MutableGame { if (tile.hasOwner()) { throw Error(`cannot set fallout, tile ${tile} has owner`) } - this.M.setFallout(tile.ref(), true) + this._map.setFallout(tile.ref(), true) this.addUpdate(ti.toUpdate()) } @@ -257,14 +257,6 @@ export class GameImpl implements MutableGame { this.unInitExecs = this.unInitExecs.filter(execution => execution !== exec) } - width(): number { - return this._width - } - - height(): number { - return this._height - } - forEachTile(fn: (tile: Tile) => void): void { for (let x = 0; x < this._width; x++) { for (let y = 0; y < this._height; y++) { @@ -304,7 +296,7 @@ export class GameImpl implements MutableGame { tile(cell: Cell): MutableTile { this.assertIsOnMap(cell) - return new TileImpl(this, this.M.ref(cell.x, cell.y)) + return new TileImpl(this, this._map.ref(cell.x, cell.y)) } isOnMap(cell: Cell): boolean { @@ -318,9 +310,6 @@ export class GameImpl implements MutableGame { return new TileImpl(this, ref) } - neighbors(tile: Tile): Tile[] { - return this.M.neighbors(tile.ref()).map(tr => new TileImpl(this, tr)) - } neighborsWithDiag(tile: Tile): Tile[] { const x = tile.cell().x @@ -332,7 +321,7 @@ export class GameImpl implements MutableGame { const newX = x + dx const newY = y + dy if (newX >= 0 && newX < this._width && newY >= 0 && newY < this._height) { - ns.push(this.fromRef(this.M.ref(newX, newY))) + ns.push(this.fromRef(this._map.ref(newX, newY))) } } } @@ -355,13 +344,13 @@ export class GameImpl implements MutableGame { previousOwner._lastTileChange = this._ticks previousOwner._tiles.delete(tile.cell().toString()) previousOwner._borderTiles.delete(tileImpl.ref()) - this.M.setBorder(tileImpl.ref(), false) + this._map.setBorder(tileImpl.ref(), false) } - this.M.setOwnerID(tileImpl.ref(), owner.smallID()) + this._map.setOwnerID(tileImpl.ref(), owner.smallID()) owner._tiles.set(tile.cell().toString(), tile) owner._lastTileChange = this._ticks this.updateBorders(tile) - this.M.setFallout(tileImpl.ref(), false) + this._map.setFallout(tileImpl.ref(), false) this.addUpdate((tile as TileImpl).toUpdate()) } @@ -378,9 +367,9 @@ export class GameImpl implements MutableGame { previousOwner._lastTileChange = this._ticks previousOwner._tiles.delete(tile.cell().toString()) previousOwner._borderTiles.delete(tileImpl.ref()) - this.M.setBorder(tileImpl.ref(), false) + this._map.setBorder(tileImpl.ref(), false) - this.M.setOwnerID(tileImpl.ref(), 0) + this._map.setOwnerID(tileImpl.ref(), 0) this.updateBorders(tile) this.addUpdate( (tile as TileImpl).toUpdate() @@ -394,21 +383,21 @@ export class GameImpl implements MutableGame { for (const t of tiles) { if (!t.hasOwner()) { - this.M.setBorder(t.ref(), false) + this._map.setBorder(t.ref(), false) continue } - if (this.isBorder(t)) { + if (this.calcIsBorder(t)) { (t.owner() as PlayerImpl)._borderTiles.add(t.ref()); - this.M.setBorder(t.ref(), true) + this._map.setBorder(t.ref(), true) } else { (t.owner() as PlayerImpl)._borderTiles.delete(t.ref()); - this.M.setBorder(t.ref(), false) + this._map.setBorder(t.ref(), false) } // this.updates.push(t.toUpdate()) } } - isBorder(tile: Tile): boolean { + private calcIsBorder(tile: Tile): boolean { if (!tile.hasOwner()) { return false } @@ -506,6 +495,28 @@ export class GameImpl implements MutableGame { playerID: id }) } + + ref(x: number, y: number): TileRef { return this._map.ref(x, y) } + x(ref: TileRef): number { return this._map.x(ref) } + y(ref: TileRef): number { return this._map.y(ref) } + cell(ref: TileRef): Cell { return this._map.cell(ref) } + width(): number { return this._map.width() } + height(): number { return this._map.height() } + numLandTiles(): number { return this._map.numLandTiles() } + isValidCoord(x: number, y: number): boolean { return this._map.isValidCoord(x, y) } + isLand(ref: TileRef): boolean { return this._map.isLake(ref) } + isOceanShore(ref: TileRef): boolean { return this._map.isOceanShore(ref) } + isOcean(ref: TileRef): boolean { return this._map.isOcean(ref) } + isShoreline(ref: TileRef): boolean { return this._map.isShoreline(ref) } + magnitude(ref: TileRef): number { return this._map.magnitude(ref) } + ownerID(ref: TileRef): number { return this._map.ownerID(ref) } + hasOwner(ref: TileRef): boolean { return this._map.hasOwner(ref) } + setOwnerID(ref: TileRef, playerId: number): void { return this._map.setOwnerID(ref, playerId) } + hasFallout(ref: TileRef): boolean { return this._map.hasFallout(ref) } + setFallout(ref: TileRef, value: boolean): void { return this._map.setFallout(ref, value) } + isBorder(ref: TileRef): boolean { return this._map.isBorder(ref) } + setBorder(ref: TileRef, value: boolean): void { return this._map.setBorder(ref, value) } + neighbors(ref: TileRef): TileRef[] { return this._map.neighbors(ref) } } // Or a more dynamic approach that will catch new enum values: diff --git a/src/core/game/GameMap.ts b/src/core/game/GameMap.ts index f8cca188f..aaabf482a 100644 --- a/src/core/game/GameMap.ts +++ b/src/core/game/GameMap.ts @@ -2,7 +2,36 @@ import { Cell, TerrainType } from "./Game"; export type TileRef = number; -export class GameMap { +export interface GameMap { + ref(x: number, y: number): TileRef + + x(ref: TileRef): number + y(ref: TileRef): number + cell(ref: TileRef): Cell + width(): number + height(): number + numLandTiles(): number + + isValidCoord(x: number, y: number): boolean + // Terrain getters (immutable) + isLand(ref: TileRef): boolean + isOceanShore(ref: TileRef): boolean + isOcean(ref: TileRef): boolean + isShoreline(ref: TileRef): boolean + magnitude(ref: TileRef): number + // State getters and setters (mutable) + ownerID(ref: TileRef): number + hasOwner(ref: TileRef): boolean + + setOwnerID(ref: TileRef, playerId: number): void + hasFallout(ref: TileRef): boolean + setFallout(ref: TileRef, value: boolean): void + isBorder(ref: TileRef): boolean + setBorder(ref: TileRef, value: boolean): void + neighbors(ref: TileRef): TileRef[] +} + +export class GameMapImpl implements GameMap { private readonly terrain: Uint8Array; // Immutable terrain data private readonly state: Uint16Array; // Mutable game state private readonly width_: number; @@ -62,7 +91,7 @@ export class GameMap { // Terrain getters (immutable) isLand(ref: TileRef): boolean { - return Boolean(this.terrain[ref] & (1 << GameMap.IS_LAND_BIT)); + return Boolean(this.terrain[ref] & (1 << GameMapImpl.IS_LAND_BIT)); } isOceanShore(ref: TileRef): boolean { @@ -70,20 +99,20 @@ export class GameMap { } isOcean(ref: TileRef): boolean { - return Boolean(this.terrain[ref] & (1 << GameMap.OCEAN_BIT)); + return Boolean(this.terrain[ref] & (1 << GameMapImpl.OCEAN_BIT)); } isShoreline(ref: TileRef): boolean { - return Boolean(this.terrain[ref] & (1 << GameMap.SHORELINE_BIT)); + return Boolean(this.terrain[ref] & (1 << GameMapImpl.SHORELINE_BIT)); } magnitude(ref: TileRef): number { - return this.terrain[ref] & GameMap.MAGNITUDE_MASK; + return this.terrain[ref] & GameMapImpl.MAGNITUDE_MASK; } // State getters and setters (mutable) ownerID(ref: TileRef): number { - return this.state[ref] & GameMap.PLAYER_ID_MASK; + return this.state[ref] & GameMapImpl.PLAYER_ID_MASK; } hasOwner(ref: TileRef): boolean { @@ -92,45 +121,45 @@ export class GameMap { setOwnerID(ref: TileRef, playerId: number): void { - if (playerId > GameMap.PLAYER_ID_MASK) { - throw new Error(`Player ID ${playerId} exceeds maximum value ${GameMap.PLAYER_ID_MASK}`); + if (playerId > GameMapImpl.PLAYER_ID_MASK) { + throw new Error(`Player ID ${playerId} exceeds maximum value ${GameMapImpl.PLAYER_ID_MASK}`); } - this.state[ref] = (this.state[ref] & ~GameMap.PLAYER_ID_MASK) | playerId; + this.state[ref] = (this.state[ref] & ~GameMapImpl.PLAYER_ID_MASK) | playerId; } hasFallout(ref: TileRef): boolean { - return Boolean(this.state[ref] & (1 << GameMap.FALLOUT_BIT)); + return Boolean(this.state[ref] & (1 << GameMapImpl.FALLOUT_BIT)); } setFallout(ref: TileRef, value: boolean): void { if (value) { - this.state[ref] |= 1 << GameMap.FALLOUT_BIT; + this.state[ref] |= 1 << GameMapImpl.FALLOUT_BIT; } else { - this.state[ref] &= ~(1 << GameMap.FALLOUT_BIT); + this.state[ref] &= ~(1 << GameMapImpl.FALLOUT_BIT); } } isBorder(ref: TileRef): boolean { - return Boolean(this.state[ref] & (1 << GameMap.BORDER_BIT)); + return Boolean(this.state[ref] & (1 << GameMapImpl.BORDER_BIT)); } setBorder(ref: TileRef, value: boolean): void { if (value) { - this.state[ref] |= 1 << GameMap.BORDER_BIT; + this.state[ref] |= 1 << GameMapImpl.BORDER_BIT; } else { - this.state[ref] &= ~(1 << GameMap.BORDER_BIT); + this.state[ref] &= ~(1 << GameMapImpl.BORDER_BIT); } } hasDefenseBonus(ref: TileRef): boolean { - return Boolean(this.state[ref] & (1 << GameMap.DEFENSE_BONUS_BIT)); + return Boolean(this.state[ref] & (1 << GameMapImpl.DEFENSE_BONUS_BIT)); } setDefenseBonus(ref: TileRef, value: boolean): void { if (value) { - this.state[ref] |= 1 << GameMap.DEFENSE_BONUS_BIT; + this.state[ref] |= 1 << GameMapImpl.DEFENSE_BONUS_BIT; } else { - this.state[ref] &= ~(1 << GameMap.DEFENSE_BONUS_BIT); + this.state[ref] &= ~(1 << GameMapImpl.DEFENSE_BONUS_BIT); } } diff --git a/src/core/game/TerrainMapLoader.ts b/src/core/game/TerrainMapLoader.ts index 23158f70d..7c32b5956 100644 --- a/src/core/game/TerrainMapLoader.ts +++ b/src/core/game/TerrainMapLoader.ts @@ -1,9 +1,9 @@ import { consolex } from '../Consolex'; import { Cell, GameMapType, TerrainMap, TerrainTile, TerrainType } from './Game'; -import { GameMap } from './GameMap'; +import { GameMapImpl } from './GameMap'; import { terrainMapFileLoader } from './TerrainMapFileLoader'; -const loadedMaps = new Map() +const loadedMaps = new Map() export interface NationMap { name: string; @@ -149,7 +149,7 @@ export class TerrainMapImpl implements TerrainMap { } -export async function loadTerrainMap(map: GameMapType): Promise<{ nationMap: NationMap, gameMap: GameMap, miniGameMap: GameMap, terrain: TerrainMap }> { +export async function loadTerrainMap(map: GameMapType): Promise<{ nationMap: NationMap, gameMap: GameMapImpl, miniGameMap: GameMapImpl, terrain: TerrainMap }> { if (loadedMaps.has(map)) { return loadedMaps.get(map) } @@ -162,7 +162,7 @@ export async function loadTerrainMap(map: GameMapType): Promise<{ nationMap: Nat return result } -export async function loadTerrainFromFile(fileData: string): Promise<{ map: GameMap, terrain: TerrainMap }> { +export async function loadTerrainFromFile(fileData: string): Promise<{ map: GameMapImpl, terrain: TerrainMap }> { const width = (fileData.charCodeAt(1) << 8) | fileData.charCodeAt(0); const height = (fileData.charCodeAt(3) << 8) | fileData.charCodeAt(2); @@ -184,7 +184,7 @@ export async function loadTerrainFromFile(fileData: string): Promise<{ map: Game m.rawData[i] = packedByte; if (packedByte & 0b10000000) numLand++; } - const gm = new GameMap(width, height, m.rawData, numLand) + const gm = new GameMapImpl(width, height, m.rawData, numLand) m._numLandTiles = numLand; return { map: gm, terrain: m } diff --git a/src/core/game/TileImpl.ts b/src/core/game/TileImpl.ts index b3967fe89..ea2fe23f4 100644 --- a/src/core/game/TileImpl.ts +++ b/src/core/game/TileImpl.ts @@ -3,7 +3,7 @@ import { TerrainMapImpl, TerrainTileImpl } from "./TerrainMapLoader"; import { GameImpl } from "./GameImpl"; import { PlayerImpl } from "./PlayerImpl"; import { TerraNulliusImpl } from "./TerraNulliusImpl"; -import { GameMap, TileRef } from "./GameMap"; +import { GameMapImpl, TileRef } from "./GameMap"; export class TileImpl implements MutableTile { @@ -102,14 +102,14 @@ export class TileImpl implements MutableTile { } neighbors(): Tile[] { - return this.gs.neighbors(this) + return this.gs.neighbors(this.ref()).map(n => this.gs.fromRef(n)) } } export class TerrainRef implements TerrainTile { - constructor(private map: GameMap, private ref: TileRef) { } + constructor(private map: GameMapImpl, private ref: TileRef) { } isLand(): boolean { return this.map.isLand(this.ref) diff --git a/src/core/pathfinding/MiniAStar.ts b/src/core/pathfinding/MiniAStar.ts index 851224164..8beed47f7 100644 --- a/src/core/pathfinding/MiniAStar.ts +++ b/src/core/pathfinding/MiniAStar.ts @@ -1,6 +1,6 @@ import { GameManager } from "../../server/GameManager"; import { Cell, Game, TerrainMap, TerrainType } from "../game/Game"; -import { GameMap, TileRef } from "../game/GameMap"; +import { GameMapImpl, TileRef } from "../game/GameMap"; import { AStar, PathFindResultType, } from "./AStar"; import { SerialAStar } from "./SerialAStar"; @@ -10,8 +10,8 @@ export class MiniAStar implements AStar { private aStar: SerialAStar constructor( - private gameMap: GameMap, - private miniMap: GameMap, + private gameMap: GameMapImpl, + private miniMap: GameMapImpl, private src: TileRef, private dst: TileRef, private canMove: (t: TileRef) => boolean, diff --git a/src/core/pathfinding/SerialAStar.ts b/src/core/pathfinding/SerialAStar.ts index a14e2546b..1eaffbd30 100644 --- a/src/core/pathfinding/SerialAStar.ts +++ b/src/core/pathfinding/SerialAStar.ts @@ -3,7 +3,7 @@ import { AStar } from "./AStar"; import { PathFindResultType } from "./AStar"; import { Cell } from "../game/Game"; import { consolex } from "../Consolex"; -import { GameMap, TileRef } from "../game/GameMap"; +import { GameMapImpl, TileRef } from "../game/GameMap"; export class SerialAStar implements AStar { @@ -22,7 +22,7 @@ export class SerialAStar implements AStar { private canMove: (t: TileRef) => boolean, private iterations: number, private maxTries: number, - private gameMap: GameMap + private gameMap: GameMapImpl ) { this.fwdOpenSet = new PriorityQueue<{ tile: TileRef; fScore: number; }>( (a, b) => a.fScore - b.fScore