Browse Source

implement state machine

main
Stephanie Gredell 4 months ago
parent
commit
f7f4068713
  1. 1
      src/commands/CommandInvoker.js
  2. 134
      src/engine/GameStateMachine.js
  3. 23
      src/engine/events.js
  4. 56
      src/engine/states/BattleState.js
  5. 28
      src/engine/states/DefeatState.js
  6. 41
      src/engine/states/EventState.js
  7. 55
      src/engine/states/GameState.js
  8. 43
      src/engine/states/MapState.js
  9. 27
      src/engine/states/RelicSelectionState.js
  10. 31
      src/engine/states/RestState.js
  11. 44
      src/engine/states/ShopState.js
  12. 28
      src/engine/states/VictoryState.js
  13. 7
      src/input/InputManager.js
  14. 123
      src/main.js
  15. 29
      src/ui/render.js

1
src/commands/CommandInvoker.js

@ -27,7 +27,6 @@ export class CommandInvoker { @@ -27,7 +27,6 @@ export class CommandInvoker {
}
command.executed = true;
console.log(`Executed: ${command.getDescription()}`);
}
return success;

134
src/engine/GameStateMachine.js

@ -0,0 +1,134 @@ @@ -0,0 +1,134 @@
/**
* GameStateMachine - Centralized state management
* Manages all game states and transitions without adding new functionality
*/
export class GameStateMachine {
constructor(gameRoot) {
this.gameRoot = gameRoot;
this.currentState = null;
this.states = new Map();
this.stateHistory = []; // For debugging
}
/**
* Register a state with the state machine
* @param {string} name - State name
* @param {GameState} state - State instance
*/
registerState(name, state) {
this.states.set(name, state);
}
/**
* Get the current state
* @returns {GameState|null}
*/
getCurrentState() {
return this.currentState;
}
/**
* Get current state name
* @returns {string|null}
*/
getCurrentStateName() {
return this.currentState?.name || null;
}
/**
* Transition to a new state
* @param {string} stateName - Name of the state to transition to
* @param {Object} transitionData - Optional data for the transition
*/
async setState(stateName, transitionData = {}) {
const newState = this.states.get(stateName);
if (!newState) {
console.error(`State '${stateName}' not found`);
return false;
}
const previousState = this.currentState;
// Exit current state
if (previousState) {
await previousState.exit(this.gameRoot, newState);
}
// Update current state
this.currentState = newState;
// Add to history for debugging
this.stateHistory.push({
from: previousState?.name || 'none',
to: stateName,
timestamp: Date.now(),
data: transitionData
});
// Keep history reasonable size
if (this.stateHistory.length > 50) {
this.stateHistory.shift();
}
// Enter new state
await newState.enter(this.gameRoot, previousState);
return true;
}
/**
* Render the current state
*/
async render() {
if (this.currentState) {
await this.currentState.render(this.gameRoot);
}
}
/**
* Get state data for saving
*/
getSaveData() {
const data = {
currentStateName: this.getCurrentStateName(),
stateHistory: this.stateHistory.slice(-10) // Save last 10 for debugging
};
// Get state-specific save data
if (this.currentState) {
data.stateData = this.currentState.getSaveData(this.gameRoot);
}
return data;
}
/**
* Restore state from save data
* @param {Object} saveData - The saved state data
*/
async restoreFromSave(saveData) {
if (!saveData.currentStateName) {
console.warn('No state name in save data');
return false;
}
const success = await this.setState(saveData.currentStateName);
if (success && this.currentState && saveData.stateData) {
this.currentState.restoreFromSave(this.gameRoot, saveData.stateData);
}
// Restore history if available
if (saveData.stateHistory) {
this.stateHistory = saveData.stateHistory;
}
return success;
}
/**
* Get state transition history (for debugging)
*/
getHistory() {
return this.stateHistory.slice();
}
}

23
src/engine/events.js

