Implement enter scores page with per-field and round validation
Each player gets a number input; scores are validated individually (divisible by 5, ≤ 110) and as a round (must sum to −110). Submit stays disabled until all constraints pass. A running sum indicator turns green when the round is valid. Submitting appends the round to game state and persists it to localStorage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+5
-2
@@ -37,12 +37,15 @@ export default function App() {
|
||||
setPage('scoresheet')
|
||||
}
|
||||
|
||||
function handleSubmitScores() {
|
||||
function handleSubmitScores(scores: number[]) {
|
||||
const updated: GameState = { ...game!, rounds: [...game!.rounds, scores] }
|
||||
saveGame(updated)
|
||||
setGame(updated)
|
||||
setPage('scoresheet')
|
||||
}
|
||||
|
||||
if (page === 'title') return <TitleScreen onNewGame={handleNewGame} />
|
||||
if (page === 'setup') return <PlayerSetup onStart={handleStart} />
|
||||
if (page === 'scoresheet') return <ScoreSheet game={game!} onScoreRound={() => setPage('enterscores')} onNewGame={handleNewGame} />
|
||||
if (page === 'enterscores') return <EnterScores onSubmit={handleSubmitScores} />
|
||||
if (page === 'enterscores') return <EnterScores game={game!} onSubmit={handleSubmitScores} />
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
export function isIndividualScoreValid(n: number): boolean {
|
||||
return n % 5 === 0 && n <= 110
|
||||
}
|
||||
|
||||
export function isRoundScoreValid(scores: number[]): boolean {
|
||||
return scores.every(isIndividualScoreValid) && scores.reduce((a, b) => a + b, 0) === -110
|
||||
}
|
||||
|
||||
// Returns totals[i][j] = cumulative score for player j after round i.
|
||||
export function computeRunningTotals(rounds: number[][]): number[][] {
|
||||
const totals: number[][] = []
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.scoreInput {
|
||||
width: 6rem;
|
||||
padding: 0.85rem 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: inherit;
|
||||
text-align: center;
|
||||
border: 1.5px solid var(--color-parchment-border);
|
||||
border-radius: 0.75rem;
|
||||
background: #fdfaf4;
|
||||
}
|
||||
|
||||
.scoreInputError {
|
||||
border-color: #c0392b;
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.sumLine {
|
||||
margin: 0.5rem 0 0;
|
||||
text-align: center;
|
||||
font-size: 0.95rem;
|
||||
color: var(--color-parchment-border);
|
||||
}
|
||||
|
||||
.sumValid {
|
||||
color: #4a9e6a;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -1,13 +1,52 @@
|
||||
import { useState } from 'react'
|
||||
import PageFrame from '../components/PageFrame'
|
||||
import PrimaryButton from '../components/PrimaryButton'
|
||||
import { isIndividualScoreValid, isRoundScoreValid } from '../game/scoring'
|
||||
import { type GameState } from '../types'
|
||||
import styles from './EnterScores.module.css'
|
||||
|
||||
interface Props {
|
||||
onSubmit: () => void
|
||||
game: GameState
|
||||
onSubmit: (scores: number[]) => void
|
||||
}
|
||||
|
||||
export default function EnterScores({ onSubmit }: Props) {
|
||||
export default function EnterScores({ game, onSubmit }: Props) {
|
||||
const [values, setValues] = useState<string[]>(() => game.players.map(() => ''))
|
||||
|
||||
function updateValue(i: number, v: string) {
|
||||
setValues(vs => vs.map((val, idx) => (idx === i ? v : val)))
|
||||
}
|
||||
|
||||
const parsed = values.map(v => parseInt(v, 10))
|
||||
const fieldError = values.map((v, i) => v !== '' && (isNaN(parsed[i]) || !isIndividualScoreValid(parsed[i])))
|
||||
const allFilled = parsed.every(n => !isNaN(n))
|
||||
const sum = parsed.reduce((acc, n) => acc + (isNaN(n) ? 0 : n), 0)
|
||||
const canSubmit = allFilled && isRoundScoreValid(parsed)
|
||||
|
||||
return (
|
||||
<PageFrame title="Enter Scores">
|
||||
<button onClick={onSubmit}>Submit</button>
|
||||
<PageFrame
|
||||
title="Enter Scores"
|
||||
footer={
|
||||
<PrimaryButton onClick={() => onSubmit(parsed)} disabled={!canSubmit}>
|
||||
Submit
|
||||
</PrimaryButton>
|
||||
}
|
||||
>
|
||||
{game.players.map((name, i) => (
|
||||
<div key={i} className={styles.row}>
|
||||
<span className={styles.name}>{name}</span>
|
||||
<input
|
||||
className={`${styles.scoreInput}${fieldError[i] ? ` ${styles.scoreInputError}` : ''}`}
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
value={values[i]}
|
||||
onChange={e => updateValue(i, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<p className={`${styles.sumLine}${sum === -110 ? ` ${styles.sumValid}` : ''}`}>
|
||||
Sum: {sum} / −110
|
||||
</p>
|
||||
</PageFrame>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user