Feature: implement return to town and resume dungeon flow with state management

This commit is contained in:
Keith Solomon
2026-03-18 18:42:43 -05:00
parent 626d5ca05c
commit a4d2890cd9
6 changed files with 192 additions and 36 deletions

View File

@@ -9,6 +9,8 @@ import {
isCurrentRoomCombatReady,
resolveRunEnemyTurn,
resolveRunPlayerTurn,
resumeDungeon,
returnToTown,
startCombatInCurrentRoom,
travelCurrentExit,
} from "@/rules/runState";
@@ -54,6 +56,7 @@ function App() {
const currentRoom = run.currentRoomId ? currentLevel?.rooms[run.currentRoomId] : undefined;
const availableMoves = getAvailableMoves(run);
const combatReadyEncounter = isCurrentRoomCombatReady(run);
const inTown = run.phase === "town";
const handleReset = () => {
setRun(createDemoRun());
@@ -96,6 +99,14 @@ function App() {
);
};
const handleReturnToTown = () => {
setRun((previous) => returnToTown(previous).run);
};
const handleResumeDungeon = () => {
setRun((previous) => resumeDungeon(previous).run);
};
return (
<main className="app-shell">
<section className="hero">
@@ -112,14 +123,21 @@ function App() {
<button className="button button-primary" onClick={handleReset}>
Reset Demo Run
</button>
<button
className="button"
onClick={inTown ? handleResumeDungeon : handleReturnToTown}
disabled={Boolean(run.activeCombat)}
>
{inTown ? "Resume Dungeon" : "Return To Town"}
</button>
<div className="status-chip">
<span>Run Status</span>
<strong>{run.status}</strong>
<span>Run Phase</span>
<strong>{run.phase}</strong>
</div>
</div>
</section>
{combatReadyEncounter && !run.activeCombat ? (
{combatReadyEncounter && !run.activeCombat && !inTown ? (
<section className="alert-banner">
<div>
<span className="alert-kicker">Encounter Ready</span>
@@ -175,9 +193,47 @@ function App() {
<p className="supporting-text">
Run rewards: {run.xpGained} XP earned, {run.defeatedCreatureIds.length} foes defeated.
</p>
<p className="supporting-text">
{inTown
? `The party is currently in town${run.lastTownAt ? ` as of ${new Date(run.lastTownAt).toLocaleString()}` : ""}.`
: "The party is still delving below ground."}
</p>
</article>
<article className="panel">
{inTown ? (
<article className="panel panel-town-hub">
<div className="panel-header">
<h2>Town Hub</h2>
<span>Between Delves</span>
</div>
<h3 className="room-title">Safe Harbor</h3>
<p className="supporting-text">
You are out of the dungeon. Review the current expedition, catch your breath, and
then resume the delve from the same level when you are ready.
</p>
<div className="town-summary-grid">
<div className="encounter-box">
<span className="encounter-label">Current Gold</span>
<strong>{run.adventurerSnapshot.inventory.currency.gold}</strong>
</div>
<div className="encounter-box">
<span className="encounter-label">Rooms Found</span>
<strong>{currentLevel?.discoveredRoomOrder.length ?? 0}</strong>
</div>
<div className="encounter-box">
<span className="encounter-label">Foes Defeated</span>
<strong>{run.defeatedCreatureIds.length}</strong>
</div>
</div>
<div className="button-row">
<button className="button button-primary" onClick={handleResumeDungeon}>
Resume Delve
</button>
</div>
</article>
) : (
<>
<article className="panel">
<div className="panel-header">
<h2>Current Room</h2>
<span>Level {run.currentLevel}</span>
@@ -318,6 +374,8 @@ function App() {
</p>
)}
</article>
</>
)}
<article className="panel panel-log">
<div className="panel-header">