Wire run loop with victory rewards, defeat restart, and combat chaining
This commit is contained in:
parent
4e457b80af
commit
77f65ace98
3 changed files with 132 additions and 51 deletions
|
|
@ -34,7 +34,11 @@
|
||||||
<section id="hand"></section>
|
<section id="hand"></section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="overlay" hidden></div>
|
<div id="overlay" hidden>
|
||||||
|
<div id="overlay-text"></div>
|
||||||
|
<div id="reward-cards"></div>
|
||||||
|
<button type="button" id="skip-btn" hidden>skip</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="module" src="src/main.js"></script>
|
<script type="module" src="src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
139
src/main.js
139
src/main.js
|
|
@ -2,18 +2,76 @@ import { getCard, initCards } from "./cards.js";
|
||||||
import { checkCombatEnd, resolveEnemyTurn, startTurn } from "./combat.js";
|
import { checkCombatEnd, resolveEnemyTurn, startTurn } from "./combat.js";
|
||||||
import { initEnemies } from "./enemies.js";
|
import { initEnemies } from "./enemies.js";
|
||||||
import { render } from "./render.js";
|
import { render } from "./render.js";
|
||||||
import { createCombatState, endTurn, playCard } from "./state.js";
|
import {
|
||||||
|
createRunState,
|
||||||
|
pickReward,
|
||||||
|
revealRewards,
|
||||||
|
skipRewards,
|
||||||
|
} from "./run.js";
|
||||||
|
import { createCombatFromRun, endTurn, playCard } from "./state.js";
|
||||||
|
|
||||||
let state = null;
|
let state = null;
|
||||||
|
let run = null;
|
||||||
|
let revealed = null;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await Promise.all([initCards(), initEnemies()]);
|
await Promise.all([initCards(), initEnemies()]);
|
||||||
state = createCombatState("ironclad", "jaw_worm");
|
startNewRun();
|
||||||
state = startTurn(state);
|
|
||||||
render(state);
|
|
||||||
bindEvents();
|
bindEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startNewRun() {
|
||||||
|
run = createRunState("ironclad");
|
||||||
|
revealed = null;
|
||||||
|
startNextCombat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startNextCombat() {
|
||||||
|
run = { ...run, combatCount: run.combatCount + 1 };
|
||||||
|
state = createCombatFromRun(run, "jaw_worm");
|
||||||
|
state = startTurn(state);
|
||||||
|
revealed = null;
|
||||||
|
render(state, revealed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncRunHp() {
|
||||||
|
run = { ...run, hp: state.players[0].hp };
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVictory() {
|
||||||
|
syncRunHp();
|
||||||
|
const result = revealRewards(run);
|
||||||
|
revealed = result.revealed;
|
||||||
|
run = result.run;
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
combat: { ...state.combat, phase: "rewards" },
|
||||||
|
};
|
||||||
|
render(state, revealed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDefeat() {
|
||||||
|
syncRunHp();
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
combat: { ...state.combat, phase: "ended", result: "defeat" },
|
||||||
|
};
|
||||||
|
render(state, revealed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkEnd() {
|
||||||
|
const end = checkCombatEnd(state);
|
||||||
|
if (end === "victory") {
|
||||||
|
handleVictory();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (end === "defeat") {
|
||||||
|
handleDefeat();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function bindEvents() {
|
function bindEvents() {
|
||||||
document.getElementById("hand").addEventListener("click", (e) => {
|
document.getElementById("hand").addEventListener("click", (e) => {
|
||||||
const cardEl = e.target.closest(".card");
|
const cardEl = e.target.closest(".card");
|
||||||
|
|
@ -22,7 +80,7 @@ function bindEvents() {
|
||||||
|
|
||||||
if (state.combat.selectedCard === index) {
|
if (state.combat.selectedCard === index) {
|
||||||
state = { ...state, combat: { ...state.combat, selectedCard: null } };
|
state = { ...state, combat: { ...state.combat, selectedCard: null } };
|
||||||
render(state);
|
render(state, revealed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,27 +90,18 @@ function bindEvents() {
|
||||||
const card = getCard(cardId);
|
const card = getCard(cardId);
|
||||||
|
|
||||||
if (card.type === "skill") {
|
if (card.type === "skill") {
|
||||||
// auto-play skills (they target self)
|
|
||||||
const result = playCard(state, index);
|
const result = playCard(state, index);
|
||||||
if (result === null) {
|
if (result === null) {
|
||||||
// not enough energy
|
|
||||||
state = { ...state, combat: { ...state.combat, selectedCard: null } };
|
state = { ...state, combat: { ...state.combat, selectedCard: null } };
|
||||||
render(state);
|
render(state, revealed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state = { ...result, combat: { ...result.combat, selectedCard: null } };
|
state = { ...result, combat: { ...result.combat, selectedCard: null } };
|
||||||
const end = checkCombatEnd(state);
|
if (!checkEnd()) render(state, revealed);
|
||||||
if (end) {
|
|
||||||
state = {
|
|
||||||
...state,
|
|
||||||
combat: { ...state.combat, phase: "ended", result: end },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
render(state);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(state);
|
render(state, revealed);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("enemy-zone").addEventListener("click", () => {
|
document.getElementById("enemy-zone").addEventListener("click", () => {
|
||||||
|
|
@ -62,23 +111,12 @@ function bindEvents() {
|
||||||
const result = playCard(state, state.combat.selectedCard);
|
const result = playCard(state, state.combat.selectedCard);
|
||||||
if (result === null) {
|
if (result === null) {
|
||||||
state = { ...state, combat: { ...state.combat, selectedCard: null } };
|
state = { ...state, combat: { ...state.combat, selectedCard: null } };
|
||||||
render(state);
|
render(state, revealed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = { ...result, combat: { ...result.combat, selectedCard: null } };
|
state = { ...result, combat: { ...result.combat, selectedCard: null } };
|
||||||
|
if (!checkEnd()) render(state, revealed);
|
||||||
const end = checkCombatEnd(state);
|
|
||||||
if (end) {
|
|
||||||
state = {
|
|
||||||
...state,
|
|
||||||
combat: { ...state.combat, phase: "ended", result: end },
|
|
||||||
};
|
|
||||||
render(state);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(state);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document
|
document
|
||||||
|
|
@ -87,24 +125,41 @@ function bindEvents() {
|
||||||
if (state.combat.phase !== "player_turn") return;
|
if (state.combat.phase !== "player_turn") return;
|
||||||
|
|
||||||
state = endTurn(state);
|
state = endTurn(state);
|
||||||
render(state);
|
render(state, revealed);
|
||||||
|
|
||||||
await delay(800);
|
await delay(800);
|
||||||
state = resolveEnemyTurn(state);
|
state = resolveEnemyTurn(state);
|
||||||
|
|
||||||
const end = checkCombatEnd(state);
|
if (!checkEnd()) {
|
||||||
if (end) {
|
state = startTurn(state);
|
||||||
state = {
|
render(state, revealed);
|
||||||
...state,
|
|
||||||
combat: { ...state.combat, phase: "ended", result: end },
|
|
||||||
};
|
|
||||||
render(state);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state = startTurn(state);
|
|
||||||
render(state);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("reward-cards").addEventListener("click", (e) => {
|
||||||
|
const img = e.target.closest(".reward-card");
|
||||||
|
if (!img || !revealed) return;
|
||||||
|
const cardId = img.dataset.cardId;
|
||||||
|
const index = revealed.indexOf(cardId);
|
||||||
|
if (index === -1) return;
|
||||||
|
run = pickReward(run, revealed, index);
|
||||||
|
startNextCombat();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("skip-btn").addEventListener("click", () => {
|
||||||
|
if (!revealed) return;
|
||||||
|
run = skipRewards(run, revealed);
|
||||||
|
startNextCombat();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("overlay").addEventListener("click", (e) => {
|
||||||
|
// only restart on defeat overlay click (not reward cards or skip btn)
|
||||||
|
if (state.combat.phase !== "ended" || state.combat.result !== "defeat")
|
||||||
|
return;
|
||||||
|
if (e.target.closest("#reward-cards") || e.target.closest("#skip-btn"))
|
||||||
|
return;
|
||||||
|
startNewRun();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function delay(ms) {
|
function delay(ms) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { getCard } from "./cards.js";
|
import { getCard } from "./cards.js";
|
||||||
import { resolveEnemyAction } from "./enemies.js";
|
import { resolveEnemyAction } from "./enemies.js";
|
||||||
|
|
||||||
export function render(state) {
|
export function render(state, revealed) {
|
||||||
renderEnemy(state);
|
renderEnemy(state);
|
||||||
renderInfoBar(state);
|
renderInfoBar(state);
|
||||||
renderHand(state);
|
renderHand(state);
|
||||||
renderOverlay(state);
|
renderOverlay(state, revealed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEnemy(state) {
|
function renderEnemy(state) {
|
||||||
|
|
@ -103,16 +103,38 @@ function renderHand(state) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOverlay(state) {
|
function renderOverlay(state, revealed) {
|
||||||
const overlay = document.getElementById("overlay");
|
const overlay = document.getElementById("overlay");
|
||||||
const result = state.combat.phase === "ended" ? state.combat.result : null;
|
const overlayText = document.getElementById("overlay-text");
|
||||||
if (result === "victory") {
|
const rewardCards = document.getElementById("reward-cards");
|
||||||
|
const skipBtn = document.getElementById("skip-btn");
|
||||||
|
|
||||||
|
if (state.combat.phase === "rewards" && revealed) {
|
||||||
overlay.hidden = false;
|
overlay.hidden = false;
|
||||||
overlay.textContent = "victory";
|
overlayText.textContent = "card reward";
|
||||||
} else if (result === "defeat") {
|
rewardCards.innerHTML = "";
|
||||||
|
for (const cardId of revealed) {
|
||||||
|
const card = getCard(cardId);
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = card.image || "";
|
||||||
|
img.alt = card.name;
|
||||||
|
img.title = `${card.name} (${card.cost}) - ${card.description}`;
|
||||||
|
img.dataset.cardId = cardId;
|
||||||
|
img.className = "reward-card";
|
||||||
|
rewardCards.appendChild(img);
|
||||||
|
}
|
||||||
|
skipBtn.hidden = false;
|
||||||
|
} else if (state.combat.phase === "ended") {
|
||||||
overlay.hidden = false;
|
overlay.hidden = false;
|
||||||
overlay.textContent = "defeat";
|
overlayText.textContent =
|
||||||
|
state.combat.result === "defeat"
|
||||||
|
? "defeat — click to restart"
|
||||||
|
: "victory";
|
||||||
|
rewardCards.innerHTML = "";
|
||||||
|
skipBtn.hidden = true;
|
||||||
} else {
|
} else {
|
||||||
overlay.hidden = true;
|
overlay.hidden = true;
|
||||||
|
rewardCards.innerHTML = "";
|
||||||
|
skipBtn.hidden = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue