Drop the high score wipe; scores leave only by being beaten

The table was clearable behind a confirm. It should not be: a stand
earns its place and loses it only to a better summer, so the control
and clearScores() are gone and the screen says so.
This commit is contained in:
2026-09-08 15:32:31 -07:00
parent 71cbb02353
commit 175589223a
4 changed files with 12 additions and 35 deletions
+5 -1
View File
@@ -41,12 +41,16 @@ Retiring writes every player's closing balance to a top-ten table kept in
from the summer you just finished are picked out in yellow. Ties break on the from the summer you just finished are picked out in yellow. Ties break on the
shorter season, then on the earlier date. shorter season, then on the earlier date.
There is no way to clear the table. A stand leaves the list only by being
pushed off the bottom by a better one, so a good summer stands until somebody
beats it.
The table is per-browser, not per-device, and it is the one piece of state the The table is per-browser, not per-device, and it is the one piece of state the
game keeps between visits. Anything already in storage can be edited by hand, game keeps between visits. Anything already in storage can be edited by hand,
so every field is validated on the way back in and malformed rows are dropped so every field is validated on the way back in and malformed rows are dropped
rather than trusted. If storage is unavailable — a private window, or a browser rather than trusted. If storage is unavailable — a private window, or a browser
set to block site data — the game plays normally and the table simply stays set to block site data — the game plays normally and the table simply stays
empty. **WIPE TABLE** clears it, and asks once before it does. empty.
## How it is put together ## How it is put together
+1 -3
View File
@@ -4,7 +4,7 @@ import { synth } from './audio/synth'
import { WEATHER } from './game/constants' import { WEATHER } from './game/constants'
import { dollars, randomSeed, rollDay, simulate } from './game/engine' import { dollars, randomSeed, rollDay, simulate } from './game/engine'
import { makeRng, type Rng } from './game/rng' import { makeRng, type Rng } from './game/rng'
import { addScores, clearScores, loadScores, type Score } from './game/highscores' import { addScores, loadScores, type Score } from './game/highscores'
import { activePlayers, initialState, reducer } from './game/reducer' import { activePlayers, initialState, reducer } from './game/reducer'
import type { Decision } from './game/types' import type { Decision } from './game/types'
import { BriefingScreen } from './components/BriefingScreen' import { BriefingScreen } from './components/BriefingScreen'
@@ -267,8 +267,6 @@ export default function App() {
synth.select() synth.select()
dispatch({ type: 'CLOSE_SCORES' }) dispatch({ type: 'CLOSE_SCORES' })
}} }}
onClear={() => setScores(clearScores())}
onBlip={blip}
/> />
)} )}
</Fit> </Fit>
+1 -21
View File
@@ -1,4 +1,3 @@
import { useState } from 'react'
import { dollars } from '../game/engine' import { dollars } from '../game/engine'
import { MAX_SCORES, type Score } from '../game/highscores' import { MAX_SCORES, type Score } from '../game/highscores'
import { Btn, Line } from './Crt' import { Btn, Line } from './Crt'
@@ -10,16 +9,11 @@ export function HighScoreScreen({
scores, scores,
highlight, highlight,
onBack, onBack,
onClear,
onBlip,
}: { }: {
scores: Score[] scores: Score[]
highlight: string[] highlight: string[]
onBack: () => void onBack: () => void
onClear: () => void
onBlip: () => void
}) { }) {
const [confirming, setConfirming] = useState(false)
const fresh = new Set(highlight) const fresh = new Set(highlight)
return ( return (
@@ -52,26 +46,12 @@ export function HighScoreScreen({
<Line /> <Line />
<Line className="center dim">TOP {MAX_SCORES}, KEPT IN THIS BROWSER.</Line> <Line className="center dim">TOP {MAX_SCORES}, KEPT IN THIS BROWSER.</Line>
<Line className="center dim">A STAND LEAVES ONLY BY BEING BEATEN.</Line>
<Line /> <Line />
<div className="row center"> <div className="row center">
<Btn kind="primary" onClick={onBack}> <Btn kind="primary" onClick={onBack}>
BACK BACK
</Btn> </Btn>
{scores.length > 0 && (
<Btn
onClick={() => {
onBlip()
if (confirming) {
onClear()
setConfirming(false)
} else {
setConfirming(true)
}
}}
>
{confirming ? 'REALLY WIPE?' : 'WIPE TABLE'}
</Btn>
)}
</div> </div>
</div> </div>
) )
+5 -10
View File
@@ -67,7 +67,11 @@ const newId = () =>
? crypto.randomUUID() ? crypto.randomUUID()
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}` : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
/** Returns the saved table and the ids of the entries this call added. */ /**
* Returns the saved table and the ids of the entries this call added. The
* table only ever loses a row by being pushed past MAX_SCORES by a better
* one - there is no way to clear it.
*/
export function addScores(entries: NewScore[]): { table: Score[]; added: string[] } { export function addScores(entries: NewScore[]): { table: Score[]; added: string[] } {
const at = Date.now() const at = Date.now()
const fresh: Score[] = entries.map((e) => ({ ...e, id: newId(), at })) const fresh: Score[] = entries.map((e) => ({ ...e, id: newId(), at }))
@@ -76,12 +80,3 @@ export function addScores(entries: NewScore[]): { table: Score[]; added: string[
const kept = new Set(table.map((s) => s.id)) const kept = new Set(table.map((s) => s.id))
return { table, added: fresh.filter((s) => kept.has(s.id)).map((s) => s.id) } return { table, added: fresh.filter((s) => kept.has(s.id)).map((s) => s.id) }
} }
export function clearScores(): Score[] {
try {
window.localStorage.removeItem(KEY)
} catch {
// Ignored - the table is rendered from the return value either way.
}
return []
}