Add a persistent high score table

Retiring writes each player's closing balance to a top-ten table in
localStorage, reachable from the title screen and the standings, with
the summer just finished picked out in yellow.

Stored rows are user-editable, so every field is validated on load and
malformed entries are dropped. Storage being unavailable is a normal
case, not an error: the game plays on with an empty table.

Also stops the status line claiming DAY 00 on screens that have no day.
This commit is contained in:
2026-09-08 15:29:35 -07:00
parent 08b2cc5391
commit 71cbb02353
9 changed files with 280 additions and 5 deletions
+13
View File
@@ -16,6 +16,8 @@ export interface GameState {
/** True while road works were in progress yesterday, so they can run on. */
streetCrewYesterday: boolean
retired: boolean
/** Where the high score table was opened from, so BACK can return there. */
scoresReturn: Phase
}
export const initialState = (seed: number): GameState => ({
@@ -30,6 +32,7 @@ export const initialState = (seed: number): GameState => ({
history: [],
streetCrewYesterday: false,
retired: false,
scoresReturn: 'title',
})
export type Action =
@@ -42,6 +45,8 @@ export type Action =
| { type: 'RESOLVE'; results: DayResult[] }
| { type: 'NEXT_DAY' }
| { type: 'RETIRE' }
| { type: 'SHOW_SCORES' }
| { type: 'CLOSE_SCORES' }
| { type: 'RESTART'; seed: number }
/** Players who can still afford to open the stand, in seating order. */
@@ -130,6 +135,14 @@ export function reducer(state: GameState, action: Action): GameState {
case 'RETIRE':
return { ...state, phase: 'gameover', retired: true }
case 'SHOW_SCORES':
return state.phase === 'scores'
? state
: { ...state, phase: 'scores', scoresReturn: state.phase }
case 'CLOSE_SCORES':
return { ...state, phase: state.scoresReturn }
case 'RESTART':
return initialState(action.seed)