use updates for serializing game updates

This commit is contained in:
Evan
2025-01-09 10:17:18 -08:00
parent 459fc50dae
commit 8c947a9fbf
12 changed files with 111 additions and 137 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
import { Game, Player, Tile, Cell } from '../../core/game/Game';
import { GameView, NameViewData } from '../../core/GameView';
import { Game, Player, Tile, Cell, NameViewData } from '../../core/game/Game';
import { GameView } from '../../core/GameView';
import { calculateBoundingBox, within } from '../../core/Util';
export interface Point {
@@ -1,7 +1,7 @@
import { LitElement, html, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { EventBus } from '../../../../core/EventBus';
import { Cell, Game, Player, UnitType } from '../../../../core/game/Game';
import { Cell, Game, Player, PlayerActions, UnitType } from '../../../../core/game/Game';
import { BuildUnitIntentEvent } from '../../../Transport';
import atomBombIcon from '../../../../../resources/images/NukeIconWhite.svg';
import hydrogenBombIcon from '../../../../../resources/images/MushroomCloudIconWhite.svg';
@@ -14,7 +14,7 @@ import shieldIcon from '../../../../../resources/images/ShieldIconWhite.svg';
import cityIcon from '../../../../../resources/images/CityIconWhite.svg';
import { renderNumber } from '../../../Utils';
import { ContextMenuEvent } from '../../../InputHandler';
import { GameView, PlayerActions, PlayerView } from '../../../../core/GameView';
import { GameView, PlayerView } from '../../../../core/GameView';
interface BuildItemDisplay {
unitType: UnitType
@@ -1,5 +1,5 @@
import { EventBus } from "../../../../core/EventBus";
import { AllPlayers, Cell, Game, Player, Tile, UnitType } from "../../../../core/game/Game";
import { AllPlayers, Cell, Game, Player, PlayerActions, Tile, UnitType } from "../../../../core/game/Game";
import { ClientID } from "../../../../core/Schemas";
import { and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore, targetTransportTile } from "../../../../core/Util";
import { ContextMenuEvent, MouseUpEvent, ShowBuildMenuEvent } from "../../../InputHandler";
@@ -20,7 +20,7 @@ import { EmojiTable } from "./EmojiTable";
import { UIState } from "../../UIState";
import { BuildMenu } from "./BuildMenu";
import { consolex } from "../../../../core/Consolex";
import { GameView, PlayerActions, PlayerView } from "../../../../core/GameView";
import { GameView, PlayerView } from "../../../../core/GameView";
enum Slot {
+4 -4
View File
@@ -4,12 +4,12 @@ import { getConfig } from "./configuration/Config";
import { EventBus } from "./EventBus";
import { Executor } from "./execution/ExecutionManager";
import { WinCheckExecution } from "./execution/WinCheckExecution";
import { Cell, DisplayMessageUpdate, Game, GameUpdateType, MessageType, MutableGame, MutableTile, Player, PlayerID, Tile, UnitType } from "./game/Game";
import { Cell, DisplayMessageUpdate, Game, GameUpdateType, MessageType, MutableGame, MutableTile, NameViewData, Player, PlayerActions, PlayerID, Tile, UnitType } from "./game/Game";
import { createGame } from "./game/GameImpl";
import { loadTerrainMap } from "./game/TerrainMapLoader";
import { GameUpdateViewData, NameViewData, packTileData, PlayerActions, PlayerViewData } from "./GameView";
import { GameConfig, Turn } from "./Schemas";
import { and, bfs, dist, targetTransportTile } from "./Util";
import { GameUpdateViewData, packTileData } from "./GameView";
export async function createGameRunner(gameID: string, gameConfig: GameConfig, callBack: (gu: GameUpdateViewData) => void): Promise<GameRunner> {
const config = getConfig(gameConfig)
@@ -69,14 +69,14 @@ export class GameRunner {
const playerViewData = {}
for (const player of this.game.allPlayers()) {
const viewData = player.toViewData()
const viewData = player.toUpdate()
viewData.nameViewData = this.playerToName.get(player.id())
playerViewData[player.id()] = viewData
}
this.callBack({
tick: this.game.ticks(),
units: this.game.units().map(u => u.toViewData()),
units: this.game.units().map(u => u.toUpdate()),
packedTileUpdates: updates.filter(u => u.type == GameUpdateType.Tile).map(u => packTileData(u)),
players: playerViewData
})
+23 -84
View File
@@ -1,26 +1,10 @@
import { GameUpdateType, MessageType, Player, Tile, TileUpdate, Unit } from './game/Game';
import { GameUpdateType, MapPos, MessageType, NameViewData, Player, PlayerActions, PlayerUpdate, Tile, TileUpdate, Unit, UnitUpdate } from './game/Game';
import { Config } from "./configuration/Config";
import { Alliance, AllianceRequest, AllPlayers, Cell, DefenseBonus, EmojiMessage, Execution, ExecutionView, Game, Gold, MutableTile, Nation, PlayerID, PlayerInfo, PlayerType, Relation, TerrainMap, TerrainTile, TerrainType, TerraNullius, Tick, UnitInfo, UnitType } from "./game/Game";
import { ClientID } from "./Schemas";
import { TerraNulliusImpl } from './game/TerraNulliusImpl';
import { WorkerClient } from './worker/WorkerClient';
export interface ViewSerializable<T> {
toViewData(): T;
}
export interface ViewData<T> {
// Base view data properties if any
}
export interface TileViewData extends ViewData<TileViewData> {
x: number
y: number
smallID: number,
hasFallout: boolean
hasDefenseBonus: boolean
isBorder: boolean
}
export class TileView {
@@ -63,21 +47,14 @@ export class TileView {
}
}
export interface UnitViewData extends ViewData<UnitView> {
id: number,
type: UnitType,
troops: number,
x: number,
y: number,
owner: string,
isActive: boolean,
health?: number
}
export class UnitView implements Unit {
constructor(private gameView: GameView, private data: UnitViewData) { }
constructor(private gameView: GameView, private data: UnitUpdate) { }
update(data: UnitViewData) {
lastTile(): Tile {
return this.gameView.tile(new Cell(this.data.lastPos.x, this.data.lastPos.y))
}
update(data: UnitUpdate) {
this.data = data
}
@@ -86,16 +63,16 @@ export class UnitView implements Unit {
}
type(): UnitType {
return this.data.type
return this.data.unitType
}
troops(): number {
return this.data.troops
}
tile(): Tile {
return this.gameView.tile(new Cell(this.data.x, this.data.y))
return this.gameView.tile(new Cell(this.data.pos.x, this.data.pos.y))
}
owner(): PlayerView {
return this.gameView.player(this.data.owner)
return this.gameView.playerBySmallID(this.data.ownerID)
}
isActive(): boolean {
return this.data.isActive
@@ -108,49 +85,8 @@ export class UnitView implements Unit {
}
}
export interface NameViewData {
x: number,
y: number,
size: number,
}
export interface PlayerViewData extends ViewData<PlayerViewData> {
nameViewData?: NameViewData,
clientID: ClientID,
name: string,
displayName: string,
id: PlayerID,
smallID: number,
type: PlayerType,
isAlive: boolean,
tilesOwned: number,
allies: PlayerID[],
gold: number,
population: number,
workers: number,
troops: number,
targetTroopRatio: number
}
export interface PlayerActions {
canBoat: boolean
canAttack: boolean
buildableUnits: UnitType[]
interaction?: PlayerInteraction
}
export interface PlayerInteraction {
sharedBorder: boolean
canSendEmoji: boolean
canSendAllianceRequest: boolean
canBreakAlliance: boolean
canTarget: boolean
canDonate: boolean
}
export class PlayerView implements Player {
constructor(private game: GameView, public data: PlayerViewData) { }
constructor(private game: GameView, public data: PlayerUpdate) { }
async actions(tile: Tile): Promise<PlayerActions> {
return this.game.worker.playerInteraction(this.id(), tile)
@@ -179,7 +115,7 @@ export class PlayerView implements Player {
return this.data.id
}
type(): PlayerType {
return this.data.type
return this.data.playerType
}
isAlive(): boolean {
return this.data.isAlive
@@ -272,11 +208,10 @@ export class PlayerView implements Player {
}
}
export interface GameUpdateViewData extends ViewData<GameUpdateViewData> {
export interface GameUpdateViewData {
tick: number
units: UnitViewData[]
players: Record<PlayerID, PlayerViewData>
tileUpdates?: TileUpdate[]
units: UnitUpdate[]
players: Record<PlayerID, PlayerUpdate>
packedTileUpdates: Uint16Array[]
}
@@ -286,6 +221,7 @@ export class GameView {
private smallIDToID = new Map<number, PlayerID>()
private _players = new Map<PlayerID, PlayerView>()
private _units = new Map<number, UnitView>()
private updatedTiles: TileView[] = []
constructor(public worker: WorkerClient, private _config: Config, private _terrainMap: TerrainMap) {
// Initialize the 2D array
@@ -300,7 +236,6 @@ export class GameView {
this.lastUpdate = {
tick: 0,
units: [],
tileUpdates: [],
packedTileUpdates: [],
players: {}
}
@@ -308,10 +243,14 @@ export class GameView {
public update(gu: GameUpdateViewData) {
this.lastUpdate = gu
this.lastUpdate.tileUpdates = this.lastUpdate.packedTileUpdates.map(tu => unpackTileData(tu))
this.lastUpdate.tileUpdates.forEach(tu => {
const updated = new Set<MapPos>()
this.lastUpdate.packedTileUpdates.map(tu => unpackTileData(tu)).forEach(tu => {
this.tiles[tu.pos.x][tu.pos.y].data = tu
updated.add(tu.pos)
})
this.updatedTiles = Array.from(updated).map(pos => this.tiles[pos.x][pos.y])
Object.entries(gu.players).forEach(([key, value]) => {
this.smallIDToID.set(value.smallID, key);
if (this._players.has(key)) {
@@ -330,7 +269,7 @@ export class GameView {
}
recentlyUpdatedTiles(): TileView[] {
return this.lastUpdate.tileUpdates.map(tu => new TileView(this, tu, this._terrainMap.terrain(new Cell(tu.pos.x, tu.pos.y))))
return this.updatedTiles
}
player(id: PlayerID): PlayerView {
+53 -6
View File
@@ -2,7 +2,6 @@ import { Config } from "../configuration/Config"
import { GameEvent } from "../EventBus"
import { ClientID, GameConfig, GameID } from "../Schemas"
import { SearchNode } from "../pathfinding/AStar"
import { PlayerViewData, TileViewData, UnitViewData, ViewSerializable } from "../GameView"
export type PlayerID = string
export type Tick = number
@@ -206,7 +205,7 @@ export interface Tile extends SearchNode {
hasDefenseBonus(): boolean
}
export interface MutableTile extends Tile, ViewSerializable<TileViewData> {
export interface MutableTile extends Tile {
// defense bonus against this player
defenseBonus(player: Player): number
borders(other: Player | TerraNullius): boolean
@@ -223,15 +222,17 @@ export interface Unit {
isActive(): boolean
hasHealth(): boolean
health(): number
lastTile(): Tile
}
export interface MutableUnit extends Unit, ViewSerializable<UnitViewData> {
export interface MutableUnit extends Unit {
move(tile: Tile): void
owner(): MutablePlayer
setTroops(troops: number): void
info(): UnitInfo
delete(displayerMessage?: boolean): void
modifyHealth(delta: number): void
toUpdate(): UnitUpdate
}
export interface TerraNullius {
@@ -288,7 +289,7 @@ export interface Player {
lastTileChange(): Tick
}
export interface MutablePlayer extends Player, ViewSerializable<PlayerViewData> {
export interface MutablePlayer extends Player {
// Targets for this player
targets(): Player[]
// Targets of player and all allies.
@@ -328,6 +329,8 @@ export interface MutablePlayer extends Player, ViewSerializable<PlayerViewData>
buildUnit(type: UnitType, troops: number, tile: Tile): MutableUnit
captureUnit(unit: MutableUnit): void
toUpdate(): PlayerUpdate
}
export interface Game {
@@ -375,6 +378,7 @@ export interface MutableGame extends Game {
export enum GameUpdateType {
Tile,
Unit,
Player,
DisplayEvent,
AllianceRequest,
AllianceRequestReply,
@@ -385,8 +389,31 @@ export enum GameUpdateType {
WinUpdate
}
export interface NameViewData {
x: number,
y: number,
size: number,
}
export interface PlayerActions {
canBoat: boolean
canAttack: boolean
buildableUnits: UnitType[]
interaction?: PlayerInteraction
}
export interface PlayerInteraction {
sharedBorder: boolean
canSendEmoji: boolean
canSendAllianceRequest: boolean
canBreakAlliance: boolean
canTarget: boolean
canDonate: boolean
}
export type GameUpdate = TileUpdate
| UnitUpdate
| PlayerUpdate
| AllianceRequestUpdate
| AllianceRequestReplyUpdate
| BrokeAllianceUpdate
@@ -412,11 +439,31 @@ export interface UnitUpdate {
id: number
ownerID: number
pos: MapPos
oldPos: MapPos
lastPos: MapPos
isActive: boolean
health?: boolean
health?: number
}
export interface PlayerUpdate {
type: GameUpdateType.Player
nameViewData?: NameViewData,
clientID: ClientID,
name: string,
displayName: string,
id: PlayerID,
smallID: number,
playerType: PlayerType,
isAlive: boolean,
tilesOwned: number,
allies: PlayerID[],
gold: number,
population: number,
workers: number,
troops: number,
targetTroopRatio: number
}
export interface AllianceRequestUpdate {
type: GameUpdateType.AllianceRequest
requestorID: number,
+2 -2
View File
@@ -416,8 +416,8 @@ export class GameImpl implements MutableGame {
return false
}
public fireUnitUpdateEvent(unit: Unit, oldTile: Tile) {
this.updates.push((unit as UnitImpl).toUpdate(oldTile))
public fireUnitUpdateEvent(unit: Unit) {
this.updates.push((unit as UnitImpl).toUpdate())
}
target(targeter: Player, target: Player) {
+6 -6
View File
@@ -1,4 +1,4 @@
import { MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, EmojiMessage, AllPlayers, Gold, UnitType, Unit, MutableUnit, Relation, MutableTile } from "./Game";
import { MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, EmojiMessage, AllPlayers, Gold, UnitType, Unit, MutableUnit, Relation, MutableTile, PlayerUpdate, GameUpdateType } from "./Game";
import { ClientID } from "../Schemas";
import { assertNever, bfs, closestOceanShoreFromPlayer, dist, distSortUnit, manhattanDist, manhattanDistWrapped, processName, simpleHash, sourceDstOceanShore, within } from "../Util";
import { CellString, GameImpl } from "./GameImpl";
@@ -6,7 +6,6 @@ import { UnitImpl } from "./UnitImpl";
import { TileImpl } from "./TileImpl";
import { MessageType } from './Game';
import { renderTroops } from "../../client/Utils";
import { PlayerViewData } from "../GameView";
interface Target {
tick: Tick
@@ -56,14 +55,15 @@ export class PlayerImpl implements MutablePlayer {
this._displayName = this._name // processName(this._name)
}
toViewData(): PlayerViewData {
toUpdate(): PlayerUpdate {
return {
type: GameUpdateType.Player,
clientID: this.clientID(),
name: this.name(),
displayName: this.displayName(),
id: this.id(),
smallID: this.smallID(),
type: this.type(),
playerType: this.type(),
isAlive: this.isAlive(),
tilesOwned: this.numTilesOwned(),
allies: this.allies().map(p => p.id()),
@@ -417,7 +417,7 @@ export class PlayerImpl implements MutablePlayer {
(prev as PlayerImpl)._units = (prev as PlayerImpl)._units.filter(u => u != unit);
(unit as UnitImpl)._owner = this
this._units.push(unit as UnitImpl)
this.gs.fireUnitUpdateEvent(unit, unit.tile())
this.gs.fireUnitUpdateEvent(unit)
this.gs.displayMessage(`${unit.type()} captured by ${this.displayName()}`, MessageType.ERROR, prev.id())
this.gs.displayMessage(`Captured ${unit.type()} from ${prev.displayName()}`, MessageType.SUCCESS, this.id())
}
@@ -428,7 +428,7 @@ export class PlayerImpl implements MutablePlayer {
this._units.push(b);
this.removeGold(cost)
this.removeTroops(troops)
this.gs.fireUnitUpdateEvent(b, b.tile());
this.gs.fireUnitUpdateEvent(b);
return b;
}
-5
View File
@@ -4,7 +4,6 @@ import { TerrainTileImpl } from "./TerrainMapLoader";
import { GameImpl } from "./GameImpl";
import { PlayerImpl } from "./PlayerImpl";
import { TerraNulliusImpl } from "./TerraNulliusImpl";
import { TileView, TileViewData, ViewData, ViewSerializable } from "../GameView";
export class TileImpl implements MutableTile {
@@ -23,10 +22,6 @@ export class TileImpl implements MutableTile {
private readonly _terrain: TerrainTileImpl
) { }
toViewData(): TileViewData {
throw new Error("Method not implemented.");
}
toUpdate(): TileUpdate {
return {
type: GameUpdateType.Tile,
+13 -20
View File
@@ -1,5 +1,4 @@
import { GameUpdateType, MessageType, UnitUpdate } from './Game';
import { UnitViewData, ViewData, ViewSerializable } from "../GameView";
import { simpleHash, within } from "../Util";
import { MutableUnit, Tile, TerraNullius, UnitType, Player, UnitInfo } from "./Game";
import { GameImpl } from "./GameImpl";
@@ -9,6 +8,7 @@ import { PlayerImpl } from "./PlayerImpl";
export class UnitImpl implements MutableUnit {
private _active = true;
private _health: number
private _lastTile: Tile = null
constructor(
private _type: UnitType,
@@ -20,9 +20,11 @@ export class UnitImpl implements MutableUnit {
) {
// default to half health (or 1 is no health specified)
this._health = (this.g.unitInfo(_type).maxHealth ?? 2) / 2
this._lastTile = _tile
}
toUpdate(oldTile: Tile): UnitUpdate {
toUpdate(): UnitUpdate {
return {
type: GameUpdateType.Unit,
unitType: this._type,
@@ -31,20 +33,7 @@ export class UnitImpl implements MutableUnit {
ownerID: this._owner.smallID(),
isActive: this._active,
pos: this._tile.cell().pos(),
oldPos: oldTile.cell().pos()
}
}
toViewData(): UnitViewData {
return {
id: this._id,
type: this._type,
troops: this._troops,
x: this.tile().cell().x,
y: this.tile().cell().y,
owner: this.owner().id(),
isActive: this.isActive(),
health: this._health,
lastPos: this._lastTile.cell().pos()
}
}
@@ -52,13 +41,17 @@ export class UnitImpl implements MutableUnit {
return this._type
}
lastTile(): Tile {
return this._lastTile
}
move(tile: Tile): void {
if (tile == null) {
throw new Error("tile cannot be null")
}
const oldTile = this._tile;
this._lastTile = this._tile
this._tile = tile;
this.g.fireUnitUpdateEvent(this, oldTile);
this.g.fireUnitUpdateEvent(this);
}
setTroops(troops: number): void {
this._troops = troops;
@@ -87,7 +80,7 @@ export class UnitImpl implements MutableUnit {
const oldOwner = this._owner
oldOwner._units = oldOwner._units.filter(u => u != this)
this._owner = newOwner as PlayerImpl
this.g.fireUnitUpdateEvent(this, this.tile())
this.g.fireUnitUpdateEvent(this)
this.g.displayMessage(
`Your ${this.type()} was captured by ${newOwner.displayName()}`,
MessageType.ERROR,
@@ -110,7 +103,7 @@ export class UnitImpl implements MutableUnit {
}
this._owner._units = this._owner._units.filter(b => b != this);
this._active = false;
this.g.fireUnitUpdateEvent(this, this._tile);
this.g.fireUnitUpdateEvent(this);
if (displayMessage) {
this.g.displayMessage(`Your ${this.type()} was destroyed`, MessageType.ERROR, this.owner().id())
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { PlayerID, Tile } from "../game/Game";
import { GameUpdateViewData, PlayerActions, PlayerInteraction } from "../GameView";
import { PlayerActions, PlayerID, Tile } from "../game/Game";
import { GameUpdateViewData } from "../GameView";
import { GameConfig, GameID, Turn } from "../Schemas";
import { generateID } from "../Util";
import { WorkerMessage } from "./WorkerMessages";
+2 -2
View File
@@ -1,6 +1,6 @@
import { GameUpdateViewData, PlayerActions, PlayerInteraction } from "../GameView";
import { GameUpdateViewData } from "../GameView";
import { GameConfig, GameID, Turn } from "../Schemas";
import { PlayerID } from "../game/Game";
import { PlayerActions, PlayerID } from "../game/Game";
export type WorkerMessageType =
| 'init'