diff --git a/src/screens/Ledger.jsx b/src/screens/Ledger.jsx deleted file mode 100644 index 42ae5b7..0000000 --- a/src/screens/Ledger.jsx +++ /dev/null @@ -1,268 +0,0 @@ -import { useState, useEffect, useRef } from 'react' -import { ROUTES, WAYPOINTS, OTIUM_DURATION_MS } from '../constants.js' -import { - applyDispatch, - applyDispatchCost, - applyOtium, - getNewJournalEntry, - getSeenJournalEntries, - isRouteUnlocked, - getChapter, -} from '../gameState.js' - -const CHAPTERS = ['ostia', 'capua', 'brundisium', 'carthago', 'alexandria'] - -export default function Game({ state, onStateChange, onNewGame }) { - const [selectedRoute, setSelectedRoute] = useState(null) - const [dispatch, setDispatch] = useState(null) - const [otium, setOtium] = useState(null) - const [message, setMessage] = useState('') - const [journal, setJournal] = useState(getSeenJournalEntries(state)) - const [newEntryKey, setNewEntryKey] = useState(null) - const tickRef = useRef(null) - const msgRef = useRef(null) - - function showMessage(text, dur = 3500) { - setMessage(text) - clearTimeout(msgRef.current) - msgRef.current = setTimeout(() => setMessage(''), dur) - } - - useEffect(() => { - tickRef.current = setInterval(() => { - const now = Date.now() - - if (dispatch) { - const elapsed = now - dispatch.startMs - if (elapsed >= dispatch.durationMs) { - setDispatch(null) - const entry = getNewJournalEntry(state, dispatch.routeId) - const newState = applyDispatch(state, dispatch.routeId) - const route = ROUTES.find((r) => r.id === dispatch.routeId) - showMessage(`Galley returned. +${route.profit} denarii.`) - if (entry) { - setJournal((j) => [entry, ...j]) - setNewEntryKey(`${dispatch.routeId}-${newState.route_dispatches[dispatch.routeId]}`) - setTimeout(() => setNewEntryKey(null), 5000) - } - setSelectedRoute(null) - onStateChange(newState) - } - } - - if (otium) { - const elapsed = now - otium.startMs - if (elapsed >= OTIUM_DURATION_MS) { - setOtium(null) - const newState = applyOtium(state) - const gain = newState.aut - state.aut - showMessage(`Otium complete. +${gain} auctoritas.`) - onStateChange(newState) - } - } - }, 250) - - return () => clearInterval(tickRef.current) - }, [dispatch, otium, state, onStateChange]) - - function handleSelectRoute(routeId) { - if (dispatch || otium) return - if (!isRouteUnlocked(state, routeId)) return - setSelectedRoute((prev) => prev === routeId ? null : routeId) - } - - function handleDispatch() { - if (!selectedRoute) { showMessage('Select a trade route first.'); return } - if (dispatch) { showMessage('A galley is already at sea.'); return } - if (otium) { showMessage('Finish your otium first.'); return } - const route = ROUTES.find((r) => r.id === selectedRoute) - if (state.den < route.cost) { showMessage('Not enough denarii for this voyage.'); return } - const newState = applyDispatchCost(state, selectedRoute) - onStateChange(newState) - setDispatch({ routeId: selectedRoute, startMs: Date.now(), durationMs: route.duration_ms }) - showMessage(`Galley dispatched on ${route.from} → ${route.to}.`) - } - - function handleOtium() { - if (dispatch) { showMessage('Your galley is at sea. Wait for it to return.'); return } - if (otium) return - setOtium({ startMs: Date.now() }) - showMessage('You rest. The harbour sounds fade.') - } - - function handleNewGame() { - if (!window.confirm('Abandon this ledger and begin a new one?')) return - onNewGame() - } - - let progressPct = 0 - let progressLabel = '' - let progressSub = '' - let isOtium = false - - if (dispatch) { - const elapsed = Date.now() - dispatch.startMs - progressPct = Math.min((elapsed / dispatch.durationMs) * 100, 100) - const route = ROUTES.find((r) => r.id === dispatch.routeId) - progressLabel = `${WAYPOINTS[route.from].name} → ${WAYPOINTS[route.to].name}` - progressSub = `${Math.round(progressPct)}%` - } else if (otium) { - const elapsed = Date.now() - otium.startMs - progressPct = Math.min((elapsed / OTIUM_DURATION_MS) * 100, 100) - progressLabel = 'resting...' - progressSub = `${Math.round(progressPct)}%` - isOtium = true - } - - const chapter = getChapter(state.den, state.aut) - const currentLocation = WAYPOINTS[CHAPTERS[chapter - 1]] - const busy = !!dispatch || !!otium - - return ( -
-
-

OTIVM

-

mercator romanus · anno DCCXL ab urbe condita

-
- -
- {CHAPTERS.map((id, i) => { - const wp = WAYPOINTS[id] - const ch = i + 1 - const cls = ch < chapter ? 'reached' : ch === chapter ? 'current' : '' - return ( - - {wp.name} - {i < CHAPTERS.length - 1 && ( - - )} - - ) - })} -
- -
-
-
Denarii
-
{Math.floor(state.den).toLocaleString()}
-
in the strongbox
-
-
-
Auctoritas
-
{Math.floor(state.aut)}
-
reputation
-
-
-
Dispatches
-
{state.dispatches}
-
completed
-
-
-
Location
-
- {currentLocation.name} -
-
chapter {['I','II','III','IV','V'][chapter - 1]}
-
-
- - {(dispatch || otium) && ( -
-
- {progressLabel} - {progressSub} -
-
-
-
-
- )} - -
{message}
- -

Trade routes

-
- {ROUTES.map((route) => { - const unlocked = isRouteUnlocked(state, route.id) - const selected = selectedRoute === route.id - const fromWp = WAYPOINTS[route.from] - const toWp = WAYPOINTS[route.to] - return ( -
handleSelectRoute(route.id)} - > -
{fromWp.name} → {toWp.name}
-
{route.goods}
-
{route.desc}
-
- +{route.profit - route.cost} dn - {Math.round(route.duration_ms / 1000)}s -
- {!unlocked && ( - - {state.den < route.unlock_den - ? `${route.unlock_den.toLocaleString()} dn required` - : `${route.unlock_aut} auctoritas required`} - - )} -
- ) - })} -
- -
- - -
- -

Merchant's journal

-
- {journal.length === 0 && ( -
-
Day 1 · Ostia
-
- The harbour smells of pitch and ambition. I have fifty denarii - and a single battered ledger. The factor tells me the olive route - to Capua is open. I shall begin there, as merchants have begun - since the first ships crossed the Tyrrhenian. -
-
- )} - {journal.map((entry, i) => { - const key = `${entry.day}-${i}` - const isNew = newEntryKey && i === 0 - return ( -
-
{entry.day}
-
{entry.text}
-
- ) - })} -
- -
- Your save code: {state.token} - -
-
- ) -} diff --git a/src/screens/Prologue.jsx b/src/screens/Prologue.jsx deleted file mode 100644 index a31864d..0000000 --- a/src/screens/Prologue.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useState } from 'react' -import { BACKGROUNDS } from '../constants.js' - -// Prologue screen — shown on the 'Prologue' tab. -// If background_id is null: shows background selection UI. -// If background_id is set: shows read-only summary of chosen background. -// Props: -// state — current game state -// onSelectBackground(id) — called when player confirms a background - -export default function Prologue({ state, onSelectBackground }) { - const [selected, setSelected] = useState(null) - const chosen = BACKGROUNDS.find(b => b.id === state.background_id) || null - - // Read-only view — background already chosen - if (chosen) { - return ( -
-
- Your origin -

{chosen.name}

- {chosen.latin} -
-

{chosen.summary}

-
- Starting funds: {chosen.starting_den} denarii -
-
- ) - } - - // Selection view — background_id is null - return ( -
-
- Before the first dispatch -

Who were you?

-

- Your background shapes your starting parameters. This choice is permanent. -

-
- -
- {BACKGROUNDS.map(bg => ( - - ))} -
- -
- -
-
- ) -}