11 changed files with 337 additions and 29 deletions
@ -0,0 +1,26 @@ |
|||||||
|
/** |
||||||
|
* Base Command class following the Command Pattern |
||||||
|
* All game actions should extend this class |
||||||
|
*/ |
||||||
|
export class Command { |
||||||
|
constructor() { |
||||||
|
this.executed = false; |
||||||
|
this.timestamp = Date.now(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Execute the command |
||||||
|
* @returns {boolean} true if successful, false otherwise |
||||||
|
*/ |
||||||
|
execute() { |
||||||
|
throw new Error("Command.execute() must be implemented by subclass"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get a description of this command for logging/debugging |
||||||
|
* @returns {string} |
||||||
|
*/ |
||||||
|
getDescription() { |
||||||
|
return this.constructor.name; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,54 @@ |
|||||||
|
/** |
||||||
|
* CommandInvoker manages command execution |
||||||
|
* Follows the Command Pattern for centralized action handling |
||||||
|
*/ |
||||||
|
export class CommandInvoker { |
||||||
|
constructor() { |
||||||
|
this.history = []; |
||||||
|
this.maxHistorySize = 50; // Prevent memory bloat
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Execute a command and add it to history |
||||||
|
* @param {Command} command - The command to execute |
||||||
|
* @returns {boolean} true if successful |
||||||
|
*/ |
||||||
|
execute(command) { |
||||||
|
try { |
||||||
|
const success = command.execute(); |
||||||
|
|
||||||
|
if (success) { |
||||||
|
// Add command to history for debugging
|
||||||
|
this.history.push(command); |
||||||
|
|
||||||
|
// Trim history if it gets too long
|
||||||
|
if (this.history.length > this.maxHistorySize) { |
||||||
|
this.history.shift(); |
||||||
|
} |
||||||
|
|
||||||
|
command.executed = true; |
||||||
|
console.log(`Executed: ${command.getDescription()}`); |
||||||
|
} |
||||||
|
|
||||||
|
return success; |
||||||
|
} catch (error) { |
||||||
|
console.error(`Command execution failed: ${command.getDescription()}`, error); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get command history for debugging |
||||||
|
* @returns {Array<string>} |
||||||
|
*/ |
||||||
|
getHistory() { |
||||||
|
return this.history.map(cmd => cmd.getDescription()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Clear command history |
||||||
|
*/ |
||||||
|
clear() { |
||||||
|
this.history = []; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
import { Command } from './Command.js'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Command for ending the player's turn in battle |
||||||
|
* Wraps the existing root.end() method |
||||||
|
*/ |
||||||
|
export class EndTurnCommand extends Command { |
||||||
|
constructor(gameRoot) { |
||||||
|
super(); |
||||||
|
this.gameRoot = gameRoot; |
||||||
|
} |
||||||
|
|
||||||
|
execute() { |
||||||
|
try { |
||||||
|
// Use existing root.end method (which now creates proper battle context)
|
||||||
|
this.gameRoot.end(); |
||||||
|
return true; |
||||||
|
} catch (error) { |
||||||
|
console.error("EndTurnCommand execution failed:", error); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getDescription() { |
||||||
|
return "End Turn"; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
import { Command } from './Command.js'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Command for moving to a node on the map |
||||||
|
* Wraps the existing root.go() method |
||||||
|
*/ |
||||||
|
export class MapMoveCommand extends Command { |
||||||
|
constructor(gameRoot, nodeId) { |
||||||
|
super(); |
||||||
|
this.gameRoot = gameRoot; |
||||||
|
this.nodeId = nodeId; |
||||||
|
} |
||||||
|
|
||||||
|
execute() { |
||||||
|
try { |
||||||
|
// Use existing root.go method
|
||||||
|
this.gameRoot.go(this.nodeId); |
||||||
|
return true; |
||||||
|
} catch (error) { |
||||||
|
console.error("MapMoveCommand execution failed:", error); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getDescription() { |
||||||
|
return `Move to Node: ${this.nodeId}`; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
import { Command } from './Command.js'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Command for picking a reward card |
||||||
|
* Not undoable as it modifies deck permanently |
||||||
|
*/ |
||||||
|
export class PickRewardCommand extends Command { |
||||||
|
constructor(gameRoot, rewardIndex) { |
||||||
|
super(); |
||||||
|
this.gameRoot = gameRoot; |
||||||
|
this.rewardIndex = rewardIndex; |
||||||
|
} |
||||||
|
|
||||||
|
execute() { |
||||||
|
if (this.gameRoot.screen !== 'reward') { |
||||||
|
console.warn("Cannot pick reward - not on reward screen"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
// Use existing reward selection logic
|
||||||
|
this.gameRoot.takeReward(this.rewardIndex); |
||||||
|
return true; |
||||||
|
} catch (error) { |
||||||
|
console.error("PickRewardCommand execution failed:", error); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
canUndo() { |
||||||
|
// Reward picks are not undoable as they modify deck
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
getDescription() { |
||||||
|
const reward = this.gameRoot.rewards?.[this.rewardIndex]; |
||||||
|
const cardName = reward?.name || 'Unknown Card'; |
||||||
|
return `Pick Reward: ${cardName}`; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
import { Command } from './Command.js'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Command for playing a card in battle |
||||||
|
* Wraps the existing root.play() method |
||||||
|
*/ |
||||||
|
export class PlayCardCommand extends Command { |
||||||
|
constructor(gameRoot, cardIndex) { |
||||||
|
super(); |
||||||
|
this.gameRoot = gameRoot; |
||||||
|
this.cardIndex = cardIndex; |
||||||
|
} |
||||||
|
|
||||||
|
execute() { |
||||||
|
try { |
||||||
|
// Use existing root.play method (which now creates proper battle context)
|
||||||
|
this.gameRoot.play(this.cardIndex); |
||||||
|
return true; |
||||||
|
} catch (error) { |
||||||
|
console.error("PlayCardCommand execution failed:", error); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getDescription() { |
||||||
|
const card = this.gameRoot.player.hand[this.cardIndex]; |
||||||
|
const cardName = card?.name || 'Unknown Card'; |
||||||
|
return `Play Card: ${cardName}`; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
import { Command } from './Command.js'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Command for rest site actions (heal, upgrade) |
||||||
|
* Wraps the existing rest action logic |
||||||
|
*/ |
||||||
|
export class RestActionCommand extends Command { |
||||||
|
constructor(gameRoot, action) { |
||||||
|
super(); |
||||||
|
this.gameRoot = gameRoot; |
||||||
|
this.action = action; // 'heal' or 'upgrade'
|
||||||
|
} |
||||||
|
|
||||||
|
execute() { |
||||||
|
try { |
||||||
|
if (this.action === 'heal') { |
||||||
|
// Heal 20% of max HP (same as current logic)
|
||||||
|
const heal = Math.floor(this.gameRoot.player.maxHp * 0.2); |
||||||
|
this.gameRoot.player.hp = Math.min(this.gameRoot.player.maxHp, this.gameRoot.player.hp + heal); |
||||||
|
this.gameRoot.log(`Healed for ${heal} HP.`); |
||||||
|
this.gameRoot.afterNode(); |
||||||
|
} else if (this.action === 'upgrade') { |
||||||
|
// Show upgrade selection (same as current logic)
|
||||||
|
if (window.gameModules?.render?.renderUpgrade) { |
||||||
|
window.gameModules.render.renderUpgrade(this.gameRoot); |
||||||
|
} |
||||||
|
} else { |
||||||
|
console.warn(`Unknown rest action: ${this.action}`); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} catch (error) { |
||||||
|
console.error("RestActionCommand execution failed:", error); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getDescription() { |
||||||
|
return `Rest Action: ${this.action}`; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
import { Command } from './Command.js'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Command for picking a reward card |
||||||
|
* Wraps the existing root.takeReward() method |
||||||
|
*/ |
||||||
|
export class RewardPickCommand extends Command { |
||||||
|
constructor(gameRoot, rewardIndex) { |
||||||
|
super(); |
||||||
|
this.gameRoot = gameRoot; |
||||||
|
this.rewardIndex = rewardIndex; |
||||||
|
} |
||||||
|
|
||||||
|
execute() { |
||||||
|
try { |
||||||
|
// Use existing root.takeReward method
|
||||||
|
this.gameRoot.takeReward(this.rewardIndex); |
||||||
|
return true; |
||||||
|
} catch (error) { |
||||||
|
console.error("RewardPickCommand execution failed:", error); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getDescription() { |
||||||
|
const reward = this.gameRoot.rewards?.[this.rewardIndex]; |
||||||
|
const cardName = reward?.name || 'Unknown Card'; |
||||||
|
return `Pick Reward: ${cardName}`; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue