slaywithfriends/src/state.js

279 lines
6.9 KiB
JavaScript

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;
}