mafia / frontend /src /systems /GameDataManager.ts
Alfaxad's picture
Migrate Mafia game to ZeroGPU Gradio Space
c670567 verified
Raw
History Blame Contribute Delete
5.51 kB
/**
* ============================================================================
* GAME DATA MANAGER - Global persistent state management
* ============================================================================
*
* Singleton that manages all persistent game state across scenes:
* - Numeric stats (HP, score, love points, money)
* - Boolean flags (chapter completed, item obtained, choice made)
* - Inventory items
* - Chapter progress
* - Ending determination
*
* Data persists across scene transitions because the singleton lives
* in module scope (not tied to any scene lifecycle).
* Optionally saves/loads from localStorage for cross-session persistence.
*
* USAGE:
* const gd = GameDataManager.getInstance();
*
* // Set values
* gd.set('playerHP', 100);
* gd.addTo('score', 50);
* gd.setFlag('chapter1_complete', true);
*
* // Get values
* const hp = gd.get('playerHP', 100); // default 100
* const done = gd.getFlag('chapter1_complete');
*
* // Inventory
* gd.addItem('health_potion');
* gd.hasItem('health_potion'); // true
*
* // Save/Load
* gd.saveToStorage('my_game_save');
* gd.loadFromStorage('my_game_save');
*
* // Ending
* const ending = gd.determineEnding(endingRules);
*/
export interface EndingRule {
/** Ending scene key */
endingKey: string;
/** Condition function -- first matching rule wins */
condition: (data: GameDataManager) => boolean;
/** Priority (higher = checked first) */
priority?: number;
}
export class GameDataManager {
// Singleton pattern
private static instance: GameDataManager;
private numericData: Map<string, number> = new Map();
private flagData: Map<string, boolean> = new Map();
private inventory: Set<string> = new Set();
private choiceHistory: Map<string, string> = new Map();
private constructor() {}
static getInstance(): GameDataManager {
if (!GameDataManager.instance) {
GameDataManager.instance = new GameDataManager();
}
return GameDataManager.instance;
}
// -- Numeric Data --
/** Set a numeric value. */
set(key: string, value: number): void {
this.numericData.set(key, value);
}
/** Get a numeric value with optional default. */
get(key: string, defaultValue: number = 0): number {
return this.numericData.get(key) ?? defaultValue;
}
/** Add to a numeric value. */
addTo(key: string, amount: number): number {
const current = this.get(key);
const newValue = current + amount;
this.set(key, newValue);
return newValue;
}
// -- Flags --
/** Set a boolean flag. */
setFlag(key: string, value: boolean): void {
this.flagData.set(key, value);
}
/** Get a boolean flag. */
getFlag(key: string): boolean {
return this.flagData.get(key) ?? false;
}
// -- Inventory --
/** Add an item to inventory. */
addItem(itemId: string): void {
this.inventory.add(itemId);
}
/** Remove an item from inventory. */
removeItem(itemId: string): boolean {
return this.inventory.delete(itemId);
}
/** Check if player has an item. */
hasItem(itemId: string): boolean {
return this.inventory.has(itemId);
}
/** Get all inventory items. */
getInventory(): string[] {
return [...this.inventory];
}
// -- Choice History --
/** Record a choice the player made. */
recordChoice(choiceId: string, selectedOption: string): void {
this.choiceHistory.set(choiceId, selectedOption);
}
/** Get what the player chose for a given choice. */
getChoice(choiceId: string): string | undefined {
return this.choiceHistory.get(choiceId);
}
// -- Ending Determination --
/**
* Evaluate ending rules and return the matching ending key.
* Rules are sorted by priority (highest first).
*/
determineEnding(rules: EndingRule[]): string | undefined {
const sorted = [...rules].sort(
(a, b) => (b.priority ?? 0) - (a.priority ?? 0),
);
for (const rule of sorted) {
if (rule.condition(this)) {
return rule.endingKey;
}
}
return undefined;
}
// -- Persistence --
/** Save all data to localStorage. */
saveToStorage(slotName: string): void {
try {
const data = {
numeric: Object.fromEntries(this.numericData),
flags: Object.fromEntries(this.flagData),
inventory: [...this.inventory],
choices: Object.fromEntries(this.choiceHistory),
};
localStorage.setItem(slotName, JSON.stringify(data));
} catch (e) {
console.warn('[GameDataManager] Failed to save:', e);
}
}
/** Load data from localStorage. Returns true if data was loaded. */
loadFromStorage(slotName: string): boolean {
try {
const raw = localStorage.getItem(slotName);
if (!raw) return false;
const data = JSON.parse(raw);
if (data.numeric) {
this.numericData = new Map(Object.entries(data.numeric));
}
if (data.flags) {
this.flagData = new Map(Object.entries(data.flags));
}
if (data.inventory) {
this.inventory = new Set(data.inventory);
}
if (data.choices) {
this.choiceHistory = new Map(Object.entries(data.choices));
}
return true;
} catch (e) {
console.warn('[GameDataManager] Failed to load:', e);
return false;
}
}
/** Reset all data to defaults. */
reset(): void {
this.numericData.clear();
this.flagData.clear();
this.inventory.clear();
this.choiceHistory.clear();
}
}