From 398d354702ec81583e683a8065008a1765a3dfa5 Mon Sep 17 00:00:00 2001 From: ilan schemoul Date: Thu, 6 Mar 2025 00:32:57 +0100 Subject: [PATCH] feat: save stats to local storage Victory lose, player stats, lobby config are stored to local storage (for later use) --- src/client/ClientGameRunner.ts | 14 ++-- src/client/LocalPersistantStats.ts | 88 +++++++++++++++++++++++ src/client/LocalServer.ts | 2 + src/client/graphics/layers/PlayerPanel.ts | 13 ++-- src/client/graphics/layers/WinModal.ts | 12 ++++ src/core/GameRunner.ts | 1 - src/core/game/Game.ts | 1 - src/core/game/GameUpdates.ts | 2 + src/core/game/GameView.ts | 10 ++- src/core/game/PlayerImpl.ts | 1 + 10 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 src/client/LocalPersistantStats.ts diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 985060473..7e2f77f5a 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -22,6 +22,7 @@ import { getConfig, getServerConfig } from "../core/configuration/Config"; import { GameView, PlayerView } from "../core/game/GameView"; import { GameUpdateViewData } from "../core/game/GameUpdates"; import { UserSettings } from "../core/game/UserSettings"; +import { LocalPersistantStats } from "./LocalPersistantStats"; export interface LobbyConfig { flag: () => string; @@ -120,6 +121,7 @@ export async function createClientGame( config, gameMap.gameMap, lobbyConfig.clientID, + lobbyConfig.gameID, ); consolex.log("going to init path finder"); @@ -137,7 +139,7 @@ export async function createClientGame( ); return new ClientGameRunner( - lobbyConfig.clientID, + lobbyConfig, eventBus, gameRenderer, new InputHandler(canvas, eventBus), @@ -148,6 +150,7 @@ export async function createClientGame( } export class ClientGameRunner { + private localPersistantsStats = new LocalPersistantStats(); private myPlayer: PlayerView; private isActive = false; @@ -155,7 +158,7 @@ export class ClientGameRunner { private hasJoined = false; constructor( - private clientID: ClientID, + private lobby: LobbyConfig, private eventBus: EventBus, private renderer: GameRenderer, private input: InputHandler, @@ -165,6 +168,7 @@ export class ClientGameRunner { ) {} public start() { + this.localPersistantsStats.startGame(this.lobby); consolex.log("starting client game"); this.isActive = true; this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e)); @@ -173,7 +177,7 @@ export class ClientGameRunner { this.input.initialize(); this.worker.start((gu: GameUpdateViewData | ErrorUpdate) => { if ("errMsg" in gu) { - showErrorModal(gu.errMsg, gu.stack, this.clientID); + showErrorModal(gu.errMsg, gu.stack, this.lobby.clientID); return; } gu.updates[GameUpdateType.Hash].forEach((hu: HashUpdate) => { @@ -217,7 +221,7 @@ export class ClientGameRunner { showErrorModal( `desync from server: ${JSON.stringify(message)}`, "", - this.clientID, + this.lobby.clientID, ); } if (message.type == "turn") { @@ -269,7 +273,7 @@ export class ClientGameRunner { return; } if (this.myPlayer == null) { - this.myPlayer = this.gameView.playerByClientID(this.clientID); + this.myPlayer = this.gameView.playerByClientID(this.lobby.clientID); if (this.myPlayer == null) { return; } diff --git a/src/client/LocalPersistantStats.ts b/src/client/LocalPersistantStats.ts new file mode 100644 index 000000000..5cdb9917e --- /dev/null +++ b/src/client/LocalPersistantStats.ts @@ -0,0 +1,88 @@ +import { consolex } from "../core/Consolex"; +import { Difficulty, GameMapType, GameType } from "../core/game/Game"; +import { PlayerStats } from "../core/game/Stats"; +import { ClientID, GameID } from "../core/Schemas"; +import { LobbyConfig } from "./ClientGameRunner"; + +export interface GameStat { + lobby: { + clientID: ClientID; + persistentID: string; + map: GameMapType | null; + gameType: GameType; + difficulty: Difficulty | null; + infiniteGold: boolean | null; + infiniteTroops: boolean | null; + instantBuild: boolean | null; + bots: number | null; + disableNPCs: boolean | null; + }; + playerStats?: PlayerStats; + outcome?: "victory" | "defeat"; +} + +export class PersistantStats { + // Can be used to handle breaking changes + version: "v0.0.1"; + games: { + [key: GameID]: GameStat; + }; +} + +export class LocalPersistantStats { + private getStats() { + const statsStr = localStorage.getItem("stats"); + let stats: PersistantStats; + if (!statsStr) { + stats = { version: "v0.0.1", games: {} }; + } else { + stats = JSON.parse(statsStr); + } + + return stats; + } + + public startGame(lobby: LobbyConfig) { + if (typeof localStorage === "undefined") { + return; + } + + const stats = this.getStats(); + stats.games[lobby.gameID] = { + lobby: { + clientID: lobby.clientID, + persistentID: lobby.persistentID, + map: lobby.map, + gameType: lobby.gameType, + difficulty: lobby.difficulty, + infiniteGold: lobby.infiniteGold, + infiniteTroops: lobby.infiniteTroops, + instantBuild: lobby.instantBuild, + bots: lobby.bots, + disableNPCs: lobby.disableNPCs, + }, + }; + localStorage.setItem("stats", JSON.stringify(stats)); + } + + public endGame( + id: GameID, + playerStats: PlayerStats, + outcome: GameStat["outcome"], + ) { + if (typeof localStorage === "undefined") { + return; + } + + const stats = this.getStats(); + const gameStat = stats.games[id]; + if (!gameStat) { + consolex.log("game not found"); + return; + } + + gameStat.outcome = outcome; + gameStat.playerStats = playerStats; + localStorage.setItem("stats", JSON.stringify(stats)); + } +} diff --git a/src/client/LocalServer.ts b/src/client/LocalServer.ts index 282e916af..eaccb6017 100644 --- a/src/client/LocalServer.ts +++ b/src/client/LocalServer.ts @@ -22,12 +22,14 @@ import { } from "../core/Schemas"; import { CreateGameRecord, generateID } from "../core/Util"; import { LobbyConfig } from "./ClientGameRunner"; +import { LocalPersistantStats } from "./LocalPersistantStats"; import { getPersistentIDFromCookie } from "./Main"; export class LocalServer { private turns: Turn[] = []; private intents: Intent[] = []; private startedAt: number; + private localPersistantsStats = new LocalPersistantStats(); private endTurnIntervalID; diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts index fba06c7a1..37f2e740a 100644 --- a/src/client/graphics/layers/PlayerPanel.ts +++ b/src/client/graphics/layers/PlayerPanel.ts @@ -8,6 +8,7 @@ import { AllPlayers, Player, PlayerActions, + PlayerID, UnitType, } from "../../../core/game/Game"; import { TileRef } from "../../../core/game/GameMap"; @@ -133,8 +134,8 @@ export class PlayerPanel extends LitElement implements Layer { this.requestUpdate(); } - getTotalNukesSent(): number { - const stats = this.actions.interaction?.stats; + getTotalNukesSent(otherId: PlayerID): number { + const stats = this.g.player(otherId).stats(); if (!stats) { return 0; } @@ -189,7 +190,7 @@ export class PlayerPanel extends LitElement implements Layer {