@ -191,9 +191,9 @@ export class EventHandler { @@ -191,9 +191,9 @@ export class EventHandler {
// Reset button
const resetBtn = this.root.app.querySelector("[data-reset]");
if (resetBtn) {
this.on(resetBtn, "click", () => {
this.on(resetBtn, "click", async () => {
this.root.clearSave();
this.root.reset();
await this.root.reset();
});
}
@ -330,19 +330,12 @@ export class EventHandler { @@ -330,19 +330,12 @@ export class EventHandler {
setupEventEvents(event) {
this.switchScreen('event');
this.root.app.querySelectorAll("[data-choice]").forEach(btn => {
this.on(btn, "click", () => {
const idx = parseInt(btn.dataset.choice, 10);
event.choices[idx].effect();
this.root.afterNode();
});
});
// Keyboard shortcuts for event choices
// Event choice handlers are managed by InputManager
// Just set up keyboard shortcuts here
for (let i = 1; i <= event.choices.length; i++) {
this.addKeyHandler(i.toString(), () => {
this.addKeyHandler(i.toString(), async () => {
event.choices[i - 1].effect();
this.root.afterNode();
await this.root.afterNode();
}, `Event Choice ${i}`);
}
}
@ -388,7 +381,7 @@ export class EventHandler { @@ -388,7 +381,7 @@ export class EventHandler {
const menuBtn = this.root.app.querySelector("[data-menu]");
if (replayBtn) {
this.on(replayBtn, "click", () => this.root.reset());
this.on(replayBtn, "click", async () => await this.root.reset());
}
if (restartAct2Btn) {
@ -403,7 +396,7 @@ export class EventHandler { @@ -403,7 +396,7 @@ export class EventHandler {
}
if (menuBtn) {
this.on(menuBtn, "click", () => this.root.reset());
this.on(menuBtn, "click", async () => await this.root.reset());
}
// Keyboard shortcuts

56
src/engine/states/BattleState.js

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
import { GameState } from './GameState.js';
import { renderBattle } from '../../ui/render.js';
import { createBattle } from '../battle.js';
/**
* BattleState - Handles combat
* Preserves exact existing functionality from createBattle() and renderBattle()
*/
export class BattleState extends GameState {
constructor() {
super('BATTLE');
}
async enter(gameRoot, previousState = null) {
// Set battle flag (preserves existing behavior)
gameRoot._battleInProgress = true;
// If we don't have an enemy yet, we need to create the battle
// This happens when transitioning from map to battle
if (!gameRoot.enemy) {
const node = gameRoot.map.nodes.find(n => n.id === gameRoot.nodeId);
if (node && node.enemy) {
createBattle(gameRoot, node.enemy);
}
}
}
async exit(gameRoot, nextState = null) {
// Clear battle flag when leaving battle
gameRoot._battleInProgress = false;
}
async render(gameRoot) {
await renderBattle(gameRoot);
}
getSaveData(gameRoot) {
return {
...super.getSaveData(gameRoot),
nodeId: gameRoot.nodeId,
battleInProgress: gameRoot._battleInProgress,
enemy: gameRoot.enemy,
flags: gameRoot.flags,
lastCard: gameRoot.lastCard
};
}
restoreFromSave(gameRoot, saveData) {
if (saveData.battleInProgress !== undefined) {
gameRoot._battleInProgress = saveData.battleInProgress;
}
if (saveData.enemy) gameRoot.enemy = saveData.enemy;
if (saveData.flags) gameRoot.flags = saveData.flags;
if (saveData.lastCard) gameRoot.lastCard = saveData.lastCard;
}
}

28
src/engine/states/DefeatState.js

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
import { GameState } from './GameState.js';
import { renderLose } from '../../ui/render.js';
/**
* DefeatState - Handles defeat screen
* Preserves exact existing functionality from renderLose()
*/
export class DefeatState extends GameState {
constructor() {
super('DEFEAT');
}
async enter(gameRoot, previousState = null) {
// Trigger initial render when entering the state
await gameRoot.render();
}
async render(gameRoot) {
await renderLose(gameRoot);
}
getSaveData(gameRoot) {
return {
...super.getSaveData(gameRoot),
nodeId: gameRoot.nodeId
};
}
}

41
src/engine/states/EventState.js

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
import { GameState } from './GameState.js';
import { renderEvent } from '../../ui/render.js';
/**
* EventState - Handles random events
* Preserves exact existing functionality from renderEvent()
*/
export class EventState extends GameState {
constructor() {
super('EVENT');
}
async enter(gameRoot, previousState = null) {
// Save when entering event (preserves existing behavior)
gameRoot.save();
// Trigger initial render when entering the state
await gameRoot.render();
}
async exit(gameRoot, nextState = null) {
// Clear event-specific state when leaving
gameRoot.currentEvent = null;
}
async render(gameRoot) {
renderEvent(gameRoot);
}
getSaveData(gameRoot) {
return {
...super.getSaveData(gameRoot),
nodeId: gameRoot.nodeId,
currentEvent: gameRoot.currentEvent
};
}
restoreFromSave(gameRoot, saveData) {
if (saveData.currentEvent) gameRoot.currentEvent = saveData.currentEvent;
}
}

55
src/engine/states/GameState.js

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
/**
* Base GameState class for the State pattern
* All game states inherit from this base class
*/
export class GameState {
constructor(name) {
this.name = name;
}
/**
* Called when entering this state
* @param {Object} gameRoot - The game root object
* @param {Object} previousState - The previous state (optional)
*/
async enter(gameRoot, previousState = null) {
// Override in subclasses
}
/**
* Called when exiting this state
* @param {Object} gameRoot - The game root object
* @param {Object} nextState - The next state (optional)
*/
async exit(gameRoot, nextState = null) {
// Override in subclasses
}
/**
* Handle state-specific rendering
* @param {Object} gameRoot - The game root object
*/
async render(gameRoot) {
// Override in subclasses
throw new Error(`render() not implemented for state: ${this.name}`);
}
/**
* Get state-specific data for saving
* @param {Object} gameRoot - The game root object
*/
getSaveData(gameRoot) {
return {
stateName: this.name
};
}
/**
* Restore state-specific data from save
* @param {Object} gameRoot - The game root object
* @param {Object} saveData - The saved data
*/
restoreFromSave(gameRoot, saveData) {
// Override in subclasses if needed
}
}

43
src/engine/states/MapState.js

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
import { GameState } from './GameState.js';
import { renderMap } from '../../ui/render.js';
/**
* MapState - Handles map navigation
* Preserves exact existing functionality from root.go() and renderMap()
*/
export class MapState extends GameState {
constructor() {
super('MAP');
}
async enter(gameRoot, previousState = null) {
// Clear battle-specific state when entering map
gameRoot.enemy = null;
gameRoot._battleInProgress = false;
// Save when entering map (preserves existing behavior)
gameRoot.save();
// Trigger initial render when entering the state
await gameRoot.render();
}
async render(gameRoot) {
await renderMap(gameRoot);
}
getSaveData(gameRoot) {
return {
...super.getSaveData(gameRoot),
nodeId: gameRoot.nodeId,
currentAct: gameRoot.currentAct,
completedNodes: gameRoot.completedNodes
};
}
restoreFromSave(gameRoot, saveData) {
if (saveData.nodeId) gameRoot.nodeId = saveData.nodeId;
if (saveData.currentAct) gameRoot.currentAct = saveData.currentAct;
if (saveData.completedNodes) gameRoot.completedNodes = saveData.completedNodes;
}
}

27
src/engine/states/RelicSelectionState.js

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
import { GameState } from './GameState.js';
import { renderRelicSelection } from '../../ui/render.js';
/**
* RelicSelectionState - Handles starting relic choice
* Preserves exact existing functionality from renderRelicSelection()
*/
export class RelicSelectionState extends GameState {
constructor() {
super('RELIC_SELECTION');
}
async enter(gameRoot, previousState = null) {
// Trigger initial render when entering the state
await gameRoot.render();
}
async render(gameRoot) {
renderRelicSelection(gameRoot);
}
getSaveData(gameRoot) {
return {
...super.getSaveData(gameRoot)
};
}
}

31
src/engine/states/RestState.js

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
import { GameState } from './GameState.js';
import { renderRest } from '../../ui/render.js';
/**
* RestState - Handles rest/upgrade interactions
* Preserves exact existing functionality from renderRest()
*/
export class RestState extends GameState {
constructor() {
super('REST');
}
async enter(gameRoot, previousState = null) {
// Save when entering rest (preserves existing behavior)
gameRoot.save();
// Trigger initial render when entering the state
await gameRoot.render();
}
async render(gameRoot) {
await renderRest(gameRoot);
}
getSaveData(gameRoot) {
return {
...super.getSaveData(gameRoot),
nodeId: gameRoot.nodeId
};
}
}

44
src/engine/states/ShopState.js

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
import { GameState } from './GameState.js';
import { renderShop } from '../../ui/render.js';
/**
* ShopState - Handles shop interactions
* Preserves exact existing functionality from renderShop()
*/
export class ShopState extends GameState {
constructor() {
super('SHOP');
}
async enter(gameRoot, previousState = null) {
// Save when entering shop (preserves existing behavior)
gameRoot.save();
// Trigger initial render when entering the state
await gameRoot.render();
}
async exit(gameRoot, nextState = null) {
// Clear shop-specific state when leaving
gameRoot.currentShopCards = null;
gameRoot.currentShopRelic = null;
}
async render(gameRoot) {
renderShop(gameRoot);
}
getSaveData(gameRoot) {
return {
...super.getSaveData(gameRoot),
nodeId: gameRoot.nodeId,
currentShopCards: gameRoot.currentShopCards,
currentShopRelic: gameRoot.currentShopRelic
};
}
restoreFromSave(gameRoot, saveData) {
if (saveData.currentShopCards) gameRoot.currentShopCards = saveData.currentShopCards;
if (saveData.currentShopRelic) gameRoot.currentShopRelic = saveData.currentShopRelic;
}
}

28
src/engine/states/VictoryState.js

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
import { GameState } from './GameState.js';
import { renderWin } from '../../ui/render.js';
/**
* VictoryState - Handles victory screen
* Preserves exact existing functionality from renderWin()
*/
export class VictoryState extends GameState {
constructor() {
super('VICTORY');
}
async enter(gameRoot, previousState = null) {
// Trigger initial render when entering the state
await gameRoot.render();
}
async render(gameRoot) {
await renderWin(gameRoot);
}
getSaveData(gameRoot) {
return {
...super.getSaveData(gameRoot),
nodeId: gameRoot.nodeId
};
}
}

7
src/input/InputManager.js

@ -3,8 +3,6 @@ @@ -3,8 +3,6 @@
*
* This class consolidates ALL event listeners from the render functions
* into one place while maintaining exact same functionality.
*
* Following Nystrom's Input Handling patterns from Game Programming Patterns
*/
import { PlayCardCommand } from '../commands/PlayCardCommand.js';
@ -220,12 +218,13 @@ export class InputManager { @@ -220,12 +218,13 @@ export class InputManager {
/**
* Handle event choice clicks
*/
handleEventChoice(element, event) {
async handleEventChoice(element, event) {
const idx = parseInt(element.dataset.choice, 10);
// Get the current event from the root (this will need to be accessible)
if (this.root.currentEvent && this.root.currentEvent.choices[idx]) {
this.root.currentEvent.choices[idx].effect();
this.root.afterNode();
await this.root.afterNode();
}
}

123
src/main.js

@ -7,6 +7,15 @@ import { createBattle, startPlayerTurn, playCard, endTurn, makeBattleContext, at @@ -7,6 +7,15 @@ import { createBattle, startPlayerTurn, playCard, endTurn, makeBattleContext, at
import { renderBattle, renderMap, renderReward, renderRest, renderShop, renderWin, renderLose, renderEvent, renderRelicSelection, renderUpgrade, updateCardSelection, showDamageNumber, renderCodeReviewSelection } from "./ui/render.js";
import { InputManager } from "./input/InputManager.js";
import { CommandInvoker } from "./commands/CommandInvoker.js";
import { GameStateMachine } from "./engine/GameStateMachine.js";
import { MapState } from "./engine/states/MapState.js";
import { BattleState } from "./engine/states/BattleState.js";
import { ShopState } from "./engine/states/ShopState.js";
import { RestState } from "./engine/states/RestState.js";
import { EventState } from "./engine/states/EventState.js";
import { VictoryState } from "./engine/states/VictoryState.js";
import { DefeatState } from "./engine/states/DefeatState.js";
import { RelicSelectionState } from "./engine/states/RelicSelectionState.js";
const app = document.getElementById("app");
@ -20,6 +29,7 @@ const root = { @@ -20,6 +29,7 @@ const root = {
enemy: null,
inputManager: null, // Will be initialized later
commandInvoker: new CommandInvoker(),
stateMachine: null, // Will be initialized below
currentEvent: null, // For event handling
currentShopCards: null, // For shop handling
currentShopRelic: null, // For shop relic handling
@ -27,7 +37,14 @@ const root = { @@ -27,7 +37,14 @@ const root = {
_codeReviewCallback: null, // For code review completion
log(m) { this.logs.push(m); this.logs = this.logs.slice(-200); },
async render() { await renderBattle(this); },
async render() {
if (this.stateMachine) {
await this.stateMachine.render();
} else {
// Fallback for initialization
await renderBattle(this);
}
},
play(i) {
const battleCtx = makeBattleContext(this);
playCard(battleCtx, i);
@ -44,23 +61,17 @@ const root = { @@ -44,23 +61,17 @@ const root = {
const node = this.map.nodes.find(n => n.id === nextId);
if (!node) return;
// Use state machine for transitions
if (node.kind === "battle" || node.kind === "elite" || node.kind === "boss") {
this._battleInProgress = true;
createBattle(this, node.enemy);
await renderBattle(this);
} else {
this.save();
if (node.kind === "rest") {
await renderRest(this);
await this.stateMachine.setState('BATTLE');
} else if (node.kind === "rest") {
await this.stateMachine.setState('REST');
} else if (node.kind === "shop") {
renderShop(this);
await this.stateMachine.setState('SHOP');
} else if (node.kind === "event") {
renderEvent(this);
await this.stateMachine.setState('EVENT');
} else if (node.kind === "start") {
await renderMap(this);
}
await this.stateMachine.setState('MAP');
}
},
@ -69,8 +80,8 @@ const root = { @@ -69,8 +80,8 @@ const root = {
this.completedNodes.push(this.nodeId);
}
const node = this.map.nodes.find(n => n.id === this.nodeId);
if (node.kind === "battle" || node.kind === "elite") {
const choices = pickCards(3);
this._pendingChoices = choices;
@ -78,10 +89,11 @@ const root = { @@ -78,10 +89,11 @@ const root = {
return;
}
if (node.kind === "boss") {
await renderWin(this); return;
await this.stateMachine.setState('VICTORY');
return;
}
await renderMap(this);
await this.stateMachine.setState('MAP');
},
async takeReward(idx) {
@ -92,12 +104,12 @@ const root = { @@ -92,12 +104,12 @@ const root = {
}
this._pendingChoices = null;
this.save();
await renderMap(this);
await this.stateMachine.setState('MAP');
},
async skipReward() {
this._pendingChoices = null;
this.save();
await renderMap(this);
await this.stateMachine.setState('MAP');
},
async onWin() {
@ -111,9 +123,11 @@ const root = { @@ -111,9 +123,11 @@ const root = {
this._battleInProgress = false;
const node = this.map.nodes.find(n => n.id === this.nodeId);
if (node.kind === "boss") {
// Check if there's a next act
const nextAct = this.currentAct === "act1" ? "act2" : null;
if (nextAct && MAPS[nextAct]) {
// Advance to next act
this.currentAct = nextAct;
@ -128,27 +142,27 @@ const root = { @@ -128,27 +142,27 @@ const root = {
}
this.save();
await renderMap(this);
await this.stateMachine.setState('MAP');
} else {
// Final victory
this.save(); // Save progress before clearing on victory
this.clearSave(); // Clear save on victory
await renderWin(this);
await this.stateMachine.setState('VICTORY');
}
}
else {
this.save();
this.afterNode();
await this.afterNode();
}
},
async onLose() {
this._battleInProgress = false;
this.clearSave(); // Clear save on defeat
await renderLose(this);
await this.stateMachine.setState('DEFEAT');
},
reset() {
async reset() {
this.logs = [];
this.player = makePlayer();
initDeck(this.player);
@ -157,13 +171,13 @@ const root = { @@ -157,13 +171,13 @@ const root = {
this.nodeId = "n1";
this.completedNodes = [];
renderRelicSelection(this);
await this.stateMachine.setState('RELIC_SELECTION');
},
async selectStartingRelic(relicId) {
attachRelics(this, [relicId]);
this.save();
await renderMap(this);
await this.stateMachine.setState('MAP');
},
save() {
@ -176,6 +190,7 @@ const root = { @@ -176,6 +190,7 @@ const root = {
completedNodes: this.completedNodes,
logs: this.logs.slice(-50), // Keep last 50 logs
battleInProgress: this._battleInProgress || false,
stateMachine: this.stateMachine ? this.stateMachine.getSaveData() : null,
timestamp: Date.now()
};
localStorage.setItem('birthday-spire-save', JSON.stringify(saveData));
@ -300,6 +315,11 @@ const root = { @@ -300,6 +315,11 @@ const root = {
this.logs = Array.isArray(data.logs) ? data.logs : [];
this._battleInProgress = Boolean(data.battleInProgress);
// Restore state machine state
if (data.stateMachine && this.stateMachine) {
this.stateMachine.restoreFromSave(data.stateMachine);
}
this.restoreCardEffects();
this.log('Game loaded from save.');
@ -343,6 +363,21 @@ const root = { @@ -343,6 +363,21 @@ const root = {
}
};
// Initialize State Machine
try {
root.stateMachine = new GameStateMachine(root);
root.stateMachine.registerState('MAP', new MapState());
root.stateMachine.registerState('BATTLE', new BattleState());
root.stateMachine.registerState('SHOP', new ShopState());
root.stateMachine.registerState('REST', new RestState());
root.stateMachine.registerState('EVENT', new EventState());
root.stateMachine.registerState('VICTORY', new VictoryState());
root.stateMachine.registerState('DEFEAT', new DefeatState());
root.stateMachine.registerState('RELIC_SELECTION', new RelicSelectionState());
} catch (error) {
console.error('Error initializing state machine:', error);
}
function pickCards(n) {
const ids = shuffle(CARD_POOL.slice()).slice(0, n);
return ids.map(id => CARDS[id]);
@ -394,29 +429,29 @@ async function initializeGame() { @@ -394,29 +429,29 @@ async function initializeGame() {
switch (screenParam.toLowerCase()) {
case 'victory':
case 'win':
await renderWin(root);
await root.stateMachine.setState('VICTORY');
return;
case 'defeat':
case 'lose':
await renderLose(root);
await root.stateMachine.setState('DEFEAT');
return;
case 'map':
await renderMap(root);
await root.stateMachine.setState('MAP');
return;
case 'shop':
renderShop(root);
await root.stateMachine.setState('SHOP');
return;
case 'rest':
await renderRest(root);
await root.stateMachine.setState('REST');
return;
case 'event':
renderEvent(root);
await root.stateMachine.setState('EVENT');
return;
case 'battle':
root.go('n2'); // Battle node
root.go('n2'); // Battle node (uses state machine internally)
return;
case 'upgrade':
await renderRest(root);
await root.stateMachine.setState('REST');
setTimeout(() => {
const upgradeBtn = root.app.querySelector("[data-act='upgrade']");
if (upgradeBtn) upgradeBtn.click();
@ -424,7 +459,7 @@ async function initializeGame() { @@ -424,7 +459,7 @@ async function initializeGame() {
return;
case 'relic':
case 'relics':
renderRelicSelection(root);
await root.stateMachine.setState('RELIC_SELECTION');
return;
default:
console.warn(`Unknown screen: ${screenParam}. Loading normal game.`);
@ -541,19 +576,23 @@ async function loadNormalGame() { @@ -541,19 +576,23 @@ async function loadNormalGame() {
} else {
// Battle state inconsistent, go to map
root._battleInProgress = false;
await renderMap(root);
await root.stateMachine.setState('MAP');
}
} else {
await renderMap(root);
await root.stateMachine.setState('MAP');
}
} else {
root.reset();
await root.reset();
}
}
// Initialize InputManager
root.inputManager = new InputManager(root);
root.inputManager.initGlobalListeners();
try {
root.inputManager = new InputManager(root);
root.inputManager.initGlobalListeners();
} catch (error) {
console.error('Error initializing InputManager:', error);
}
// Make modules available globally for InputManager
window.gameModules = {
@ -561,5 +600,7 @@ window.gameModules = { @@ -561,5 +600,7 @@ window.gameModules = {
render: { renderMap, renderUpgrade, updateCardSelection, renderCodeReviewSelection }
};
initializeGame();
initializeGame().catch(error => {
console.error('Error during game initialization:', error);
});

29
src/ui/render.js

@ -11,7 +11,6 @@ function playSound(soundFile) { @@ -11,7 +11,6 @@ function playSound(soundFile) {
}
export function showDamageNumber(damage, target, isPlayer = false) {
console.log('this is shown - damage number')
const targetElement = isPlayer ?
document.querySelector('.player-battle-zone') :
document.querySelector('.enemy-battle-zone');
@ -1125,9 +1124,15 @@ export function renderEvent(root) { @@ -1125,9 +1124,15 @@ export function renderEvent(root) {
icon: "assets/card-art/apple.png",
risk: "high",
effect: () => {
root.player.hp = Math.min(root.player.maxHp, root.player.hp + 15);
const oldHp = root.player.hp;
root.player.hp += 15;
if (root.player.hp > root.player.maxHp) {
root.player.maxHp = root.player.hp;
}
root.player.deck.push("sugar_crash");
root.log("Ate cake: +15 HP, added Sugar Crash curse");
root.log(`Ate cake: +15 HP (${oldHp}${root.player.hp}), added Sugar Crash curse`);
}
},
{
@ -1136,7 +1141,10 @@ export function renderEvent(root) { @@ -1136,7 +1141,10 @@ export function renderEvent(root) {
icon: "assets/card-art/heart.png",
risk: "low",
effect: () => {
root.player.maxHp += 5;
root.player.maxHp += 8;
if (root.player.hp > root.player.maxHp) {
root.player.maxHp = root.player.hp;
}
root.log("Small bite: +8 HP");
}
},
@ -1246,6 +1254,9 @@ export function renderEvent(root) { @@ -1246,6 +1254,9 @@ export function renderEvent(root) {
const event = events[Math.floor(Math.random() * events.length)];
// Store the current event so other systems can access it
root.currentEvent = event;
root.app.innerHTML = `
<div class="event-screen">
<div class="event-header">
@ -1296,13 +1307,7 @@ export function renderEvent(root) { @@ -1296,13 +1307,7 @@ export function renderEvent(root) {
</div>
`;
root.app.querySelectorAll("[data-choice]").forEach(btn => {
btn.addEventListener("click", () => {
const idx = parseInt(btn.dataset.choice, 10);
event.choices[idx].effect();
root.afterNode();
});
});
// Event handlers are managed by InputManager - no need to add them here
}
export async function renderWin(root) {
@ -1402,7 +1407,7 @@ export async function renderWin(root) { @@ -1402,7 +1407,7 @@ export async function renderWin(root) {
</div>
</div>
`;
root.app.querySelector("[data-replay]").addEventListener("click", () => root.reset());
root.app.querySelector("[data-replay]").addEventListener("click", async () => await root.reset());
}
export async function renderCodeReviewSelection(root, cards) {

Loading…
Cancel
Save