import { getCard, getStarterDeck } from "./cards.js"; import { resolveEffects } from "./effects.js"; import { getEnemy } from "./enemies.js"; function makePlayer(character, index) { return { id: `player_${index}`, character, hp: 11, maxHp: 11, energy: 3, maxEnergy: 3, block: 0, strength: 0, vulnerable: 0, weak: 0, drawPile: shuffle([...getStarterDeck(character)]), hand: [], discardPile: [], exhaustPile: [], powers: [], }; } function makeEnemy(enemyId, index) { const enemy = getEnemy(enemyId); return { id: enemy.id, instanceId: `${enemy.id}_${index}`, name: enemy.name, hp: enemy.hp, maxHp: enemy.hp, block: 0, strength: 0, vulnerable: 0, weak: 0, actionType: enemy.actionType, actions: enemy.actions, actionTrack: enemy.actionTrack || null, trackPosition: 0, row: index, }; } export function createCombatState(characterOrChars, enemyIdOrIds) { const characters = Array.isArray(characterOrChars) ? characterOrChars : [characterOrChars]; const enemyIds = Array.isArray(enemyIdOrIds) ? enemyIdOrIds : [enemyIdOrIds]; const players = characters.map((c, i) => makePlayer(c, i)); const enemies = enemyIds.map((id, i) => makeEnemy(id, i)); const state = { players, enemies, combat: { turn: 1, phase: "player_turn", dieResult: null, selectedCard: null, log: [], playerCount: players.length, activePlayerIndex: null, // TODO: unused — remove once no callers remain playersReady: [], }, }; // backward-compat aliases — only valid for single-enemy encounters state.player = players[0]; state.enemy = enemies[0]; return state; } export function createCombatFromRun(run, enemyIdOrIds) { const enemyIds = Array.isArray(enemyIdOrIds) ? enemyIdOrIds : [enemyIdOrIds]; const enemies = enemyIds.map((id, i) => makeEnemy(id, i)); const player = { id: "player_0", character: run.character, hp: run.hp, maxHp: run.maxHp, energy: 3, maxEnergy: 3, block: 0, strength: 0, vulnerable: 0, weak: 0, drawPile: shuffle([...run.deck]), hand: [], discardPile: [], exhaustPile: [], powers: [], }; const state = { players: [player], enemies, combat: { turn: 1, phase: "player_turn", dieResult: null, selectedCard: null, log: [], playerCount: 1, activePlayerIndex: null, playersReady: [], }, }; state.player = player; state.enemy = enemies[0]; return state; } export function drawCards(state, playerIndexOrCount, count) { // drawCards(state, count) — old single-player form // drawCards(state, playerIndex, count) — new indexed form let playerIndex, drawCount; if (count === undefined) { playerIndex = 0; drawCount = playerIndexOrCount; } else { playerIndex = playerIndexOrCount; drawCount = count; } const player = state.players[playerIndex]; let drawPile = [...player.drawPile]; let discardPile = [...player.discardPile]; const hand = [...player.hand]; for (let i = 0; i < drawCount; i++) { if (drawPile.length === 0) { drawPile = shuffle(discardPile); discardPile = []; } if (drawPile.length > 0) { hand.push(drawPile.pop()); } } const updatedPlayer = { ...player, drawPile, hand, discardPile }; const players = state.players.map((p, i) => i === playerIndex ? updatedPlayer : p, ); return { ...state, players, // keep top-level compat alias in sync player: players[0], }; } export function playCard(state, handIndex, opts = {}) { const playerIndex = opts.playerIndex ?? 0; const enemyIndex = opts.targetIndex ?? 0; const player = state.players[playerIndex]; const cardId = player.hand[handIndex]; const card = getCard(cardId); if (card.keywords?.includes("unplayable")) return null; if (player.energy < card.cost) return null; const hand = [...player.hand]; hand.splice(handIndex, 1); const exhausted = card.keywords?.includes("exhaust"); const discardPile = exhausted ? [...player.discardPile] : [...player.discardPile, cardId]; const exhaustPile = exhausted ? [...player.exhaustPile, cardId] : [...player.exhaustPile]; const energy = player.energy - card.cost; const updatedPlayer = { ...player, hand, discardPile, exhaustPile, energy }; const players = state.players.map((p, i) => i === playerIndex ? updatedPlayer : p, ); let next = { ...state, players, player: players[0], }; next = resolveEffects( next, card.effects, { type: "player", index: playerIndex }, { type: "enemy", index: enemyIndex }, ); return next; } export function endTurn(state, playerIndex) { // endTurn(state) — old form: discards all, sets enemy phase // endTurn(state, playerIndex) — new form: marks one player done if (playerIndex === undefined) { const player = state.players[0]; const retained = []; const exhausted = []; const discarded = []; for (const cardId of player.hand) { const card = getCard(cardId); if (card.keywords?.includes("retain")) { retained.push(cardId); } else if (card.keywords?.includes("ethereal")) { exhausted.push(cardId); } else { discarded.push(cardId); } } const updatedPlayer = { ...player, hand: retained, discardPile: [...player.discardPile, ...discarded], exhaustPile: [...player.exhaustPile, ...exhausted], }; const players = state.players.map((p, i) => (i === 0 ? updatedPlayer : p)); return { ...state, players, player: players[0], combat: { ...state.combat, phase: "enemy_turn" }, }; } if (state.combat.playersReady.includes(playerIndex)) return state; const player = state.players[playerIndex]; const retained = []; const exhausted = []; const discarded = []; for (const cardId of player.hand) { const card = getCard(cardId); if (card.keywords?.includes("retain")) { retained.push(cardId); } else if (card.keywords?.includes("ethereal")) { exhausted.push(cardId); } else { discarded.push(cardId); } } const updatedPlayer = { ...player, hand: retained, discardPile: [...player.discardPile, ...discarded], exhaustPile: [...player.exhaustPile, ...exhausted], }; const players = state.players.map((p, i) => i === playerIndex ? updatedPlayer : p, ); const playersReady = [...state.combat.playersReady, playerIndex]; const allReady = playersReady.length >= state.combat.playerCount; return { ...state, players, player: players[0], combat: { ...state.combat, playersReady, phase: allReady ? "enemy_turn" : state.combat.phase, }, }; } export function shuffle(arr) { const a = [...arr]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; }