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>
|
||||
</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>
|
||||
</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 { initEnemies } from "./enemies.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 run = null;
|
||||
let revealed = null;
|
||||
|
||||
async function init() {
|
||||
await Promise.all([initCards(), initEnemies()]);
|
||||
state = createCombatState("ironclad", "jaw_worm");
|
||||
state = startTurn(state);
|
||||
render(state);
|
||||
startNewRun();
|
||||
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() {
|
||||
document.getElementById("hand").addEventListener("click", (e) => {
|
||||
const cardEl = e.target.closest(".card");
|
||||
|
|
@ -22,7 +80,7 @@ function bindEvents() {
|
|||
|
||||
if (state.combat.selectedCard === index) {
|
||||
state = { ...state, combat: { ...state.combat, selectedCard: null } };
|
||||
render(state);
|
||||
render(state, revealed);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -32,27 +90,18 @@ function bindEvents() {
|
|||
const card = getCard(cardId);
|
||||
|
||||
if (card.type === "skill") {
|
||||
// auto-play skills (they target self)
|
||||
const result = playCard(state, index);
|
||||
if (result === null) {
|
||||
// not enough energy
|
||||
state = { ...state, combat: { ...state.combat, selectedCard: null } };
|
||||
render(state);
|
||||
render(state, revealed);
|
||||
return;
|
||||
}
|
||||
state = { ...result, combat: { ...result.combat, selectedCard: null } };
|
||||
const end = checkCombatEnd(state);
|
||||
if (end) {
|
||||
state = {
|
||||
...state,
|
||||
combat: { ...state.combat, phase: "ended", result: end },
|
||||
};
|
||||
}
|
||||
render(state);
|
||||
if (!checkEnd()) render(state, revealed);
|
||||
return;
|
||||
}
|
||||
|
||||
render(state);
|
||||
render(state, revealed);
|
||||
});
|
||||
|
||||
document.getElementById("enemy-zone").addEventListener("click", () => {
|
||||
|
|
@ -62,23 +111,12 @@ function bindEvents() {
|
|||
const result = playCard(state, state.combat.selectedCard);
|
||||
if (result === null) {
|
||||
state = { ...state, combat: { ...state.combat, selectedCard: null } };
|
||||
render(state);
|
||||
render(state, revealed);
|
||||
return;
|
||||
}
|
||||
|
||||
state = { ...result, combat: { ...result.combat, selectedCard: null } };
|
||||
|
||||
const end = checkCombatEnd(state);
|
||||
if (end) {
|
||||
state = {
|
||||
...state,
|
||||
combat: { ...state.combat, phase: "ended", result: end },
|
||||
};
|
||||
render(state);
|
||||
return;
|
||||
}
|
||||
|
||||
render(state);
|
||||
if (!checkEnd()) render(state, revealed);
|
||||
});
|
||||
|
||||
document
|
||||
|
|
@ -87,24 +125,41 @@ function bindEvents() {
|
|||
if (state.combat.phase !== "player_turn") return;
|
||||
|
||||
state = endTurn(state);
|
||||
render(state);
|
||||
render(state, revealed);
|
||||
|
||||
await delay(800);
|
||||
state = resolveEnemyTurn(state);
|
||||
|
||||
const end = checkCombatEnd(state);
|
||||
if (end) {
|
||||
state = {
|
||||
...state,
|
||||
combat: { ...state.combat, phase: "ended", result: end },
|
||||
};
|
||||
render(state);
|
||||
return;
|
||||
if (!checkEnd()) {
|
||||
state = startTurn(state);
|
||||
render(state, revealed);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { getCard } from "./cards.js";
|
||||
import { resolveEnemyAction } from "./enemies.js";
|
||||
|
||||
export function render(state) {
|
||||
export function render(state, revealed) {
|
||||
renderEnemy(state);
|
||||
renderInfoBar(state);
|
||||
renderHand(state);
|
||||
renderOverlay(state);
|
||||
renderOverlay(state, revealed);
|
||||
}
|
||||
|
||||
function renderEnemy(state) {
|
||||
|
|
@ -103,16 +103,38 @@ function renderHand(state) {
|
|||
});
|
||||
}
|
||||
|
||||
function renderOverlay(state) {
|
||||
function renderOverlay(state, revealed) {
|
||||
const overlay = document.getElementById("overlay");
|
||||
const result = state.combat.phase === "ended" ? state.combat.result : null;
|
||||
if (result === "victory") {
|
||||
const overlayText = document.getElementById("overlay-text");
|
||||
const rewardCards = document.getElementById("reward-cards");
|
||||
const skipBtn = document.getElementById("skip-btn");
|
||||
|
||||
if (state.combat.phase === "rewards" && revealed) {
|
||||
overlay.hidden = false;
|
||||
overlay.textContent = "victory";
|
||||
} else if (result === "defeat") {
|
||||
overlayText.textContent = "card reward";
|
||||
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.textContent = "defeat";
|
||||
overlayText.textContent =
|
||||
state.combat.result === "defeat"
|
||||
? "defeat — click to restart"
|
||||
: "victory";
|
||||
rewardCards.innerHTML = "";
|
||||
skipBtn.hidden = true;
|
||||
} else {
|
||||
overlay.hidden = true;
|
||||
rewardCards.innerHTML = "";
|
||||
skipBtn.hidden = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue