@ttt-productions/q-core 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/business.js +2 -2
- package/dist/constants/business.js.map +1 -1
- package/dist/constants/media.js +1 -1
- package/dist/constants/media.js.map +1 -1
- package/dist/constants/sports.js +1 -1
- package/dist/constants/sports.js.map +1 -1
- package/dist/constants/telemetry.js +3 -3
- package/dist/constants/telemetry.js.map +1 -1
- package/dist/doc-schemas/admin-notifications.js +3 -3
- package/dist/doc-schemas/admin-notifications.js.map +1 -1
- package/dist/doc-schemas/aggregate-stats.js +3 -3
- package/dist/doc-schemas/aggregate-stats.js.map +1 -1
- package/dist/doc-schemas/atoms.d.ts +1 -1
- package/dist/doc-schemas/atoms.js +3 -3
- package/dist/doc-schemas/atoms.js.map +1 -1
- package/dist/doc-schemas/audit-event.js +2 -2
- package/dist/doc-schemas/audit-event.js.map +1 -1
- package/dist/doc-schemas/firestore-primitives.js +1 -1
- package/dist/doc-schemas/firestore-primitives.js.map +1 -1
- package/dist/doc-schemas/game-location.js +2 -2
- package/dist/doc-schemas/game-location.js.map +1 -1
- package/dist/doc-schemas/game-stat-history.js +1 -1
- package/dist/doc-schemas/game-stat-history.js.map +1 -1
- package/dist/doc-schemas/game-stats.js +6 -6
- package/dist/doc-schemas/game-stats.js.map +1 -1
- package/dist/doc-schemas/league-team.js +4 -4
- package/dist/doc-schemas/league-team.js.map +1 -1
- package/dist/doc-schemas/membership-roster-sync.js +2 -2
- package/dist/doc-schemas/membership-roster-sync.js.map +1 -1
- package/dist/doc-schemas/pending-media.d.ts +6 -6
- package/dist/doc-schemas/publicUser.js +1 -1
- package/dist/doc-schemas/publicUser.js.map +1 -1
- package/dist/doc-schemas/registration-start.js +1 -1
- package/dist/doc-schemas/registration-start.js.map +1 -1
- package/dist/doc-schemas/registry.d.ts +3 -3
- package/dist/doc-schemas/sport-game.js +4 -4
- package/dist/doc-schemas/sport-game.js.map +1 -1
- package/dist/doc-schemas/sport-league.js +1 -1
- package/dist/doc-schemas/sport-league.js.map +1 -1
- package/dist/doc-schemas/stats-operation.js +2 -2
- package/dist/doc-schemas/stats-operation.js.map +1 -1
- package/dist/doc-schemas/status-reconcile-queue.js +1 -1
- package/dist/doc-schemas/status-reconcile-queue.js.map +1 -1
- package/dist/doc-schemas/sweep-state.js +2 -2
- package/dist/doc-schemas/sweep-state.js.map +1 -1
- package/dist/doc-schemas/team-membership.js +1 -1
- package/dist/doc-schemas/team-membership.js.map +1 -1
- package/dist/doc-schemas/user-profile.js +1 -1
- package/dist/doc-schemas/user-profile.js.map +1 -1
- package/dist/doc-schemas/waiver-template.js +1 -1
- package/dist/doc-schemas/waiver-template.js.map +1 -1
- package/dist/doc-schemas/waivers-and-hardware.js +3 -3
- package/dist/doc-schemas/waivers-and-hardware.js.map +1 -1
- package/dist/domain-events/domain-events.js +2 -2
- package/dist/domain-events/domain-events.js.map +1 -1
- package/dist/ids/award-grant-ids.js +1 -1
- package/dist/ids/award-grant-ids.js.map +1 -1
- package/dist/ids/compound-ids.js +1 -1
- package/dist/ids/compound-ids.js.map +1 -1
- package/dist/ids/minted-ids.js +1 -1
- package/dist/ids/minted-ids.js.map +1 -1
- package/dist/ids/notification-ids.js +1 -1
- package/dist/ids/notification-ids.js.map +1 -1
- package/dist/ids/stat-event-ids.d.ts +1 -1
- package/dist/ids/stat-event-ids.js +2 -2
- package/dist/ids/stat-event-ids.js.map +1 -1
- package/dist/ids/stats-operation-ids.js +1 -1
- package/dist/ids/stats-operation-ids.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/input-formats/input-formats.js +1 -1
- package/dist/input-formats/input-formats.js.map +1 -1
- package/dist/media/file-origins.js +1 -1
- package/dist/media/file-origins.js.map +1 -1
- package/dist/media/picture-url.js +1 -1
- package/dist/media/picture-url.js.map +1 -1
- package/dist/migration-console/action-ids.js +1 -1
- package/dist/migration-console/action-ids.js.map +1 -1
- package/dist/migration-console/manifest.js +1 -1
- package/dist/migration-console/manifest.js.map +1 -1
- package/dist/notifications/admin-notification-config.js +1 -1
- package/dist/notifications/admin-notification-config.js.map +1 -1
- package/dist/notifications/occurrences.js +1 -1
- package/dist/notifications/occurrences.js.map +1 -1
- package/dist/paths/collections.js +3 -3
- package/dist/paths/collections.js.map +1 -1
- package/dist/paths/picture-owner-paths.js +1 -1
- package/dist/paths/picture-owner-paths.js.map +1 -1
- package/dist/paths/storage-paths.d.ts +1 -1
- package/dist/paths/storage-paths.js +1 -1
- package/dist/paths/storage-paths.js.map +1 -1
- package/dist/paths/utils.d.ts +1 -1
- package/dist/paths/utils.js +1 -1
- package/dist/paths/utils.js.map +1 -1
- package/dist/schemas/admin-notifications.js +1 -1
- package/dist/schemas/admin-notifications.js.map +1 -1
- package/dist/schemas/app-config.js +1 -1
- package/dist/schemas/app-config.js.map +1 -1
- package/dist/schemas/awards.js +1 -1
- package/dist/schemas/awards.js.map +1 -1
- package/dist/schemas/dead-letters.js +2 -2
- package/dist/schemas/dead-letters.js.map +1 -1
- package/dist/schemas/leagues.js +1 -1
- package/dist/schemas/leagues.js.map +1 -1
- package/dist/schemas/migration-console.js +1 -1
- package/dist/schemas/migration-console.js.map +1 -1
- package/dist/schemas/pictures.js +1 -1
- package/dist/schemas/pictures.js.map +1 -1
- package/dist/schemas/schedules.js +2 -2
- package/dist/schemas/schedules.js.map +1 -1
- package/dist/schemas/scoring.js +1 -1
- package/dist/schemas/scoring.js.map +1 -1
- package/dist/schemas/signed-waivers.js +1 -1
- package/dist/schemas/signed-waivers.js.map +1 -1
- package/dist/schemas/stats.js +6 -6
- package/dist/schemas/stats.js.map +1 -1
- package/dist/schemas/team-joining.js +3 -3
- package/dist/schemas/team-joining.js.map +1 -1
- package/dist/schemas/teams.js +7 -7
- package/dist/schemas/teams.js.map +1 -1
- package/dist/schemas/users.js +3 -3
- package/dist/schemas/users.js.map +1 -1
- package/dist/sports/basketball-scoring.js +4 -4
- package/dist/sports/basketball-scoring.js.map +1 -1
- package/dist/sports/bulk-entry.js +1 -1
- package/dist/sports/bulk-entry.js.map +1 -1
- package/dist/sports/contribution.d.ts +5 -5
- package/dist/sports/contribution.d.ts.map +1 -1
- package/dist/sports/contribution.js +4 -4
- package/dist/sports/contribution.js.map +1 -1
- package/dist/sports/curling-scoring.js +2 -2
- package/dist/sports/curling-scoring.js.map +1 -1
- package/dist/sports/football-scoring.js +2 -2
- package/dist/sports/football-scoring.js.map +1 -1
- package/dist/sports/index.js +1 -1
- package/dist/sports/index.js.map +1 -1
- package/dist/sports/ledger-continuity.js +1 -1
- package/dist/sports/ledger-continuity.js.map +1 -1
- package/dist/sports/registry.js +2 -2
- package/dist/sports/registry.js.map +1 -1
- package/dist/sports/replay.js +1 -1
- package/dist/sports/replay.js.map +1 -1
- package/dist/sports/rollup-check.js +1 -1
- package/dist/sports/rollup-check.js.map +1 -1
- package/dist/sports/score-mirror.js +1 -1
- package/dist/sports/score-mirror.js.map +1 -1
- package/dist/sports/scoring-display.d.ts +1 -1
- package/dist/sports/scoring-display.js +2 -2
- package/dist/sports/scoring-display.js.map +1 -1
- package/dist/sports/scoring.d.ts +5 -5
- package/dist/sports/scoring.d.ts.map +1 -1
- package/dist/sports/scoring.js +2 -2
- package/dist/sports/scoring.js.map +1 -1
- package/dist/sports/sport-adapter.d.ts +3 -3
- package/dist/sports/sport-adapter.d.ts.map +1 -1
- package/dist/sports/sport-adapter.js +2 -2
- package/dist/sports/sport-adapter.js.map +1 -1
- package/dist/sports/store-driver.d.ts +1 -1
- package/dist/sports/store-driver.d.ts.map +1 -1
- package/dist/sports/store-driver.js +2 -2
- package/dist/sports/store-driver.js.map +1 -1
- package/dist/types/audit.js +4 -4
- package/dist/types/audit.js.map +1 -1
- package/dist/types/claims.js +1 -1
- package/dist/types/claims.js.map +1 -1
- package/dist/types/picture-owners.js +1 -1
- package/dist/types/picture-owners.js.map +1 -1
- package/dist/types/schedules.js +1 -1
- package/dist/types/schedules.js.map +1 -1
- package/dist/types/sports.js +2 -2
- package/dist/types/sports.js.map +1 -1
- package/dist/types/stats.js +5 -5
- package/dist/types/stats.js.map +1 -1
- package/dist/types/unions.js +2 -2
- package/dist/types/unions.js.map +1 -1
- package/dist/types/uploads.js +1 -1
- package/dist/types/uploads.js.map +1 -1
- package/dist/upload-variables/picture-upload-variables.d.ts +2 -2
- package/dist/upload-variables/sign-hardware-contract-variables.d.ts +1 -1
- package/dist/upload-variables/sign-hardware-contract-variables.js +1 -1
- package/dist/upload-variables/sign-hardware-contract-variables.js.map +1 -1
- package/dist/upload-variables/sign-waiver-variables.d.ts +1 -1
- package/dist/upload-variables/sign-waiver-variables.js +1 -1
- package/dist/upload-variables/sign-waiver-variables.js.map +1 -1
- package/dist/utils/account-status.d.ts +1 -2
- package/dist/utils/account-status.d.ts.map +1 -1
- package/dist/utils/account-status.js +1 -1
- package/dist/utils/account-status.js.map +1 -1
- package/dist/utils/app-routes.js +1 -1
- package/dist/utils/app-routes.js.map +1 -1
- package/dist/utils/league-join-warning.js +1 -1
- package/dist/utils/league-join-warning.js.map +1 -1
- package/dist/utils/league-joining.js +1 -1
- package/dist/utils/league-joining.js.map +1 -1
- package/dist/utils/league-mode.js +1 -1
- package/dist/utils/league-mode.js.map +1 -1
- package/dist/utils/team-creation.js +1 -1
- package/dist/utils/team-creation.js.map +1 -1
- package/dist/utils/team-member-actions.js +1 -1
- package/dist/utils/team-member-actions.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basketball-scoring.js","sourceRoot":"","sources":["../../src/sports/basketball-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The basketball adapter's scoring, display, and bulk-entry members. Basketball is player-scored: a\n// shot credits its scorer's points and its team's score, and — when it names one — its assister's\n// assists, all on the one event (SPORT-003).\n//\n// `apply` and `undo` are one function over an event's effects with opposite signs, so they are exact\n// inverses by construction (SPORT-103).\n\nimport { z } from 'zod';\nimport {\n BASKETBALL_FANTASY_POINT_WEIGHTS,\n BASKETBALL_POINTS_BY_EVENT_TYPE,\n MAX_STAT_EVENTS_PER_WRITE,\n NOT_ON_GAME_ROSTER_MESSAGE,\n} from '../constants/sports.js';\nimport type {\n BasketballPlayerStats,\n BasketballTeamStats,\n GameStatEvent,\n} from '../doc-schemas/game-stats.js';\nimport type { BasketballGameStats, GameStats } from '../doc-schemas/sport-game.js';\nimport { buildGameRosterPlayerId, parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport {\n BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS,\n BASKETBALL_ASSIST_EVENT_TYPE,\n BASKETBALL_BULK_EDIT_EVENT_TYPE,\n BASKETBALL_ONE_POINT_EVENT_TYPE,\n BASKETBALL_PLAYER_STAT_KEYS,\n BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS,\n BASKETBALL_REBOUND_EVENT_TYPE,\n BASKETBALL_SCORE_EVENT_TYPE_KEYS,\n BASKETBALL_STAT_EVENT_TYPE_KEYS,\n BASKETBALL_TEAM_STAT_KEYS,\n BASKETBALL_THREE_POINT_EVENT_TYPE,\n BASKETBALL_TIMEOUT_EVENT_TYPE,\n BASKETBALL_TWO_POINT_EVENT_TYPE,\n} from '../types/sports.js';\nimport type {\n BasketballPlayerStatKey,\n BasketballStatEventType,\n BasketballTeamStatKey,\n} from '../types/sports.js';\nimport { PLAYER_STAT_LINE_KIND_KEYS } from '../types/stats.js';\nimport type { StatLineKind } from '../types/stats.js';\nimport {\n BULK_ENTRY_TOO_LARGE_MESSAGE,\n bulkEntryCountField,\n type BulkEntryContext,\n type PlayerLinesBulkEntry,\n type PlayerLinesValues,\n type StatEventDraft,\n} from './bulk-entry.js';\nimport {\n buildGameContribution,\n decideParticipation,\n type ContributionSpec,\n} from './contribution.js';\nimport {\n FINALIZATION_READY,\n STAT_EVENT_ACCEPTED,\n isAllZeroLine,\n isSportStatEventIn,\n isStatCorrection,\n refuseStatEvent,\n type CorrectableFields,\n type GameTeamLine,\n type SportScoring,\n type SportStatEventBody,\n type StatCorrectionBody,\n type StatEventBody,\n type StatLineValues,\n} from './scoring.js';\nimport {\n describeStoreEvent,\n formatSignedValue,\n type PlayerEventsScoringPanel,\n type Scoreboard,\n type StatEventLabel,\n type StatEventNameResolver,\n type StatFieldLabels,\n} from './scoring-display.js';\n\ntype BasketballEvent = Extract<SportStatEventBody, { type: BasketballStatEventType }>;\ntype PlayerCounters = Record<BasketballPlayerStatKey, number>;\n\nfunction isBasketballEvent(event: StatEventBody | undefined): event is BasketballEvent {\n return isSportStatEventIn(BASKETBALL_STAT_EVENT_TYPE_KEYS, event);\n}\n\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\n// basketball game's (SPORT-002).\nfunction ownStats(state: GameStats): BasketballGameStats {\n return state as BasketballGameStats;\n}\n\nfunction ownTeamLine(line: GameTeamLine): BasketballTeamStats {\n return line as BasketballTeamStats;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Lines\n// ---------------------------------------------------------------------------------------------\n\n/**\n * Fantasy points (FP) of a basketball line: each player counter times its weight. Every stored\n * `fantasyPoints` equals this over the counters of its own line; `deriveLine` is what applies it.\n */\nexport function basketballFantasyPoints(\n line: Pick<BasketballPlayerStats, BasketballPlayerStatKey>,\n): number {\n return BASKETBALL_PLAYER_STAT_KEYS.reduce(\n (total, key) => total + line[key] * BASKETBALL_FANTASY_POINT_WEIGHTS[key],\n 0,\n );\n}\n\nconst FANTASY_POINTS_FIELD = 'fantasyPoints' satisfies keyof BasketballPlayerStats;\nconst PLAYER_LINE_KINDS: readonly StatLineKind[] = PLAYER_STAT_LINE_KIND_KEYS;\n\nexport function emptyBasketballTeamLine(): BasketballTeamStats {\n return Object.fromEntries(\n BASKETBALL_TEAM_STAT_KEYS.map((key) => [key, 0]),\n ) as BasketballTeamStats;\n}\n\nfunction emptyPlayerCounters(): PlayerCounters {\n return Object.fromEntries(BASKETBALL_PLAYER_STAT_KEYS.map((key) => [key, 0])) as PlayerCounters;\n}\n\nfunction counterOf(line: StatLineValues, field: string): number {\n const value = line[field] ?? 0;\n if (typeof value !== 'number')\n throw new TypeError(`A basketball line's ${field} is not a number.`);\n return value;\n}\n\nfunction playerCountersOf(line: StatLineValues | undefined): PlayerCounters {\n const counters = emptyPlayerCounters();\n if (line === undefined) return counters;\n for (const key of BASKETBALL_PLAYER_STAT_KEYS) counters[key] = counterOf(line, key);\n return counters;\n}\n\n// FP is an individual stat: it goes on the four player kinds and on no team kind.\nfunction deriveBasketballLine(kind: StatLineKind, line: StatLineValues): StatLineValues {\n const withoutDerived = Object.fromEntries(\n Object.entries(line).filter(([field]) => field !== FANTASY_POINTS_FIELD),\n );\n if (!PLAYER_LINE_KINDS.includes(kind)) return withoutDerived;\n return {\n ...withoutDerived,\n [FANTASY_POINTS_FIELD]: basketballFantasyPoints(playerCountersOf(line)),\n };\n}\n\n// ---------------------------------------------------------------------------------------------\n// Events and their effects\n// ---------------------------------------------------------------------------------------------\n\nconst CORRECTABLE_FIELDS: CorrectableFields = {\n team: BASKETBALL_TEAM_STAT_KEYS,\n player: BASKETBALL_PLAYER_STAT_KEYS,\n};\n\nconst TALLY_FIELD = {\n [BASKETBALL_REBOUND_EVENT_TYPE]: 'rebounds',\n [BASKETBALL_ASSIST_EVENT_TYPE]: 'assists',\n} as const satisfies Record<\n (typeof BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS)[number],\n BasketballPlayerStatKey\n>;\n\ninterface LineChange<Field extends string> {\n /** The team id of a team line, or the roster-entry id of a player line. */\n readonly line: string;\n readonly field: Field;\n readonly amount: number;\n}\n\ninterface Effects {\n readonly team: readonly LineChange<BasketballTeamStatKey>[];\n readonly player: readonly LineChange<BasketballPlayerStatKey>[];\n}\n\nfunction assisterOf(event: StatEventBody): string | undefined {\n return 'assistedByUserId' in event && typeof event.assistedByUserId === 'string'\n ? event.assistedByUserId\n : undefined;\n}\n\nfunction namesPlayer(event: StatEventBody): boolean {\n return 'playerUserId' in event && event.playerUserId !== undefined;\n}\n\nfunction effectsOf(event: BasketballEvent): Effects {\n if (event.type === BASKETBALL_TIMEOUT_EVENT_TYPE) {\n return { team: [{ line: event.teamId, field: 'timeouts', amount: 1 }], player: [] };\n }\n const scorer = buildGameRosterPlayerId(event.teamId, event.playerUserId);\n if (event.type === BASKETBALL_BULK_EDIT_EVENT_TYPE) {\n // A scorer's points edit keeps the team's score the sum of its players' points.\n return {\n team:\n event.stat === 'points'\n ? [{ line: event.teamId, field: 'score', amount: event.value }]\n : [],\n player: [{ line: scorer, field: event.stat, amount: event.value }],\n };\n }\n\n const team: LineChange<BasketballTeamStatKey>[] = [];\n const player: LineChange<BasketballPlayerStatKey>[] = [];\n const points = BASKETBALL_POINTS_BY_EVENT_TYPE[event.type];\n if (points > 0) {\n team.push({ line: event.teamId, field: 'score', amount: points });\n player.push({ line: scorer, field: 'points', amount: points });\n }\n if (event.type === BASKETBALL_REBOUND_EVENT_TYPE || event.type === BASKETBALL_ASSIST_EVENT_TYPE) {\n player.push({ line: scorer, field: TALLY_FIELD[event.type], amount: 1 });\n }\n const assister = assisterOf(event);\n if (assister !== undefined) {\n player.push({\n line: buildGameRosterPlayerId(event.teamId, assister),\n field: 'assists',\n amount: 1,\n });\n }\n return { team, player };\n}\n\nfunction correctionEffects(event: StatCorrectionBody): Effects {\n const fields =\n event.playerUserId === undefined ? CORRECTABLE_FIELDS.team : CORRECTABLE_FIELDS.player;\n if (!fields.includes(event.stat)) {\n throw new Error(\n `A basketball correction cannot change ${event.stat} on a ${event.playerUserId === undefined ? 'team' : 'player'} line.`,\n );\n }\n // A correction moves only its own field: a player-points correction does not move the team score.\n return event.playerUserId === undefined\n ? {\n team: [\n { line: event.teamId, field: event.stat as BasketballTeamStatKey, amount: event.value },\n ],\n player: [],\n }\n : {\n team: [],\n player: [\n {\n line: buildGameRosterPlayerId(event.teamId, event.playerUserId),\n field: event.stat as BasketballPlayerStatKey,\n amount: event.value,\n },\n ],\n };\n}\n\nfunction applyEffects(\n state: BasketballGameStats,\n effects: Effects,\n sign: 1 | -1,\n): BasketballGameStats {\n const teamStats = { ...state.teamStats };\n for (const change of effects.team) {\n const line = { ...(teamStats[change.line] ?? emptyBasketballTeamLine()) };\n line[change.field] += sign * change.amount;\n teamStats[change.line] = line;\n }\n\n const playerStats = { ...state.playerStats };\n for (const change of effects.player) {\n const counters = playerCountersOf(playerStats[change.line]);\n counters[change.field] += sign * change.amount;\n if (isAllZeroLine(counters, BASKETBALL_PLAYER_STAT_KEYS)) delete playerStats[change.line];\n else\n playerStats[change.line] = deriveBasketballLine(\n 'gamePlayer',\n counters,\n ) as BasketballPlayerStats;\n }\n return { teamStats, playerStats };\n}\n\n// ---------------------------------------------------------------------------------------------\n// Validation — the same rules the bulk form holds, re-checked on the server (SPORT-006)\n// ---------------------------------------------------------------------------------------------\n\nfunction isAssistableShot(type: BasketballStatEventType): boolean {\n return (BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS as readonly string[]).includes(type);\n}\n\nconst validate: SportScoring['validate'] = (stateIn, event, roster) => {\n if (!isBasketballEvent(event)) return refuseStatEvent('eventTypeNotInSport');\n if (!roster.teamIds.includes(event.teamId)) return refuseStatEvent('teamNotInGame');\n if (event.type === BASKETBALL_TIMEOUT_EVENT_TYPE) {\n return namesPlayer(event) || assisterOf(event) !== undefined\n ? refuseStatEvent('playerNotAllowed')\n : STAT_EVENT_ACCEPTED;\n }\n\n const scorer = buildGameRosterPlayerId(event.teamId, event.playerUserId);\n if (!roster.rosterPlayerIds.has(scorer)) return refuseStatEvent('playerNotRostered');\n\n const assister = assisterOf(event);\n if (assister !== undefined) {\n if (!isAssistableShot(event.type) || assister === event.playerUserId)\n return refuseStatEvent('assistNotAllowed');\n if (!roster.rosterPlayerIds.has(buildGameRosterPlayerId(event.teamId, assister))) {\n return refuseStatEvent('assisterNotRostered');\n }\n }\n\n if (event.type === BASKETBALL_BULK_EDIT_EVENT_TYPE) {\n if (!Number.isInteger(event.value) || event.value === 0)\n return refuseStatEvent('bulkValueInvalid');\n const state = ownStats(stateIn);\n const current = playerCountersOf(state.playerStats[scorer])[event.stat];\n if (event.previousValue !== current) return refuseStatEvent('bulkStale');\n if (current + event.value < 0) return refuseStatEvent('bulkValueInvalid');\n const teamScore = (state.teamStats[event.teamId] ?? emptyBasketballTeamLine()).score;\n if (event.stat === 'points' && teamScore + event.value < 0)\n return refuseStatEvent('bulkValueInvalid');\n }\n return STAT_EVENT_ACCEPTED;\n};\n\n// What a completed basketball game adds: each entry's points, rebounds, assists, and FP to its row,\n// and the team's rebounds and assists to its standings line. A present entry with no points,\n// rebounds, or assists is credited no game played — zero points alone is not exclusion.\nconst BASKETBALL_CONTRIBUTION: ContributionSpec = {\n playerStatKeys: BASKETBALL_PLAYER_STAT_KEYS,\n teamSeasonPlayerSums: ['rebounds', 'assists'] satisfies BasketballPlayerStatKey[],\n zeroLineIsNonScoring: true,\n deriveLine: deriveBasketballLine,\n};\n\nexport const BASKETBALL_SCORING: SportScoring = {\n validate,\n apply: (state, event) => {\n if (isStatCorrection(event)) return applyEffects(ownStats(state), correctionEffects(event), 1);\n if (!isBasketballEvent(event))\n throw new Error(`The basketball adapter does not apply ${event.type}.`);\n return applyEffects(ownStats(state), effectsOf(event), 1);\n },\n undo: (state, target) => {\n if (!isBasketballEvent(target))\n throw new Error(`The basketball adapter does not undo ${target.type}.`);\n return applyEffects(ownStats(state), effectsOf(target), -1);\n },\n deriveLine: deriveBasketballLine,\n correctableFields: CORRECTABLE_FIELDS,\n // Played in no rounds, and finalized by hand whenever the scorer chooses.\n nextRound: () => null,\n finalizationReadiness: () => FINALIZATION_READY,\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\n decideParticipation(BASKETBALL_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\n contribution: (game) => buildGameContribution(BASKETBALL_CONTRIBUTION, game),\n};\n\n// ---------------------------------------------------------------------------------------------\n// Display\n// ---------------------------------------------------------------------------------------------\n\nexport const BASKETBALL_EVENT_LABELS = {\n [BASKETBALL_ONE_POINT_EVENT_TYPE]: { button: '+1 PT', play: '1-point basket' },\n [BASKETBALL_TWO_POINT_EVENT_TYPE]: { button: '+2 PT', play: '2-point basket' },\n [BASKETBALL_THREE_POINT_EVENT_TYPE]: { button: '+3 PT', play: '3-point basket' },\n [BASKETBALL_REBOUND_EVENT_TYPE]: { button: 'Rebound', play: 'Rebound' },\n [BASKETBALL_ASSIST_EVENT_TYPE]: { button: 'Assist', play: 'Assist' },\n [BASKETBALL_TIMEOUT_EVENT_TYPE]: { button: 'Timeout', play: 'Timeout' },\n [BASKETBALL_BULK_EDIT_EVENT_TYPE]: { button: 'Bulk entry', play: 'Bulk entry' },\n} as const satisfies Record<BasketballStatEventType, StatEventLabel>;\n\nexport const BASKETBALL_FIELD_LABELS = {\n team: { score: 'Score', timeouts: 'Timeouts' },\n player: { points: 'Points', rebounds: 'Rebounds', assists: 'Assists' },\n} as const satisfies StatFieldLabels<BasketballTeamStatKey, BasketballPlayerStatKey>;\n\nexport function describeBasketballEvent(\n event: GameStatEvent,\n resolve: StatEventNameResolver,\n): string {\n const storeEvent = describeStoreEvent(event, resolve, BASKETBALL_FIELD_LABELS);\n if (storeEvent !== null) return storeEvent;\n if (!isBasketballEvent(event)) return event.type;\n\n const team = resolve.team(event.teamId);\n if (event.type === BASKETBALL_TIMEOUT_EVENT_TYPE)\n return `${team}: ${BASKETBALL_EVENT_LABELS[event.type].play}`;\n\n const player = `${resolve.player(event.playerUserId)} (${team})`;\n if (event.type === BASKETBALL_BULK_EDIT_EVENT_TYPE) {\n const field = BASKETBALL_FIELD_LABELS.player[event.stat].toLowerCase();\n return `${player}: ${formatSignedValue(event.value)} ${field} (${BASKETBALL_EVENT_LABELS[event.type].play.toLowerCase()})`;\n }\n const play = `${player}: ${BASKETBALL_EVENT_LABELS[event.type].play}`;\n const assister = assisterOf(event);\n return assister === undefined ? play : `${play}, assisted by ${resolve.player(assister)}`;\n}\n\nfunction sumOf(\n lines: readonly { readonly rebounds: number; readonly assists: number }[],\n field: 'rebounds' | 'assists',\n): number {\n return lines.reduce((total, line) => total + line[field], 0);\n}\n\nexport const BASKETBALL_SCOREBOARD: Scoreboard = {\n figures: [\n {\n id: 'points',\n label: BASKETBALL_FIELD_LABELS.player.points,\n value: (teamLine) => teamLine.score,\n },\n {\n id: 'rebounds',\n label: BASKETBALL_FIELD_LABELS.player.rebounds,\n value: (_teamLine, sideLines) => sumOf(sideLines, 'rebounds'),\n },\n {\n id: 'assists',\n label: BASKETBALL_FIELD_LABELS.player.assists,\n value: (_teamLine, sideLines) => sumOf(sideLines, 'assists'),\n },\n {\n id: 'timeouts',\n label: BASKETBALL_FIELD_LABELS.team.timeouts,\n value: (teamLine) => ownTeamLine(teamLine).timeouts,\n },\n ],\n jumpBall: true,\n};\n\nexport const BASKETBALL_SCORING_PANEL: PlayerEventsScoringPanel = {\n kind: 'playerEvents',\n playerEventTypes: [\n ...BASKETBALL_SCORE_EVENT_TYPE_KEYS,\n ...BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS,\n ],\n assistableEventTypes: BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS,\n teamEventTypes: [BASKETBALL_TIMEOUT_EVENT_TYPE],\n};\n\n// ---------------------------------------------------------------------------------------------\n// Bulk entry — one row per roster entry, absent entries included\n// ---------------------------------------------------------------------------------------------\n\nexport const BASKETBALL_TEAM_SCORE_FLOOR_MESSAGE =\n \"These changes would leave a team's score below zero.\";\n\nconst playerLineValuesSchema = z.strictObject(\n Object.fromEntries(\n BASKETBALL_PLAYER_STAT_KEYS.map((key) => [key, bulkEntryCountField]),\n ) as Record<BasketballPlayerStatKey, typeof bulkEntryCountField>,\n);\n\nfunction currentPlayerCounters(context: BulkEntryContext, entryId: string): PlayerCounters {\n return playerCountersOf(ownStats(context.state).playerStats[entryId]);\n}\n\nfunction bulkDrafts(values: PlayerLinesValues, context: BulkEntryContext): StatEventDraft[] {\n const increases: StatEventDraft[] = [];\n const decreases: StatEventDraft[] = [];\n for (const entryId of context.rosterPlayerIds) {\n const entered = values.lines[entryId];\n const entry = parseGameRosterPlayerId(entryId);\n if (entered === undefined || entry === null) continue;\n const before = currentPlayerCounters(context, entryId);\n for (const stat of BASKETBALL_PLAYER_STAT_KEYS) {\n const value = (entered[stat] ?? before[stat]) - before[stat];\n if (value === 0) continue;\n const draft: StatEventDraft = {\n type: BASKETBALL_BULK_EDIT_EVENT_TYPE,\n teamId: entry.teamId,\n playerUserId: entry.userId,\n stat,\n value,\n previousValue: before[stat],\n };\n (value > 0 ? increases : decreases).push(draft);\n }\n }\n // Every increase before any decrease, so a team's running score never dips below the floor its\n // final score clears — each draft passes `validate` against the state the ones before it leave.\n return [...increases, ...decreases];\n}\n\nexport const BASKETBALL_BULK_ENTRY: PlayerLinesBulkEntry = {\n kind: 'playerLines',\n formDefaults: (context) => ({\n lines: Object.fromEntries(\n context.rosterPlayerIds.map((entryId) => [entryId, currentPlayerCounters(context, entryId)]),\n ),\n }),\n formSchema: (context) => {\n const roster = new Set(context.rosterPlayerIds);\n return z\n .object({ lines: z.record(z.string(), playerLineValuesSchema) })\n .superRefine((values, issues) => {\n for (const entryId of Object.keys(values.lines)) {\n if (!roster.has(entryId)) {\n issues.addIssue({\n code: 'custom',\n path: ['lines', entryId],\n message: NOT_ON_GAME_ROSTER_MESSAGE,\n });\n }\n }\n const state = ownStats(context.state);\n for (const teamId of context.game.teamIds) {\n let score = (state.teamStats[teamId] ?? emptyBasketballTeamLine()).score;\n for (const [entryId, entered] of Object.entries(values.lines)) {\n if (parseGameRosterPlayerId(entryId)?.teamId !== teamId) continue;\n score += entered.points - currentPlayerCounters(context, entryId).points;\n }\n if (score < 0)\n issues.addIssue({\n code: 'custom',\n path: ['lines'],\n message: BASKETBALL_TEAM_SCORE_FLOOR_MESSAGE,\n });\n }\n if (bulkDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\n issues.addIssue({\n code: 'custom',\n path: ['lines'],\n message: BULK_ENTRY_TOO_LARGE_MESSAGE,\n });\n }\n });\n },\n toEvents: bulkDrafts,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"basketball-scoring.js","sourceRoot":"","sources":["../../src/sports/basketball-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The basketball adapter's scoring, display, and bulk-entry members. Basketball is player-scored: a\n// shot credits its scorer's points and its team's score, and — when it names one — its assister's\n// assists, all on the one event (RULE-QS-SPORT-903).\n//\n// `apply` and `undo` are one function over an event's effects with opposite signs, so they are exact\n// inverses by construction (RULE-QS-SPORT-913).\n\nimport { z } from 'zod';\nimport {\n BASKETBALL_FANTASY_POINT_WEIGHTS,\n BASKETBALL_POINTS_BY_EVENT_TYPE,\n MAX_STAT_EVENTS_PER_WRITE,\n NOT_ON_GAME_ROSTER_MESSAGE,\n} from '../constants/sports.js';\nimport type {\n BasketballPlayerStats,\n BasketballTeamStats,\n GameStatEvent,\n} from '../doc-schemas/game-stats.js';\nimport type { BasketballGameStats, GameStats } from '../doc-schemas/sport-game.js';\nimport { buildGameRosterPlayerId, parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport {\n BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS,\n BASKETBALL_ASSIST_EVENT_TYPE,\n BASKETBALL_BULK_EDIT_EVENT_TYPE,\n BASKETBALL_ONE_POINT_EVENT_TYPE,\n BASKETBALL_PLAYER_STAT_KEYS,\n BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS,\n BASKETBALL_REBOUND_EVENT_TYPE,\n BASKETBALL_SCORE_EVENT_TYPE_KEYS,\n BASKETBALL_STAT_EVENT_TYPE_KEYS,\n BASKETBALL_TEAM_STAT_KEYS,\n BASKETBALL_THREE_POINT_EVENT_TYPE,\n BASKETBALL_TIMEOUT_EVENT_TYPE,\n BASKETBALL_TWO_POINT_EVENT_TYPE,\n} from '../types/sports.js';\nimport type {\n BasketballPlayerStatKey,\n BasketballStatEventType,\n BasketballTeamStatKey,\n} from '../types/sports.js';\nimport { PLAYER_STAT_LINE_KIND_KEYS } from '../types/stats.js';\nimport type { StatLineKind } from '../types/stats.js';\nimport {\n BULK_ENTRY_TOO_LARGE_MESSAGE,\n bulkEntryCountField,\n type BulkEntryContext,\n type PlayerLinesBulkEntry,\n type PlayerLinesValues,\n type StatEventDraft,\n} from './bulk-entry.js';\nimport {\n buildGameContribution,\n decideParticipation,\n type ContributionSpec,\n} from './contribution.js';\nimport {\n FINALIZATION_READY,\n STAT_EVENT_ACCEPTED,\n isAllZeroLine,\n isSportStatEventIn,\n isStatCorrection,\n refuseStatEvent,\n type CorrectableFields,\n type GameTeamLine,\n type SportScoring,\n type SportStatEventBody,\n type StatCorrectionBody,\n type StatEventBody,\n type StatLineValues,\n} from './scoring.js';\nimport {\n describeStoreEvent,\n formatSignedValue,\n type PlayerEventsScoringPanel,\n type Scoreboard,\n type StatEventLabel,\n type StatEventNameResolver,\n type StatFieldLabels,\n} from './scoring-display.js';\n\ntype BasketballEvent = Extract<SportStatEventBody, { type: BasketballStatEventType }>;\ntype PlayerCounters = Record<BasketballPlayerStatKey, number>;\n\nfunction isBasketballEvent(event: StatEventBody | undefined): event is BasketballEvent {\n return isSportStatEventIn(BASKETBALL_STAT_EVENT_TYPE_KEYS, event);\n}\n\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\n// basketball game's (RULE-QS-SPORT-902).\nfunction ownStats(state: GameStats): BasketballGameStats {\n return state as BasketballGameStats;\n}\n\nfunction ownTeamLine(line: GameTeamLine): BasketballTeamStats {\n return line as BasketballTeamStats;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Lines\n// ---------------------------------------------------------------------------------------------\n\n/**\n * Fantasy points (FP) of a basketball line: each player counter times its weight. Every stored\n * `fantasyPoints` equals this over the counters of its own line; `deriveLine` is what applies it.\n */\nexport function basketballFantasyPoints(\n line: Pick<BasketballPlayerStats, BasketballPlayerStatKey>,\n): number {\n return BASKETBALL_PLAYER_STAT_KEYS.reduce(\n (total, key) => total + line[key] * BASKETBALL_FANTASY_POINT_WEIGHTS[key],\n 0,\n );\n}\n\nconst FANTASY_POINTS_FIELD = 'fantasyPoints' satisfies keyof BasketballPlayerStats;\nconst PLAYER_LINE_KINDS: readonly StatLineKind[] = PLAYER_STAT_LINE_KIND_KEYS;\n\nexport function emptyBasketballTeamLine(): BasketballTeamStats {\n return Object.fromEntries(\n BASKETBALL_TEAM_STAT_KEYS.map((key) => [key, 0]),\n ) as BasketballTeamStats;\n}\n\nfunction emptyPlayerCounters(): PlayerCounters {\n return Object.fromEntries(BASKETBALL_PLAYER_STAT_KEYS.map((key) => [key, 0])) as PlayerCounters;\n}\n\nfunction counterOf(line: StatLineValues, field: string): number {\n const value = line[field] ?? 0;\n if (typeof value !== 'number')\n throw new TypeError(`A basketball line's ${field} is not a number.`);\n return value;\n}\n\nfunction playerCountersOf(line: StatLineValues | undefined): PlayerCounters {\n const counters = emptyPlayerCounters();\n if (line === undefined) return counters;\n for (const key of BASKETBALL_PLAYER_STAT_KEYS) counters[key] = counterOf(line, key);\n return counters;\n}\n\n// FP is an individual stat: it goes on the four player kinds and on no team kind.\nfunction deriveBasketballLine(kind: StatLineKind, line: StatLineValues): StatLineValues {\n const withoutDerived = Object.fromEntries(\n Object.entries(line).filter(([field]) => field !== FANTASY_POINTS_FIELD),\n );\n if (!PLAYER_LINE_KINDS.includes(kind)) return withoutDerived;\n return {\n ...withoutDerived,\n [FANTASY_POINTS_FIELD]: basketballFantasyPoints(playerCountersOf(line)),\n };\n}\n\n// ---------------------------------------------------------------------------------------------\n// Events and their effects\n// ---------------------------------------------------------------------------------------------\n\nconst CORRECTABLE_FIELDS: CorrectableFields = {\n team: BASKETBALL_TEAM_STAT_KEYS,\n player: BASKETBALL_PLAYER_STAT_KEYS,\n};\n\nconst TALLY_FIELD = {\n [BASKETBALL_REBOUND_EVENT_TYPE]: 'rebounds',\n [BASKETBALL_ASSIST_EVENT_TYPE]: 'assists',\n} as const satisfies Record<\n (typeof BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS)[number],\n BasketballPlayerStatKey\n>;\n\ninterface LineChange<Field extends string> {\n /** The team id of a team line, or the roster-entry id of a player line. */\n readonly line: string;\n readonly field: Field;\n readonly amount: number;\n}\n\ninterface Effects {\n readonly team: readonly LineChange<BasketballTeamStatKey>[];\n readonly player: readonly LineChange<BasketballPlayerStatKey>[];\n}\n\nfunction assisterOf(event: StatEventBody): string | undefined {\n return 'assistedByUserId' in event && typeof event.assistedByUserId === 'string'\n ? event.assistedByUserId\n : undefined;\n}\n\nfunction namesPlayer(event: StatEventBody): boolean {\n return 'playerUserId' in event && event.playerUserId !== undefined;\n}\n\nfunction effectsOf(event: BasketballEvent): Effects {\n if (event.type === BASKETBALL_TIMEOUT_EVENT_TYPE) {\n return { team: [{ line: event.teamId, field: 'timeouts', amount: 1 }], player: [] };\n }\n const scorer = buildGameRosterPlayerId(event.teamId, event.playerUserId);\n if (event.type === BASKETBALL_BULK_EDIT_EVENT_TYPE) {\n // A scorer's points edit keeps the team's score the sum of its players' points.\n return {\n team:\n event.stat === 'points'\n ? [{ line: event.teamId, field: 'score', amount: event.value }]\n : [],\n player: [{ line: scorer, field: event.stat, amount: event.value }],\n };\n }\n\n const team: LineChange<BasketballTeamStatKey>[] = [];\n const player: LineChange<BasketballPlayerStatKey>[] = [];\n const points = BASKETBALL_POINTS_BY_EVENT_TYPE[event.type];\n if (points > 0) {\n team.push({ line: event.teamId, field: 'score', amount: points });\n player.push({ line: scorer, field: 'points', amount: points });\n }\n if (event.type === BASKETBALL_REBOUND_EVENT_TYPE || event.type === BASKETBALL_ASSIST_EVENT_TYPE) {\n player.push({ line: scorer, field: TALLY_FIELD[event.type], amount: 1 });\n }\n const assister = assisterOf(event);\n if (assister !== undefined) {\n player.push({\n line: buildGameRosterPlayerId(event.teamId, assister),\n field: 'assists',\n amount: 1,\n });\n }\n return { team, player };\n}\n\nfunction correctionEffects(event: StatCorrectionBody): Effects {\n const fields =\n event.playerUserId === undefined ? CORRECTABLE_FIELDS.team : CORRECTABLE_FIELDS.player;\n if (!fields.includes(event.stat)) {\n throw new Error(\n `A basketball correction cannot change ${event.stat} on a ${event.playerUserId === undefined ? 'team' : 'player'} line.`,\n );\n }\n // A correction moves only its own field: a player-points correction does not move the team score.\n return event.playerUserId === undefined\n ? {\n team: [\n { line: event.teamId, field: event.stat as BasketballTeamStatKey, amount: event.value },\n ],\n player: [],\n }\n : {\n team: [],\n player: [\n {\n line: buildGameRosterPlayerId(event.teamId, event.playerUserId),\n field: event.stat as BasketballPlayerStatKey,\n amount: event.value,\n },\n ],\n };\n}\n\nfunction applyEffects(\n state: BasketballGameStats,\n effects: Effects,\n sign: 1 | -1,\n): BasketballGameStats {\n const teamStats = { ...state.teamStats };\n for (const change of effects.team) {\n const line = { ...(teamStats[change.line] ?? emptyBasketballTeamLine()) };\n line[change.field] += sign * change.amount;\n teamStats[change.line] = line;\n }\n\n const playerStats = { ...state.playerStats };\n for (const change of effects.player) {\n const counters = playerCountersOf(playerStats[change.line]);\n counters[change.field] += sign * change.amount;\n if (isAllZeroLine(counters, BASKETBALL_PLAYER_STAT_KEYS)) delete playerStats[change.line];\n else\n playerStats[change.line] = deriveBasketballLine(\n 'gamePlayer',\n counters,\n ) as BasketballPlayerStats;\n }\n return { teamStats, playerStats };\n}\n\n// ---------------------------------------------------------------------------------------------\n// Validation — the same rules the bulk form holds, re-checked on the server (RULE-QS-SPORT-906)\n// ---------------------------------------------------------------------------------------------\n\nfunction isAssistableShot(type: BasketballStatEventType): boolean {\n return (BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS as readonly string[]).includes(type);\n}\n\nconst validate: SportScoring['validate'] = (stateIn, event, roster) => {\n if (!isBasketballEvent(event)) return refuseStatEvent('eventTypeNotInSport');\n if (!roster.teamIds.includes(event.teamId)) return refuseStatEvent('teamNotInGame');\n if (event.type === BASKETBALL_TIMEOUT_EVENT_TYPE) {\n return namesPlayer(event) || assisterOf(event) !== undefined\n ? refuseStatEvent('playerNotAllowed')\n : STAT_EVENT_ACCEPTED;\n }\n\n const scorer = buildGameRosterPlayerId(event.teamId, event.playerUserId);\n if (!roster.rosterPlayerIds.has(scorer)) return refuseStatEvent('playerNotRostered');\n\n const assister = assisterOf(event);\n if (assister !== undefined) {\n if (!isAssistableShot(event.type) || assister === event.playerUserId)\n return refuseStatEvent('assistNotAllowed');\n if (!roster.rosterPlayerIds.has(buildGameRosterPlayerId(event.teamId, assister))) {\n return refuseStatEvent('assisterNotRostered');\n }\n }\n\n if (event.type === BASKETBALL_BULK_EDIT_EVENT_TYPE) {\n if (!Number.isInteger(event.value) || event.value === 0)\n return refuseStatEvent('bulkValueInvalid');\n const state = ownStats(stateIn);\n const current = playerCountersOf(state.playerStats[scorer])[event.stat];\n if (event.previousValue !== current) return refuseStatEvent('bulkStale');\n if (current + event.value < 0) return refuseStatEvent('bulkValueInvalid');\n const teamScore = (state.teamStats[event.teamId] ?? emptyBasketballTeamLine()).score;\n if (event.stat === 'points' && teamScore + event.value < 0)\n return refuseStatEvent('bulkValueInvalid');\n }\n return STAT_EVENT_ACCEPTED;\n};\n\n// What a completed basketball game adds: each entry's points, rebounds, assists, and FP to its row,\n// and the team's rebounds and assists to its standings line. A present entry with no points,\n// rebounds, or assists is credited no game played — zero points alone is not exclusion.\nconst BASKETBALL_CONTRIBUTION: ContributionSpec = {\n playerStatKeys: BASKETBALL_PLAYER_STAT_KEYS,\n teamSeasonPlayerSums: ['rebounds', 'assists'] satisfies BasketballPlayerStatKey[],\n zeroLineIsNonScoring: true,\n deriveLine: deriveBasketballLine,\n};\n\nexport const BASKETBALL_SCORING: SportScoring = {\n validate,\n apply: (state, event) => {\n if (isStatCorrection(event)) return applyEffects(ownStats(state), correctionEffects(event), 1);\n if (!isBasketballEvent(event))\n throw new Error(`The basketball adapter does not apply ${event.type}.`);\n return applyEffects(ownStats(state), effectsOf(event), 1);\n },\n undo: (state, target) => {\n if (!isBasketballEvent(target))\n throw new Error(`The basketball adapter does not undo ${target.type}.`);\n return applyEffects(ownStats(state), effectsOf(target), -1);\n },\n deriveLine: deriveBasketballLine,\n correctableFields: CORRECTABLE_FIELDS,\n // Played in no rounds, and finalized by hand whenever the scorer chooses.\n nextRound: () => null,\n finalizationReadiness: () => FINALIZATION_READY,\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\n decideParticipation(BASKETBALL_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\n contribution: (game) => buildGameContribution(BASKETBALL_CONTRIBUTION, game),\n};\n\n// ---------------------------------------------------------------------------------------------\n// Display\n// ---------------------------------------------------------------------------------------------\n\nexport const BASKETBALL_EVENT_LABELS = {\n [BASKETBALL_ONE_POINT_EVENT_TYPE]: { button: '+1 PT', play: '1-point basket' },\n [BASKETBALL_TWO_POINT_EVENT_TYPE]: { button: '+2 PT', play: '2-point basket' },\n [BASKETBALL_THREE_POINT_EVENT_TYPE]: { button: '+3 PT', play: '3-point basket' },\n [BASKETBALL_REBOUND_EVENT_TYPE]: { button: 'Rebound', play: 'Rebound' },\n [BASKETBALL_ASSIST_EVENT_TYPE]: { button: 'Assist', play: 'Assist' },\n [BASKETBALL_TIMEOUT_EVENT_TYPE]: { button: 'Timeout', play: 'Timeout' },\n [BASKETBALL_BULK_EDIT_EVENT_TYPE]: { button: 'Bulk entry', play: 'Bulk entry' },\n} as const satisfies Record<BasketballStatEventType, StatEventLabel>;\n\nexport const BASKETBALL_FIELD_LABELS = {\n team: { score: 'Score', timeouts: 'Timeouts' },\n player: { points: 'Points', rebounds: 'Rebounds', assists: 'Assists' },\n} as const satisfies StatFieldLabels<BasketballTeamStatKey, BasketballPlayerStatKey>;\n\nexport function describeBasketballEvent(\n event: GameStatEvent,\n resolve: StatEventNameResolver,\n): string {\n const storeEvent = describeStoreEvent(event, resolve, BASKETBALL_FIELD_LABELS);\n if (storeEvent !== null) return storeEvent;\n if (!isBasketballEvent(event)) return event.type;\n\n const team = resolve.team(event.teamId);\n if (event.type === BASKETBALL_TIMEOUT_EVENT_TYPE)\n return `${team}: ${BASKETBALL_EVENT_LABELS[event.type].play}`;\n\n const player = `${resolve.player(event.playerUserId)} (${team})`;\n if (event.type === BASKETBALL_BULK_EDIT_EVENT_TYPE) {\n const field = BASKETBALL_FIELD_LABELS.player[event.stat].toLowerCase();\n return `${player}: ${formatSignedValue(event.value)} ${field} (${BASKETBALL_EVENT_LABELS[event.type].play.toLowerCase()})`;\n }\n const play = `${player}: ${BASKETBALL_EVENT_LABELS[event.type].play}`;\n const assister = assisterOf(event);\n return assister === undefined ? play : `${play}, assisted by ${resolve.player(assister)}`;\n}\n\nfunction sumOf(\n lines: readonly { readonly rebounds: number; readonly assists: number }[],\n field: 'rebounds' | 'assists',\n): number {\n return lines.reduce((total, line) => total + line[field], 0);\n}\n\nexport const BASKETBALL_SCOREBOARD: Scoreboard = {\n figures: [\n {\n id: 'points',\n label: BASKETBALL_FIELD_LABELS.player.points,\n value: (teamLine) => teamLine.score,\n },\n {\n id: 'rebounds',\n label: BASKETBALL_FIELD_LABELS.player.rebounds,\n value: (_teamLine, sideLines) => sumOf(sideLines, 'rebounds'),\n },\n {\n id: 'assists',\n label: BASKETBALL_FIELD_LABELS.player.assists,\n value: (_teamLine, sideLines) => sumOf(sideLines, 'assists'),\n },\n {\n id: 'timeouts',\n label: BASKETBALL_FIELD_LABELS.team.timeouts,\n value: (teamLine) => ownTeamLine(teamLine).timeouts,\n },\n ],\n jumpBall: true,\n};\n\nexport const BASKETBALL_SCORING_PANEL: PlayerEventsScoringPanel = {\n kind: 'playerEvents',\n playerEventTypes: [\n ...BASKETBALL_SCORE_EVENT_TYPE_KEYS,\n ...BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS,\n ],\n assistableEventTypes: BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS,\n teamEventTypes: [BASKETBALL_TIMEOUT_EVENT_TYPE],\n};\n\n// ---------------------------------------------------------------------------------------------\n// Bulk entry — one row per roster entry, absent entries included\n// ---------------------------------------------------------------------------------------------\n\nexport const BASKETBALL_TEAM_SCORE_FLOOR_MESSAGE =\n \"These changes would leave a team's score below zero.\";\n\nconst playerLineValuesSchema = z.strictObject(\n Object.fromEntries(\n BASKETBALL_PLAYER_STAT_KEYS.map((key) => [key, bulkEntryCountField]),\n ) as Record<BasketballPlayerStatKey, typeof bulkEntryCountField>,\n);\n\nfunction currentPlayerCounters(context: BulkEntryContext, entryId: string): PlayerCounters {\n return playerCountersOf(ownStats(context.state).playerStats[entryId]);\n}\n\nfunction bulkDrafts(values: PlayerLinesValues, context: BulkEntryContext): StatEventDraft[] {\n const increases: StatEventDraft[] = [];\n const decreases: StatEventDraft[] = [];\n for (const entryId of context.rosterPlayerIds) {\n const entered = values.lines[entryId];\n const entry = parseGameRosterPlayerId(entryId);\n if (entered === undefined || entry === null) continue;\n const before = currentPlayerCounters(context, entryId);\n for (const stat of BASKETBALL_PLAYER_STAT_KEYS) {\n const value = (entered[stat] ?? before[stat]) - before[stat];\n if (value === 0) continue;\n const draft: StatEventDraft = {\n type: BASKETBALL_BULK_EDIT_EVENT_TYPE,\n teamId: entry.teamId,\n playerUserId: entry.userId,\n stat,\n value,\n previousValue: before[stat],\n };\n (value > 0 ? increases : decreases).push(draft);\n }\n }\n // Every increase before any decrease, so a team's running score never dips below the floor its\n // final score clears — each draft passes `validate` against the state the ones before it leave.\n return [...increases, ...decreases];\n}\n\nexport const BASKETBALL_BULK_ENTRY: PlayerLinesBulkEntry = {\n kind: 'playerLines',\n formDefaults: (context) => ({\n lines: Object.fromEntries(\n context.rosterPlayerIds.map((entryId) => [entryId, currentPlayerCounters(context, entryId)]),\n ),\n }),\n formSchema: (context) => {\n const roster = new Set(context.rosterPlayerIds);\n return z\n .object({ lines: z.record(z.string(), playerLineValuesSchema) })\n .superRefine((values, issues) => {\n for (const entryId of Object.keys(values.lines)) {\n if (!roster.has(entryId)) {\n issues.addIssue({\n code: 'custom',\n path: ['lines', entryId],\n message: NOT_ON_GAME_ROSTER_MESSAGE,\n });\n }\n }\n const state = ownStats(context.state);\n for (const teamId of context.game.teamIds) {\n let score = (state.teamStats[teamId] ?? emptyBasketballTeamLine()).score;\n for (const [entryId, entered] of Object.entries(values.lines)) {\n if (parseGameRosterPlayerId(entryId)?.teamId !== teamId) continue;\n score += entered.points - currentPlayerCounters(context, entryId).points;\n }\n if (score < 0)\n issues.addIssue({\n code: 'custom',\n path: ['lines'],\n message: BASKETBALL_TEAM_SCORE_FLOOR_MESSAGE,\n });\n }\n if (bulkDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\n issues.addIssue({\n code: 'custom',\n path: ['lines'],\n message: BULK_ENTRY_TOO_LARGE_MESSAGE,\n });\n }\n });\n },\n toEvents: bulkDrafts,\n};\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// A sport's bulk entry: the form the shared bulk-entry dialog renders for the sport's kind, and the
|
|
2
2
|
// events a submission becomes. The form's rules are built from the sport's constants, and every one
|
|
3
3
|
// of them is also an event rule the adapter's `validate` enforces, because the callable sees events,
|
|
4
|
-
// never form values, and captains use it (SPORT-
|
|
4
|
+
// never form values, and captains use it (RULE-QS-SPORT-906).
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { countSchema } from '../doc-schemas/atoms.js';
|
|
7
7
|
/** The form's refusal when a submission would pass the per-write event cap. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-entry.js","sourceRoot":"","sources":["../../src/sports/bulk-entry.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,oGAAoG;AACpG,qGAAqG;AACrG,
|
|
1
|
+
{"version":3,"file":"bulk-entry.js","sourceRoot":"","sources":["../../src/sports/bulk-entry.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,oGAAoG;AACpG,qGAAqG;AACrG,8DAA8D;AAE9D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA8DtD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,4BAA4B,GACvC,6FAA6F,CAAC;AAEhG,gFAAgF;AAChF,MAAM,CAAC,MAAM,wBAAwB,GAAG,qCAAqC,CAAC;AAE9E;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CACzC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,EAC/C,wBAAwB,CACzB,CAAC","sourcesContent":["// A sport's bulk entry: the form the shared bulk-entry dialog renders for the sport's kind, and the\n// events a submission becomes. The form's rules are built from the sport's constants, and every one\n// of them is also an event rule the adapter's `validate` enforces, because the callable sees events,\n// never form values, and captains use it (RULE-QS-SPORT-906).\n\nimport { z } from 'zod';\nimport { countSchema } from '../doc-schemas/atoms.js';\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport type { BulkEntryKind } from '../types/stats.js';\nimport type { SportStatEventBody, StatVoidBody } from './scoring.js';\nimport type { GameTeamIds } from './sport-adapter.js';\n\n/** A recorded round's live event — one no void voids — so an edit of that round can void it. */\nexport interface LiveRoundEvent {\n readonly eventId: string;\n readonly roundNumber: number;\n}\n\n/** What the form edits: the game, the scorer's current state, and the game's roster. */\nexport interface BulkEntryContext {\n readonly game: GameTeamIds;\n /** The scorer's optimistic rollup, the queue's pending events applied. */\n readonly state: GameStats;\n /** Every roster entry of the game, grouped by team in the order the form lists them. */\n readonly rosterPlayerIds: readonly string[];\n readonly absentRosterPlayerIds: readonly string[];\n /** For a sport played in rounds, the game's unvoided round events; empty for any other sport. */\n readonly liveRoundEvents: readonly LiveRoundEvent[];\n}\n\n/**\n * One event of a submission, in batch order: a sport event's type and payload, or the target of a\n * void. At submit — the tap — the dialog mints each sport event's id and stamps its `recordedAt`, and\n * the queue sends the drafts as one unit it never splits across calls.\n */\nexport type StatEventDraft = SportStatEventBody | StatVoidBody;\n\nexport interface BulkEntryForm<K extends BulkEntryKind, Values> {\n readonly kind: K;\n /** The form's resolver: its rules for this game and state. */\n readonly formSchema: (context: BulkEntryContext) => z.ZodType<Values>;\n readonly formDefaults: (context: BulkEntryContext) => Values;\n /** The drafts a submission becomes; empty when nothing changed. */\n readonly toEvents: (values: Values, context: BulkEntryContext) => readonly StatEventDraft[];\n}\n\n/** Each roster entry's player counters, by roster entry id. */\nexport interface PlayerLinesValues {\n readonly lines: Readonly<Record<string, Readonly<Record<string, number>>>>;\n}\n\n/** Each team's final score, by team id. */\nexport interface TeamScoresValues {\n readonly scores: Readonly<Record<string, number>>;\n}\n\n/** Each round, first to last: every team's points by team id, `null` where the round is not recorded. */\nexport interface RoundsValues {\n readonly rounds: readonly Readonly<Record<string, number | null>>[];\n}\n\nexport type PlayerLinesBulkEntry = BulkEntryForm<'playerLines', PlayerLinesValues>;\nexport type TeamScoresBulkEntry = BulkEntryForm<'teamScores', TeamScoresValues>;\nexport type RoundsBulkEntry = BulkEntryForm<'rounds', RoundsValues>;\n\n/** The dialog picks its form body by `kind`, from an exhaustive map over the kind union. */\nexport type BulkEntry = PlayerLinesBulkEntry | TeamScoresBulkEntry | RoundsBulkEntry;\n\n/** The form's refusal when a submission would pass the per-write event cap. */\nexport const BULK_ENTRY_TOO_LARGE_MESSAGE =\n 'This entry changes more stats than one save can hold. Save part of it, then enter the rest.';\n\n/** The form's refusal of a value that is not a whole number of zero or more. */\nexport const BULK_ENTRY_COUNT_MESSAGE = 'Enter a whole number, zero or more.';\n\n/**\n * Every bulk-entry cell that holds a count: the count atom's check, answered with the one message the\n * form shows for any failure of it — which the atom's own issues cannot carry.\n */\nexport const bulkEntryCountField = z.custom<number>(\n (value) => countSchema.safeParse(value).success,\n BULK_ENTRY_COUNT_MESSAGE,\n);\n"]}
|
|
@@ -5,7 +5,7 @@ import type { StatLineValues } from './scoring.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* A completed game as its finalized record holds it: the record's events replayed to lines, and the
|
|
7
7
|
* first page's sport, teams, and participation snapshot. Finalize builds it from the game it is
|
|
8
|
-
* finalizing; every later path builds it from the record, never from the game document (SPORT-
|
|
8
|
+
* finalizing; every later path builds it from the record, never from the game document (RULE-QS-SPORT-924).
|
|
9
9
|
*/
|
|
10
10
|
export interface FinalizedGame {
|
|
11
11
|
readonly leagueType: LeagueType;
|
|
@@ -27,12 +27,12 @@ export interface GameContribution {
|
|
|
27
27
|
/**
|
|
28
28
|
* Per person who is not a ghost: the sum of that person's rows, games played included — what the
|
|
29
29
|
* game adds to `lifetimeStats.{sportKey}` and, where the sport keeps one, the hall of fame
|
|
30
|
-
* (`lifetime`). A person on both teams of the game counts both sides (SPORT-
|
|
30
|
+
* (`lifetime`). A person on both teams of the game counts both sides (RULE-QS-SPORT-928 keeps ghosts out).
|
|
31
31
|
*/
|
|
32
32
|
readonly personLines: Readonly<Record<string, StatLineValues>>;
|
|
33
33
|
}
|
|
34
34
|
export declare const EMPTY_GAME_CONTRIBUTION: GameContribution;
|
|
35
|
-
/** One sport's aggregate mapping, declared once by its adapter (SPORT-
|
|
35
|
+
/** One sport's aggregate mapping, declared once by its adapter (RULE-QS-SPORT-927). */
|
|
36
36
|
export interface ContributionSpec {
|
|
37
37
|
/** The counters of a player line that a membership row, a lifetime line, and the hall of fame sum. */
|
|
38
38
|
readonly playerStatKeys: readonly string[];
|
|
@@ -52,7 +52,7 @@ export declare function isZeroStatLine(line: StatLineValues): boolean;
|
|
|
52
52
|
* The games-played decision for every roster entry: the entries credited no game played. An absent
|
|
53
53
|
* entry is never credited; under `zeroLineIsNonScoring` a present entry whose line holds nothing is
|
|
54
54
|
* not either. Stored on the completed game as `nonScoringRosterPlayerIds` and read, never decided
|
|
55
|
-
* again, by every later path (SPORT-
|
|
55
|
+
* again, by every later path (RULE-QS-SPORT-924). Returned in roster order.
|
|
56
56
|
*/
|
|
57
57
|
export declare function decideParticipation(spec: Pick<ContributionSpec, 'playerStatKeys' | 'zeroLineIsNonScoring'>, stats: GameStats, rosterPlayerIds: readonly string[], absentRosterPlayerIds: readonly string[]): string[];
|
|
58
58
|
/** Whether the stored decision credits the entry a game played. */
|
|
@@ -61,7 +61,7 @@ export declare function isCreditedGamePlayed(game: Pick<FinalizedGame, 'absentRo
|
|
|
61
61
|
export declare function addStatLines(left: StatLineValues, right: StatLineValues, sign?: 1 | -1): Record<string, number>;
|
|
62
62
|
/**
|
|
63
63
|
* The contribution of one completed game under a sport's mapping. Every line derives from the
|
|
64
|
-
* replayed lines and the stored decision only, never from a stored total (SPORT-
|
|
64
|
+
* replayed lines and the stored decision only, never from a stored total (RULE-QS-SPORT-922, RULE-QS-SPORT-924):
|
|
65
65
|
*
|
|
66
66
|
* - each team with an opponent: one game, a win, loss, or tie decided from the two replayed scores,
|
|
67
67
|
* its points for and against, and the spec's player counters summed from the lines keyed to it;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contribution.d.ts","sourceRoot":"","sources":["../../src/sports/contribution.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAG9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IAClD,QAAQ,CAAC,yBAAyB,EAAE,SAAS,MAAM,EAAE,CAAC;CACvD;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAC7D,qGAAqG;IACrG,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAClE;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;CAChE;AAED,eAAO,MAAM,uBAAuB,EAAE,gBAIrC,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"contribution.d.ts","sourceRoot":"","sources":["../../src/sports/contribution.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// The aggregate half of the sport adapter contract: who a completed game credits a game played, and
|
|
2
|
-
// what the game adds to every derived destination (SPORT-
|
|
2
|
+
// what the game adds to every derived destination (RULE-QS-SPORT-921, RULE-QS-SPORT-924, RULE-QS-SPORT-927). Each sport
|
|
3
3
|
// declares its mapping once, as a `ContributionSpec`; the two members built from it — `participation`
|
|
4
4
|
// and `contribution` — are the only place a game's effect on standings, per-membership rows, lifetime
|
|
5
5
|
// lines, and the hall of fame is decided. Finalize, reopen, a ghost link, every rebuild, validation,
|
|
6
6
|
// and the week view all derive through them, so no two paths can disagree about what a game adds.
|
|
7
7
|
//
|
|
8
|
-
// Everything here is pure: no Firestore operation, no clock, no id minted (ARCH-202). The aggregate
|
|
8
|
+
// Everything here is pure: no Firestore operation, no clock, no id minted (RULE-STD-ARCH-202). The aggregate
|
|
9
9
|
// writer in Functions applies a contribution; the browser sums contributions for the week view.
|
|
10
10
|
//
|
|
11
11
|
// A stored derived line that sums to nothing — every counter and games played zero — is the same as
|
|
@@ -33,7 +33,7 @@ export function isZeroStatLine(line) {
|
|
|
33
33
|
* The games-played decision for every roster entry: the entries credited no game played. An absent
|
|
34
34
|
* entry is never credited; under `zeroLineIsNonScoring` a present entry whose line holds nothing is
|
|
35
35
|
* not either. Stored on the completed game as `nonScoringRosterPlayerIds` and read, never decided
|
|
36
|
-
* again, by every later path (SPORT-
|
|
36
|
+
* again, by every later path (RULE-QS-SPORT-924). Returned in roster order.
|
|
37
37
|
*/
|
|
38
38
|
export function decideParticipation(spec, stats, rosterPlayerIds, absentRosterPlayerIds) {
|
|
39
39
|
if (!spec.zeroLineIsNonScoring)
|
|
@@ -65,7 +65,7 @@ export function addStatLines(left, right, sign = 1) {
|
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
67
|
* The contribution of one completed game under a sport's mapping. Every line derives from the
|
|
68
|
-
* replayed lines and the stored decision only, never from a stored total (SPORT-
|
|
68
|
+
* replayed lines and the stored decision only, never from a stored total (RULE-QS-SPORT-922, RULE-QS-SPORT-924):
|
|
69
69
|
*
|
|
70
70
|
* - each team with an opponent: one game, a win, loss, or tie decided from the two replayed scores,
|
|
71
71
|
* its points for and against, and the spec's player counters summed from the lines keyed to it;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contribution.js","sourceRoot":"","sources":["../../src/sports/contribution.ts"],"names":[],"mappings":"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","sourcesContent":["// The aggregate half of the sport adapter contract: who a completed game credits a game played, and\n// what the game adds to every derived destination (SPORT-201, SPORT-204, SPORT-207). Each sport\n// declares its mapping once, as a `ContributionSpec`; the two members built from it — `participation`\n// and `contribution` — are the only place a game's effect on standings, per-membership rows, lifetime\n// lines, and the hall of fame is decided. Finalize, reopen, a ghost link, every rebuild, validation,\n// and the week view all derive through them, so no two paths can disagree about what a game adds.\n//\n// Everything here is pure: no Firestore operation, no clock, no id minted (ARCH-202). The aggregate\n// writer in Functions applies a contribution; the browser sums contributions for the week view.\n//\n// A stored derived line that sums to nothing — every counter and games played zero — is the same as\n// no line at all, as an all-zero player line is in a game's rollup: a contribution leaves one out,\n// and applying a contribution removes a line it returns to zero. That is what makes finalize followed\n// by reopen leave every destination exactly as it was.\n\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport { parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport { isGhostUserId } from '../ids/minted-ids.js';\nimport type { LeagueType } from '../types/sports.js';\nimport type { StatLineKind } from '../types/stats.js';\nimport { isAllZeroLine } from './scoring.js';\nimport type { StatLineValues } from './scoring.js';\n\n/**\n * A completed game as its finalized record holds it: the record's events replayed to lines, and the\n * first page's sport, teams, and participation snapshot. Finalize builds it from the game it is\n * finalizing; every later path builds it from the record, never from the game document (SPORT-204).\n */\nexport interface FinalizedGame {\n readonly leagueType: LeagueType;\n readonly teamIds: readonly string[];\n readonly stats: GameStats;\n readonly rosterPlayerIds: readonly string[];\n readonly absentRosterPlayerIds: readonly string[];\n readonly nonScoringRosterPlayerIds: readonly string[];\n}\n\n/**\n * What one completed game adds to each destination, before the caller's sign. Every line carries its\n * derived fields through the sport's `deriveLine`.\n */\nexport interface GameContribution {\n /** Per team id: the team's standings line (`teamSeason`). A team with no opponent has none. */\n readonly teamLines: Readonly<Record<string, StatLineValues>>;\n /** Per roster-entry id: that membership's row (`membershipRow`), credited to the team in its key. */\n readonly membershipRows: Readonly<Record<string, StatLineValues>>;\n /**\n * Per person who is not a ghost: the sum of that person's rows, games played included — what the\n * game adds to `lifetimeStats.{sportKey}` and, where the sport keeps one, the hall of fame\n * (`lifetime`). A person on both teams of the game counts both sides (SPORT-208 keeps ghosts out).\n */\n readonly personLines: Readonly<Record<string, StatLineValues>>;\n}\n\nexport const EMPTY_GAME_CONTRIBUTION: GameContribution = {\n teamLines: {},\n membershipRows: {},\n personLines: {},\n};\n\n/** One sport's aggregate mapping, declared once by its adapter (SPORT-207). */\nexport interface ContributionSpec {\n /** The counters of a player line that a membership row, a lifetime line, and the hall of fame sum. */\n readonly playerStatKeys: readonly string[];\n /** The player counters summed onto the team's standings line from the lines keyed to that team. */\n readonly teamSeasonPlayerSums: readonly string[];\n /**\n * Whether a present entry whose line holds nothing is credited no game played. Basketball's rule:\n * zero points, rebounds, and assists — zero points alone is not exclusion. Without it only absence\n * withholds a game played.\n */\n readonly zeroLineIsNonScoring: boolean;\n readonly deriveLine: (kind: StatLineKind, line: StatLineValues) => StatLineValues;\n}\n\n/** The value of a counter on a stored line, absent read as zero. */\nfunction counter(line: StatLineValues | undefined, field: string): number {\n const value = line?.[field];\n return typeof value === 'number' ? value : 0;\n}\n\n/** Whether every number a line holds is zero: a derived line that sums to nothing. */\nexport function isZeroStatLine(line: StatLineValues): boolean {\n return Object.values(line).every((value) => typeof value !== 'number' || value === 0);\n}\n\n/**\n * The games-played decision for every roster entry: the entries credited no game played. An absent\n * entry is never credited; under `zeroLineIsNonScoring` a present entry whose line holds nothing is\n * not either. Stored on the completed game as `nonScoringRosterPlayerIds` and read, never decided\n * again, by every later path (SPORT-204). Returned in roster order.\n */\nexport function decideParticipation(\n spec: Pick<ContributionSpec, 'playerStatKeys' | 'zeroLineIsNonScoring'>,\n stats: GameStats,\n rosterPlayerIds: readonly string[],\n absentRosterPlayerIds: readonly string[],\n): string[] {\n if (!spec.zeroLineIsNonScoring) return [];\n const absent = new Set(absentRosterPlayerIds);\n const playerStats = stats.playerStats as Readonly<Record<string, StatLineValues>>;\n return rosterPlayerIds.filter((entryId) => {\n if (absent.has(entryId)) return true;\n const line = playerStats[entryId];\n return line === undefined || isAllZeroLine(line, spec.playerStatKeys);\n });\n}\n\n/** Whether the stored decision credits the entry a game played. */\nexport function isCreditedGamePlayed(\n game: Pick<FinalizedGame, 'absentRosterPlayerIds' | 'nonScoringRosterPlayerIds'>,\n entryId: string,\n): boolean {\n return (\n !game.absentRosterPlayerIds.includes(entryId) &&\n !game.nonScoringRosterPlayerIds.includes(entryId)\n );\n}\n\n/** `left` with every number of `right` added `sign` times; a field only one holds is kept. */\nexport function addStatLines(\n left: StatLineValues,\n right: StatLineValues,\n sign: 1 | -1 = 1,\n): Record<string, number> {\n const sum: Record<string, number> = {};\n for (const field of new Set([...Object.keys(left), ...Object.keys(right)])) {\n const [a, b] = [left[field], right[field]];\n if ((a !== undefined && typeof a !== 'number') || (b !== undefined && typeof b !== 'number'))\n continue;\n sum[field] = (a ?? 0) + sign * (b ?? 0);\n }\n return sum;\n}\n\n/**\n * The contribution of one completed game under a sport's mapping. Every line derives from the\n * replayed lines and the stored decision only, never from a stored total (SPORT-202, SPORT-204):\n *\n * - each team with an opponent: one game, a win, loss, or tie decided from the two replayed scores,\n * its points for and against, and the spec's player counters summed from the lines keyed to it;\n * - each roster entry of such a team — and any line keyed to one, rostered or not, so no recorded\n * stat is lost — its row: games played per the stored decision, and the sport's counters, credited\n * to the team in its key;\n * - each person who is not a ghost: the sum of their rows.\n *\n * A row or person line that sums to nothing is left out.\n */\nexport function buildGameContribution(\n spec: ContributionSpec,\n game: FinalizedGame,\n): GameContribution {\n const teamStats = game.stats.teamStats as Readonly<Record<string, StatLineValues>>;\n const playerStats = game.stats.playerStats as Readonly<Record<string, StatLineValues>>;\n const opponentOf = (teamId: string): string | undefined =>\n game.teamIds.find((other) => other !== teamId);\n const counted = new Set(game.teamIds.filter((teamId) => opponentOf(teamId) !== undefined));\n\n const teamLines: Record<string, StatLineValues> = {};\n for (const teamId of counted) {\n const scored = counter(teamStats[teamId], 'score');\n const allowed = counter(teamStats[opponentOf(teamId) as string], 'score');\n const line: Record<string, number> = {\n gamesPlayed: 1,\n wins: scored > allowed ? 1 : 0,\n losses: scored < allowed ? 1 : 0,\n ties: scored === allowed ? 1 : 0,\n pointsFor: scored,\n pointsAgainst: allowed,\n };\n for (const field of spec.teamSeasonPlayerSums) line[field] = 0;\n for (const [entryId, playerLine] of Object.entries(playerStats)) {\n if (parseGameRosterPlayerId(entryId)?.teamId !== teamId) continue;\n for (const field of spec.teamSeasonPlayerSums)\n line[field] = (line[field] ?? 0) + counter(playerLine, field);\n }\n teamLines[teamId] = spec.deriveLine('teamSeason', line);\n }\n\n const membershipRows: Record<string, StatLineValues> = {};\n const personTotals = new Map<string, Record<string, number>>();\n const entryIds = [...new Set([...game.rosterPlayerIds, ...Object.keys(playerStats)])].sort();\n for (const entryId of entryIds) {\n const entry = parseGameRosterPlayerId(entryId);\n if (entry === null || !counted.has(entry.teamId)) continue;\n const row: Record<string, number> = {\n gamesPlayed: isCreditedGamePlayed(game, entryId) ? 1 : 0,\n };\n for (const field of spec.playerStatKeys) row[field] = counter(playerStats[entryId], field);\n if (isZeroStatLine(row)) continue;\n membershipRows[entryId] = spec.deriveLine('membershipRow', row);\n if (isGhostUserId(entry.userId)) continue;\n personTotals.set(entry.userId, addStatLines(personTotals.get(entry.userId) ?? {}, row));\n }\n\n const personLines: Record<string, StatLineValues> = {};\n for (const [userId, line] of personTotals)\n personLines[userId] = spec.deriveLine('lifetime', line);\n return { teamLines, membershipRows, personLines };\n}\n\n/** A contribution and the sign it is applied with: +1 to add a game, −1 to take it back. */\nexport interface SignedContribution {\n readonly contribution: GameContribution;\n readonly sign: 1 | -1;\n}\n\n/**\n * Several signed contributions folded into one, every line through the sport's `deriveLine` and a\n * line that sums to nothing left out — the week view's rows, a rebuild's totals, and a ghost link's\n * reversal of the old entries beside the merged one, all through the same arithmetic.\n */\nexport function sumGameContributions(\n deriveLine: ContributionSpec['deriveLine'],\n signed: readonly SignedContribution[],\n): GameContribution {\n const fold = (\n pick: (contribution: GameContribution) => Readonly<Record<string, StatLineValues>>,\n kind: StatLineKind,\n ) => {\n const totals = new Map<string, Record<string, number>>();\n for (const { contribution, sign } of signed) {\n for (const [key, line] of Object.entries(pick(contribution))) {\n totals.set(key, addStatLines(totals.get(key) ?? {}, line, sign));\n }\n }\n const lines: Record<string, StatLineValues> = {};\n for (const [key, line] of totals) {\n const derived = deriveLine(kind, line);\n if (!isZeroStatLine(derived)) lines[key] = derived;\n }\n return lines;\n };\n return {\n teamLines: fold((contribution) => contribution.teamLines, 'teamSeason'),\n membershipRows: fold((contribution) => contribution.membershipRows, 'membershipRow'),\n personLines: fold((contribution) => contribution.personLines, 'lifetime'),\n };\n}\n\n/**\n * A stored line moved by a delta: the line after, `null` when it sums to nothing, or the field that\n * would go below zero.\n */\nexport type StatLineChange =\n | { readonly ok: true; readonly after: StatLineValues | null }\n | { readonly ok: false; readonly field: string };\n\n/**\n * `before` (absent read as no line) moved by `delta` times `sign`, through the sport's `deriveLine`\n * for `kind`. A counter that would go below zero is refused, never clamped: it means the stored line\n * no longer holds what the games put there.\n */\nexport function applyStatLineDelta(\n deriveLine: ContributionSpec['deriveLine'],\n kind: StatLineKind,\n before: StatLineValues | undefined,\n delta: StatLineValues,\n sign: 1 | -1,\n): StatLineChange {\n // A derived field is recomputed from the moved counters, never moved itself, so a stale stored FP\n // can neither go below zero nor survive.\n const after = deriveLine(kind, addStatLines(before ?? {}, delta, sign));\n const below = Object.entries(after).find(([, value]) => typeof value === 'number' && value < 0);\n if (below) return { ok: false, field: below[0] };\n return { ok: true, after: isZeroStatLine(after) ? null : after };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"contribution.js","sourceRoot":"","sources":["../../src/sports/contribution.ts"],"names":[],"mappings":"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","sourcesContent":["// The aggregate half of the sport adapter contract: who a completed game credits a game played, and\n// what the game adds to every derived destination (RULE-QS-SPORT-921, RULE-QS-SPORT-924, RULE-QS-SPORT-927). Each sport\n// declares its mapping once, as a `ContributionSpec`; the two members built from it — `participation`\n// and `contribution` — are the only place a game's effect on standings, per-membership rows, lifetime\n// lines, and the hall of fame is decided. Finalize, reopen, a ghost link, every rebuild, validation,\n// and the week view all derive through them, so no two paths can disagree about what a game adds.\n//\n// Everything here is pure: no Firestore operation, no clock, no id minted (RULE-STD-ARCH-202). The aggregate\n// writer in Functions applies a contribution; the browser sums contributions for the week view.\n//\n// A stored derived line that sums to nothing — every counter and games played zero — is the same as\n// no line at all, as an all-zero player line is in a game's rollup: a contribution leaves one out,\n// and applying a contribution removes a line it returns to zero. That is what makes finalize followed\n// by reopen leave every destination exactly as it was.\n\nimport type { GameStats } from '../doc-schemas/sport-game.js';\nimport { parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport { isGhostUserId } from '../ids/minted-ids.js';\nimport type { LeagueType } from '../types/sports.js';\nimport type { StatLineKind } from '../types/stats.js';\nimport { isAllZeroLine } from './scoring.js';\nimport type { StatLineValues } from './scoring.js';\n\n/**\n * A completed game as its finalized record holds it: the record's events replayed to lines, and the\n * first page's sport, teams, and participation snapshot. Finalize builds it from the game it is\n * finalizing; every later path builds it from the record, never from the game document (RULE-QS-SPORT-924).\n */\nexport interface FinalizedGame {\n readonly leagueType: LeagueType;\n readonly teamIds: readonly string[];\n readonly stats: GameStats;\n readonly rosterPlayerIds: readonly string[];\n readonly absentRosterPlayerIds: readonly string[];\n readonly nonScoringRosterPlayerIds: readonly string[];\n}\n\n/**\n * What one completed game adds to each destination, before the caller's sign. Every line carries its\n * derived fields through the sport's `deriveLine`.\n */\nexport interface GameContribution {\n /** Per team id: the team's standings line (`teamSeason`). A team with no opponent has none. */\n readonly teamLines: Readonly<Record<string, StatLineValues>>;\n /** Per roster-entry id: that membership's row (`membershipRow`), credited to the team in its key. */\n readonly membershipRows: Readonly<Record<string, StatLineValues>>;\n /**\n * Per person who is not a ghost: the sum of that person's rows, games played included — what the\n * game adds to `lifetimeStats.{sportKey}` and, where the sport keeps one, the hall of fame\n * (`lifetime`). A person on both teams of the game counts both sides (RULE-QS-SPORT-928 keeps ghosts out).\n */\n readonly personLines: Readonly<Record<string, StatLineValues>>;\n}\n\nexport const EMPTY_GAME_CONTRIBUTION: GameContribution = {\n teamLines: {},\n membershipRows: {},\n personLines: {},\n};\n\n/** One sport's aggregate mapping, declared once by its adapter (RULE-QS-SPORT-927). */\nexport interface ContributionSpec {\n /** The counters of a player line that a membership row, a lifetime line, and the hall of fame sum. */\n readonly playerStatKeys: readonly string[];\n /** The player counters summed onto the team's standings line from the lines keyed to that team. */\n readonly teamSeasonPlayerSums: readonly string[];\n /**\n * Whether a present entry whose line holds nothing is credited no game played. Basketball's rule:\n * zero points, rebounds, and assists — zero points alone is not exclusion. Without it only absence\n * withholds a game played.\n */\n readonly zeroLineIsNonScoring: boolean;\n readonly deriveLine: (kind: StatLineKind, line: StatLineValues) => StatLineValues;\n}\n\n/** The value of a counter on a stored line, absent read as zero. */\nfunction counter(line: StatLineValues | undefined, field: string): number {\n const value = line?.[field];\n return typeof value === 'number' ? value : 0;\n}\n\n/** Whether every number a line holds is zero: a derived line that sums to nothing. */\nexport function isZeroStatLine(line: StatLineValues): boolean {\n return Object.values(line).every((value) => typeof value !== 'number' || value === 0);\n}\n\n/**\n * The games-played decision for every roster entry: the entries credited no game played. An absent\n * entry is never credited; under `zeroLineIsNonScoring` a present entry whose line holds nothing is\n * not either. Stored on the completed game as `nonScoringRosterPlayerIds` and read, never decided\n * again, by every later path (RULE-QS-SPORT-924). Returned in roster order.\n */\nexport function decideParticipation(\n spec: Pick<ContributionSpec, 'playerStatKeys' | 'zeroLineIsNonScoring'>,\n stats: GameStats,\n rosterPlayerIds: readonly string[],\n absentRosterPlayerIds: readonly string[],\n): string[] {\n if (!spec.zeroLineIsNonScoring) return [];\n const absent = new Set(absentRosterPlayerIds);\n const playerStats = stats.playerStats as Readonly<Record<string, StatLineValues>>;\n return rosterPlayerIds.filter((entryId) => {\n if (absent.has(entryId)) return true;\n const line = playerStats[entryId];\n return line === undefined || isAllZeroLine(line, spec.playerStatKeys);\n });\n}\n\n/** Whether the stored decision credits the entry a game played. */\nexport function isCreditedGamePlayed(\n game: Pick<FinalizedGame, 'absentRosterPlayerIds' | 'nonScoringRosterPlayerIds'>,\n entryId: string,\n): boolean {\n return (\n !game.absentRosterPlayerIds.includes(entryId) &&\n !game.nonScoringRosterPlayerIds.includes(entryId)\n );\n}\n\n/** `left` with every number of `right` added `sign` times; a field only one holds is kept. */\nexport function addStatLines(\n left: StatLineValues,\n right: StatLineValues,\n sign: 1 | -1 = 1,\n): Record<string, number> {\n const sum: Record<string, number> = {};\n for (const field of new Set([...Object.keys(left), ...Object.keys(right)])) {\n const [a, b] = [left[field], right[field]];\n if ((a !== undefined && typeof a !== 'number') || (b !== undefined && typeof b !== 'number'))\n continue;\n sum[field] = (a ?? 0) + sign * (b ?? 0);\n }\n return sum;\n}\n\n/**\n * The contribution of one completed game under a sport's mapping. Every line derives from the\n * replayed lines and the stored decision only, never from a stored total (RULE-QS-SPORT-922, RULE-QS-SPORT-924):\n *\n * - each team with an opponent: one game, a win, loss, or tie decided from the two replayed scores,\n * its points for and against, and the spec's player counters summed from the lines keyed to it;\n * - each roster entry of such a team — and any line keyed to one, rostered or not, so no recorded\n * stat is lost — its row: games played per the stored decision, and the sport's counters, credited\n * to the team in its key;\n * - each person who is not a ghost: the sum of their rows.\n *\n * A row or person line that sums to nothing is left out.\n */\nexport function buildGameContribution(\n spec: ContributionSpec,\n game: FinalizedGame,\n): GameContribution {\n const teamStats = game.stats.teamStats as Readonly<Record<string, StatLineValues>>;\n const playerStats = game.stats.playerStats as Readonly<Record<string, StatLineValues>>;\n const opponentOf = (teamId: string): string | undefined =>\n game.teamIds.find((other) => other !== teamId);\n const counted = new Set(game.teamIds.filter((teamId) => opponentOf(teamId) !== undefined));\n\n const teamLines: Record<string, StatLineValues> = {};\n for (const teamId of counted) {\n const scored = counter(teamStats[teamId], 'score');\n const allowed = counter(teamStats[opponentOf(teamId) as string], 'score');\n const line: Record<string, number> = {\n gamesPlayed: 1,\n wins: scored > allowed ? 1 : 0,\n losses: scored < allowed ? 1 : 0,\n ties: scored === allowed ? 1 : 0,\n pointsFor: scored,\n pointsAgainst: allowed,\n };\n for (const field of spec.teamSeasonPlayerSums) line[field] = 0;\n for (const [entryId, playerLine] of Object.entries(playerStats)) {\n if (parseGameRosterPlayerId(entryId)?.teamId !== teamId) continue;\n for (const field of spec.teamSeasonPlayerSums)\n line[field] = (line[field] ?? 0) + counter(playerLine, field);\n }\n teamLines[teamId] = spec.deriveLine('teamSeason', line);\n }\n\n const membershipRows: Record<string, StatLineValues> = {};\n const personTotals = new Map<string, Record<string, number>>();\n const entryIds = [...new Set([...game.rosterPlayerIds, ...Object.keys(playerStats)])].sort();\n for (const entryId of entryIds) {\n const entry = parseGameRosterPlayerId(entryId);\n if (entry === null || !counted.has(entry.teamId)) continue;\n const row: Record<string, number> = {\n gamesPlayed: isCreditedGamePlayed(game, entryId) ? 1 : 0,\n };\n for (const field of spec.playerStatKeys) row[field] = counter(playerStats[entryId], field);\n if (isZeroStatLine(row)) continue;\n membershipRows[entryId] = spec.deriveLine('membershipRow', row);\n if (isGhostUserId(entry.userId)) continue;\n personTotals.set(entry.userId, addStatLines(personTotals.get(entry.userId) ?? {}, row));\n }\n\n const personLines: Record<string, StatLineValues> = {};\n for (const [userId, line] of personTotals)\n personLines[userId] = spec.deriveLine('lifetime', line);\n return { teamLines, membershipRows, personLines };\n}\n\n/** A contribution and the sign it is applied with: +1 to add a game, −1 to take it back. */\nexport interface SignedContribution {\n readonly contribution: GameContribution;\n readonly sign: 1 | -1;\n}\n\n/**\n * Several signed contributions folded into one, every line through the sport's `deriveLine` and a\n * line that sums to nothing left out — the week view's rows, a rebuild's totals, and a ghost link's\n * reversal of the old entries beside the merged one, all through the same arithmetic.\n */\nexport function sumGameContributions(\n deriveLine: ContributionSpec['deriveLine'],\n signed: readonly SignedContribution[],\n): GameContribution {\n const fold = (\n pick: (contribution: GameContribution) => Readonly<Record<string, StatLineValues>>,\n kind: StatLineKind,\n ) => {\n const totals = new Map<string, Record<string, number>>();\n for (const { contribution, sign } of signed) {\n for (const [key, line] of Object.entries(pick(contribution))) {\n totals.set(key, addStatLines(totals.get(key) ?? {}, line, sign));\n }\n }\n const lines: Record<string, StatLineValues> = {};\n for (const [key, line] of totals) {\n const derived = deriveLine(kind, line);\n if (!isZeroStatLine(derived)) lines[key] = derived;\n }\n return lines;\n };\n return {\n teamLines: fold((contribution) => contribution.teamLines, 'teamSeason'),\n membershipRows: fold((contribution) => contribution.membershipRows, 'membershipRow'),\n personLines: fold((contribution) => contribution.personLines, 'lifetime'),\n };\n}\n\n/**\n * A stored line moved by a delta: the line after, `null` when it sums to nothing, or the field that\n * would go below zero.\n */\nexport type StatLineChange =\n | { readonly ok: true; readonly after: StatLineValues | null }\n | { readonly ok: false; readonly field: string };\n\n/**\n * `before` (absent read as no line) moved by `delta` times `sign`, through the sport's `deriveLine`\n * for `kind`. A counter that would go below zero is refused, never clamped: it means the stored line\n * no longer holds what the games put there.\n */\nexport function applyStatLineDelta(\n deriveLine: ContributionSpec['deriveLine'],\n kind: StatLineKind,\n before: StatLineValues | undefined,\n delta: StatLineValues,\n sign: 1 | -1,\n): StatLineChange {\n // A derived field is recomputed from the moved counters, never moved itself, so a stale stored FP\n // can neither go below zero nor survive.\n const after = deriveLine(kind, addStatLines(before ?? {}, delta, sign));\n const below = Object.entries(after).find(([, value]) => typeof value === 'number' && value < 0);\n if (below) return { ok: false, field: below[0] };\n return { ok: true, after: isZeroStatLine(after) ? null : after };\n}\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// round event records one round — one team's points, or a blank round for every team — and nothing
|
|
3
3
|
// names a player. A round is recorded once, in order: `validate` accepts a round event only for the
|
|
4
4
|
// first unrecorded round, and re-scoring a round is a void of its event plus a new one, so no curling
|
|
5
|
-
// event is ever superseded and undoing one always returns its round to unrecorded (SPORT-
|
|
5
|
+
// event is ever superseded and undoing one always returns its round to unrecorded (RULE-QS-SPORT-913).
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { CURLING_ROUND_COUNT, MAX_CURLING_ROUND_POINTS, MAX_STAT_EVENTS_PER_WRITE, } from '../constants/sports.js';
|
|
8
8
|
import { CURLING_ROUND_BLANK_EVENT_TYPE, CURLING_ROUND_SCORE_EVENT_TYPE, CURLING_STAT_EVENT_TYPE_KEYS, } from '../types/sports.js';
|
|
@@ -15,7 +15,7 @@ function isCurlingEvent(event) {
|
|
|
15
15
|
return isSportStatEventIn(CURLING_STAT_EVENT_TYPE_KEYS, event);
|
|
16
16
|
}
|
|
17
17
|
// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a
|
|
18
|
-
// curling game's (SPORT-
|
|
18
|
+
// curling game's (RULE-QS-SPORT-902).
|
|
19
19
|
function ownStats(state) {
|
|
20
20
|
return state;
|
|
21
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"curling-scoring.js","sourceRoot":"","sources":["../../src/sports/curling-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The curling adapter's scoring, display, and bulk-entry members. Curling is team-scored by round: a\n// round event records one round — one team's points, or a blank round for every team — and nothing\n// names a player. A round is recorded once, in order: `validate` accepts a round event only for the\n// first unrecorded round, and re-scoring a round is a void of its event plus a new one, so no curling\n// event is ever superseded and undoing one always returns its round to unrecorded (SPORT-103).\n\nimport { z } from 'zod';\nimport {\n CURLING_ROUND_COUNT,\n MAX_CURLING_ROUND_POINTS,\n MAX_STAT_EVENTS_PER_WRITE,\n} from '../constants/sports.js';\nimport type { CurlingTeamStats, GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { CurlingGameStats, GameStats } from '../doc-schemas/sport-game.js';\nimport {\n CURLING_ROUND_BLANK_EVENT_TYPE,\n CURLING_ROUND_SCORE_EVENT_TYPE,\n CURLING_STAT_EVENT_TYPE_KEYS,\n} from '../types/sports.js';\nimport type { CurlingStatEventType, CurlingTeamStatKey } from '../types/sports.js';\nimport { STAT_VOID_EVENT_TYPE } from '../types/stats.js';\nimport {\n BULK_ENTRY_TOO_LARGE_MESSAGE,\n bulkEntryCountField,\n type BulkEntryContext,\n type RoundsBulkEntry,\n type RoundsValues,\n type StatEventDraft,\n} from './bulk-entry.js';\nimport {\n buildGameContribution,\n decideParticipation,\n type ContributionSpec,\n} from './contribution.js';\nimport {\n FINALIZATION_READY,\n STAT_EVENT_ACCEPTED,\n isSportStatEventIn,\n isStatCorrection,\n refuseStatEvent,\n type CorrectableFields,\n type FinalizationReadiness,\n type GameTeamLine,\n type SportScoring,\n type SportStatEventBody,\n type StatEventBody,\n} from './scoring.js';\nimport {\n describeStoreEvent,\n type RoundsDisplay,\n type RoundsScoringPanel,\n type Scoreboard,\n type StatEventLabel,\n type StatEventNameResolver,\n type StatFieldLabels,\n} from './scoring-display.js';\n\ntype CurlingEvent = Extract<SportStatEventBody, { type: CurlingStatEventType }>;\n\nfunction isCurlingEvent(event: StatEventBody | undefined): event is CurlingEvent {\n return isSportStatEventIn(CURLING_STAT_EVENT_TYPE_KEYS, event);\n}\n\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\n// curling game's (SPORT-002).\nfunction ownStats(state: GameStats): CurlingGameStats {\n return state as CurlingGameStats;\n}\n\nfunction ownTeamLine(line: GameTeamLine): CurlingTeamStats {\n return line as CurlingTeamStats;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Lines — a team's `score` is always the sum of its recorded rounds\n// ---------------------------------------------------------------------------------------------\n\ntype RoundSlots = CurlingTeamStats['roundScores'];\n\nfunction sumOfRounds(roundScores: RoundSlots): number {\n return roundScores.reduce<number>((total, slot) => total + (slot ?? 0), 0);\n}\n\n/** Every round unrecorded — `null`, where a blank round is `0`. */\nexport function emptyCurlingTeamLine(): CurlingTeamStats {\n const roundScores: RoundSlots = Array.from({ length: CURLING_ROUND_COUNT }, () => null);\n return { score: sumOfRounds(roundScores), roundScores };\n}\n\nfunction withRound(\n line: CurlingTeamStats,\n roundNumber: number,\n slot: number | null,\n): CurlingTeamStats {\n const roundScores = [...line.roundScores];\n roundScores[roundNumber - 1] = slot;\n return { score: sumOfRounds(roundScores), roundScores };\n}\n\n/**\n * Record or clear one round on every team line: a scored round is the scoring team's points and 0\n * for the others, a blank round 0 for all, and a cleared round `null` for all.\n */\nfunction setRound(stateIn: GameStats, event: CurlingEvent, recorded: boolean): CurlingGameStats {\n const state = ownStats(stateIn);\n const teamStats = { ...state.teamStats };\n const scoringTeamId = event.type === CURLING_ROUND_SCORE_EVENT_TYPE ? event.teamId : undefined;\n if (scoringTeamId !== undefined && teamStats[scoringTeamId] === undefined) {\n teamStats[scoringTeamId] = emptyCurlingTeamLine();\n }\n for (const [teamId, line] of Object.entries(teamStats)) {\n const points =\n event.type === CURLING_ROUND_SCORE_EVENT_TYPE && teamId === scoringTeamId ? event.points : 0;\n teamStats[teamId] = withRound(line, event.roundNumber, recorded ? points : null);\n }\n return { teamStats, playerStats: state.playerStats };\n}\n\nfunction nextCurlingRound(stateIn: GameStats): number | null {\n const lines = Object.values(ownStats(stateIn).teamStats);\n for (let roundNumber = 1; roundNumber <= CURLING_ROUND_COUNT; roundNumber += 1) {\n if (lines.length === 0 || lines.some((line) => line.roundScores[roundNumber - 1] === null))\n return roundNumber;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Scoring\n// ---------------------------------------------------------------------------------------------\n\n// No curling field is correctable: a curling result changes only by reopening the game and\n// re-scoring the round.\nconst CORRECTABLE_FIELDS: CorrectableFields = { team: [], player: [] };\n\nfunction namesPlayer(event: StatEventBody): boolean {\n return 'playerUserId' in event && event.playerUserId !== undefined;\n}\n\nconst validate: SportScoring['validate'] = (state, event, roster) => {\n if (!isCurlingEvent(event)) return refuseStatEvent('eventTypeNotInSport');\n if (namesPlayer(event)) return refuseStatEvent('playerNotAllowed');\n if (event.type === CURLING_ROUND_SCORE_EVENT_TYPE && !roster.teamIds.includes(event.teamId)) {\n return refuseStatEvent('teamNotInGame');\n }\n const next = nextCurlingRound(state);\n if (next === null) return refuseStatEvent('roundsComplete');\n return event.roundNumber === next ? STAT_EVENT_ACCEPTED : refuseStatEvent('roundNotNext');\n};\n\nfunction finalizationReadiness(state: GameStats): FinalizationReadiness {\n return nextCurlingRound(state) === null\n ? FINALIZATION_READY\n : { ready: false, reason: 'roundsIncomplete' };\n}\n\n// What a completed curling game adds: the standings line from the two scores, and a game played\n// to every present roster entry, whatever its team scored. There are no individual stats, so only\n// absence withholds a game played and no entry is ever non-scoring.\nconst CURLING_CONTRIBUTION: ContributionSpec = {\n playerStatKeys: [],\n teamSeasonPlayerSums: [],\n zeroLineIsNonScoring: false,\n deriveLine: (_kind, line) => line,\n};\n\nexport const CURLING_SCORING: SportScoring = {\n validate,\n apply: (state, event) => {\n if (isStatCorrection(event)) throw new Error('No curling field is correctable.');\n if (!isCurlingEvent(event))\n throw new Error(`The curling adapter does not apply ${event.type}.`);\n return setRound(state, event, true);\n },\n undo: (state, target) => {\n if (!isCurlingEvent(target))\n throw new Error(`The curling adapter does not undo ${target.type}.`);\n return setRound(state, target, false);\n },\n // A curling line has no derived field.\n deriveLine: CURLING_CONTRIBUTION.deriveLine,\n correctableFields: CORRECTABLE_FIELDS,\n nextRound: nextCurlingRound,\n finalizationReadiness,\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\n decideParticipation(CURLING_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\n contribution: (game) => buildGameContribution(CURLING_CONTRIBUTION, game),\n};\n\n// ---------------------------------------------------------------------------------------------\n// Display\n// ---------------------------------------------------------------------------------------------\n\nexport const CURLING_ROUNDS: RoundsDisplay = { count: CURLING_ROUND_COUNT, label: 'Round' };\n\nexport const CURLING_EVENT_LABELS = {\n [CURLING_ROUND_SCORE_EVENT_TYPE]: { button: 'Score', play: 'Round score' },\n [CURLING_ROUND_BLANK_EVENT_TYPE]: { button: '+0 (Blank)', play: 'Blank end' },\n} as const satisfies Record<CurlingStatEventType, StatEventLabel>;\n\nexport const CURLING_FIELD_LABELS = {\n team: { score: 'Score' },\n player: {},\n} as const satisfies StatFieldLabels<CurlingTeamStatKey, never>;\n\nexport function describeCurlingEvent(event: GameStatEvent, resolve: StatEventNameResolver): string {\n const storeEvent = describeStoreEvent(event, resolve, CURLING_FIELD_LABELS);\n if (storeEvent !== null) return storeEvent;\n if (!isCurlingEvent(event)) return event.type;\n const round = `${CURLING_ROUNDS.label} ${event.roundNumber}`;\n if (event.type === CURLING_ROUND_BLANK_EVENT_TYPE)\n return `${round}: ${CURLING_EVENT_LABELS[event.type].play.toLowerCase()}`;\n return `${round}: ${resolve.team(event.teamId)} scored ${event.points}`;\n}\n\nexport const CURLING_SCOREBOARD: Scoreboard = {\n figures: [{ id: 'points', label: 'Points', value: (teamLine) => teamLine.score }],\n jumpBall: false,\n roundSlots: (teamLine) => ownTeamLine(teamLine).roundScores,\n};\n\nexport const CURLING_SCORING_PANEL: RoundsScoringPanel = {\n kind: 'rounds',\n scoreEventType: CURLING_ROUND_SCORE_EVENT_TYPE,\n blankEventType: CURLING_ROUND_BLANK_EVENT_TYPE,\n maxPoints: MAX_CURLING_ROUND_POINTS,\n};\n\n// ---------------------------------------------------------------------------------------------\n// Bulk entry — one row per round, a cell per team\n// ---------------------------------------------------------------------------------------------\n\nexport const CURLING_ROUND_POINTS_MESSAGE = `A round is worth 0 to ${MAX_CURLING_ROUND_POINTS} points.`;\nexport const CURLING_ONE_SCORING_TEAM_MESSAGE =\n 'A round is scored by one team (the other 0 or empty), blank (0 for every team), or left empty.';\nexport const CURLING_NO_SKIPPED_ROUND_MESSAGE =\n 'Rounds are recorded in order: fill in every earlier round first.';\n\ntype RoundOutcome =\n | { readonly kind: 'unrecorded' }\n | { readonly kind: 'blank' }\n | { readonly kind: 'scored'; readonly teamId: string; readonly points: number }\n | { readonly kind: 'invalid' };\n\nfunction roundOutcome(\n row: Readonly<Record<string, number | null | undefined>>,\n teamIds: readonly string[],\n): RoundOutcome {\n const cells = teamIds.map((teamId) => ({ teamId, points: row[teamId] ?? null }));\n if (cells.every((cell) => cell.points === null)) return { kind: 'unrecorded' };\n if (cells.every((cell) => cell.points === 0)) return { kind: 'blank' };\n const scorers = cells.filter((cell) => cell.points !== null && cell.points > 0);\n const [scorer] = scorers;\n if (scorers.length === 1 && scorer !== undefined && scorer.points !== null) {\n return { kind: 'scored', teamId: scorer.teamId, points: scorer.points };\n }\n return { kind: 'invalid' };\n}\n\nfunction sameOutcome(left: RoundOutcome, right: RoundOutcome): boolean {\n if (left.kind === 'scored' && right.kind === 'scored')\n return left.teamId === right.teamId && left.points === right.points;\n return left.kind === right.kind;\n}\n\nfunction storedRound(\n context: BulkEntryContext,\n roundNumber: number,\n): Record<string, number | null> {\n const state = ownStats(context.state);\n return Object.fromEntries(\n context.game.teamIds.map((teamId) => [\n teamId,\n state.teamStats[teamId]?.roundScores[roundNumber - 1] ?? null,\n ]),\n );\n}\n\nfunction roundDrafts(values: RoundsValues, context: BulkEntryContext): StatEventDraft[] {\n const { teamIds } = context.game;\n const liveEventByRound = new Map(\n context.liveRoundEvents.map((event) => [event.roundNumber, event.eventId]),\n );\n const voids: StatEventDraft[] = [];\n const recorded: StatEventDraft[] = [];\n values.rounds.forEach((row, index) => {\n const roundNumber = index + 1;\n const entered = roundOutcome(row, teamIds);\n if (sameOutcome(entered, roundOutcome(storedRound(context, roundNumber), teamIds))) return;\n const liveEventId = liveEventByRound.get(roundNumber);\n if (liveEventId !== undefined)\n voids.push({ type: STAT_VOID_EVENT_TYPE, targetEventId: liveEventId });\n if (entered.kind === 'scored') {\n recorded.push({\n type: CURLING_ROUND_SCORE_EVENT_TYPE,\n teamId: entered.teamId,\n roundNumber,\n points: entered.points,\n });\n } else if (entered.kind === 'blank') {\n recorded.push({ type: CURLING_ROUND_BLANK_EVENT_TYPE, roundNumber });\n }\n });\n // Every void first, then the new rounds in order: each changed round is unrecorded again before\n // any new event is validated, so each new event is the next unrecorded round when it arrives.\n return [...voids, ...recorded];\n}\n\nconst roundCell = bulkEntryCountField\n .pipe(z.number().max(MAX_CURLING_ROUND_POINTS, CURLING_ROUND_POINTS_MESSAGE))\n .nullable();\n\nexport const CURLING_BULK_ENTRY: RoundsBulkEntry = {\n kind: 'rounds',\n formDefaults: (context) => ({\n rounds: Array.from({ length: CURLING_ROUND_COUNT }, (_, index) =>\n storedRound(context, index + 1),\n ),\n }),\n formSchema: (context) => {\n const { teamIds } = context.game;\n const row = z.strictObject(Object.fromEntries(teamIds.map((teamId) => [teamId, roundCell])));\n return z\n .object({ rounds: z.array(row).length(CURLING_ROUND_COUNT) })\n .superRefine((values, issues) => {\n let unrecordedBefore = false;\n values.rounds.forEach((cells, index) => {\n const outcome = roundOutcome(cells, teamIds);\n if (outcome.kind === 'invalid') {\n issues.addIssue({\n code: 'custom',\n path: ['rounds', index],\n message: CURLING_ONE_SCORING_TEAM_MESSAGE,\n });\n } else if (outcome.kind === 'unrecorded') {\n unrecordedBefore = true;\n } else if (unrecordedBefore) {\n issues.addIssue({\n code: 'custom',\n path: ['rounds', index],\n message: CURLING_NO_SKIPPED_ROUND_MESSAGE,\n });\n }\n });\n if (roundDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\n issues.addIssue({\n code: 'custom',\n path: ['rounds'],\n message: BULK_ENTRY_TOO_LARGE_MESSAGE,\n });\n }\n });\n },\n toEvents: roundDrafts,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"curling-scoring.js","sourceRoot":"","sources":["../../src/sports/curling-scoring.ts"],"names":[],"mappings":"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","sourcesContent":["// The curling adapter's scoring, display, and bulk-entry members. Curling is team-scored by round: a\n// round event records one round — one team's points, or a blank round for every team — and nothing\n// names a player. A round is recorded once, in order: `validate` accepts a round event only for the\n// first unrecorded round, and re-scoring a round is a void of its event plus a new one, so no curling\n// event is ever superseded and undoing one always returns its round to unrecorded (RULE-QS-SPORT-913).\n\nimport { z } from 'zod';\nimport {\n CURLING_ROUND_COUNT,\n MAX_CURLING_ROUND_POINTS,\n MAX_STAT_EVENTS_PER_WRITE,\n} from '../constants/sports.js';\nimport type { CurlingTeamStats, GameStatEvent } from '../doc-schemas/game-stats.js';\nimport type { CurlingGameStats, GameStats } from '../doc-schemas/sport-game.js';\nimport {\n CURLING_ROUND_BLANK_EVENT_TYPE,\n CURLING_ROUND_SCORE_EVENT_TYPE,\n CURLING_STAT_EVENT_TYPE_KEYS,\n} from '../types/sports.js';\nimport type { CurlingStatEventType, CurlingTeamStatKey } from '../types/sports.js';\nimport { STAT_VOID_EVENT_TYPE } from '../types/stats.js';\nimport {\n BULK_ENTRY_TOO_LARGE_MESSAGE,\n bulkEntryCountField,\n type BulkEntryContext,\n type RoundsBulkEntry,\n type RoundsValues,\n type StatEventDraft,\n} from './bulk-entry.js';\nimport {\n buildGameContribution,\n decideParticipation,\n type ContributionSpec,\n} from './contribution.js';\nimport {\n FINALIZATION_READY,\n STAT_EVENT_ACCEPTED,\n isSportStatEventIn,\n isStatCorrection,\n refuseStatEvent,\n type CorrectableFields,\n type FinalizationReadiness,\n type GameTeamLine,\n type SportScoring,\n type SportStatEventBody,\n type StatEventBody,\n} from './scoring.js';\nimport {\n describeStoreEvent,\n type RoundsDisplay,\n type RoundsScoringPanel,\n type Scoreboard,\n type StatEventLabel,\n type StatEventNameResolver,\n type StatFieldLabels,\n} from './scoring-display.js';\n\ntype CurlingEvent = Extract<SportStatEventBody, { type: CurlingStatEventType }>;\n\nfunction isCurlingEvent(event: StatEventBody | undefined): event is CurlingEvent {\n return isSportStatEventIn(CURLING_STAT_EVENT_TYPE_KEYS, event);\n}\n\n// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a\n// curling game's (RULE-QS-SPORT-902).\nfunction ownStats(state: GameStats): CurlingGameStats {\n return state as CurlingGameStats;\n}\n\nfunction ownTeamLine(line: GameTeamLine): CurlingTeamStats {\n return line as CurlingTeamStats;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Lines — a team's `score` is always the sum of its recorded rounds\n// ---------------------------------------------------------------------------------------------\n\ntype RoundSlots = CurlingTeamStats['roundScores'];\n\nfunction sumOfRounds(roundScores: RoundSlots): number {\n return roundScores.reduce<number>((total, slot) => total + (slot ?? 0), 0);\n}\n\n/** Every round unrecorded — `null`, where a blank round is `0`. */\nexport function emptyCurlingTeamLine(): CurlingTeamStats {\n const roundScores: RoundSlots = Array.from({ length: CURLING_ROUND_COUNT }, () => null);\n return { score: sumOfRounds(roundScores), roundScores };\n}\n\nfunction withRound(\n line: CurlingTeamStats,\n roundNumber: number,\n slot: number | null,\n): CurlingTeamStats {\n const roundScores = [...line.roundScores];\n roundScores[roundNumber - 1] = slot;\n return { score: sumOfRounds(roundScores), roundScores };\n}\n\n/**\n * Record or clear one round on every team line: a scored round is the scoring team's points and 0\n * for the others, a blank round 0 for all, and a cleared round `null` for all.\n */\nfunction setRound(stateIn: GameStats, event: CurlingEvent, recorded: boolean): CurlingGameStats {\n const state = ownStats(stateIn);\n const teamStats = { ...state.teamStats };\n const scoringTeamId = event.type === CURLING_ROUND_SCORE_EVENT_TYPE ? event.teamId : undefined;\n if (scoringTeamId !== undefined && teamStats[scoringTeamId] === undefined) {\n teamStats[scoringTeamId] = emptyCurlingTeamLine();\n }\n for (const [teamId, line] of Object.entries(teamStats)) {\n const points =\n event.type === CURLING_ROUND_SCORE_EVENT_TYPE && teamId === scoringTeamId ? event.points : 0;\n teamStats[teamId] = withRound(line, event.roundNumber, recorded ? points : null);\n }\n return { teamStats, playerStats: state.playerStats };\n}\n\nfunction nextCurlingRound(stateIn: GameStats): number | null {\n const lines = Object.values(ownStats(stateIn).teamStats);\n for (let roundNumber = 1; roundNumber <= CURLING_ROUND_COUNT; roundNumber += 1) {\n if (lines.length === 0 || lines.some((line) => line.roundScores[roundNumber - 1] === null))\n return roundNumber;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------------------------\n// Scoring\n// ---------------------------------------------------------------------------------------------\n\n// No curling field is correctable: a curling result changes only by reopening the game and\n// re-scoring the round.\nconst CORRECTABLE_FIELDS: CorrectableFields = { team: [], player: [] };\n\nfunction namesPlayer(event: StatEventBody): boolean {\n return 'playerUserId' in event && event.playerUserId !== undefined;\n}\n\nconst validate: SportScoring['validate'] = (state, event, roster) => {\n if (!isCurlingEvent(event)) return refuseStatEvent('eventTypeNotInSport');\n if (namesPlayer(event)) return refuseStatEvent('playerNotAllowed');\n if (event.type === CURLING_ROUND_SCORE_EVENT_TYPE && !roster.teamIds.includes(event.teamId)) {\n return refuseStatEvent('teamNotInGame');\n }\n const next = nextCurlingRound(state);\n if (next === null) return refuseStatEvent('roundsComplete');\n return event.roundNumber === next ? STAT_EVENT_ACCEPTED : refuseStatEvent('roundNotNext');\n};\n\nfunction finalizationReadiness(state: GameStats): FinalizationReadiness {\n return nextCurlingRound(state) === null\n ? FINALIZATION_READY\n : { ready: false, reason: 'roundsIncomplete' };\n}\n\n// What a completed curling game adds: the standings line from the two scores, and a game played\n// to every present roster entry, whatever its team scored. There are no individual stats, so only\n// absence withholds a game played and no entry is ever non-scoring.\nconst CURLING_CONTRIBUTION: ContributionSpec = {\n playerStatKeys: [],\n teamSeasonPlayerSums: [],\n zeroLineIsNonScoring: false,\n deriveLine: (_kind, line) => line,\n};\n\nexport const CURLING_SCORING: SportScoring = {\n validate,\n apply: (state, event) => {\n if (isStatCorrection(event)) throw new Error('No curling field is correctable.');\n if (!isCurlingEvent(event))\n throw new Error(`The curling adapter does not apply ${event.type}.`);\n return setRound(state, event, true);\n },\n undo: (state, target) => {\n if (!isCurlingEvent(target))\n throw new Error(`The curling adapter does not undo ${target.type}.`);\n return setRound(state, target, false);\n },\n // A curling line has no derived field.\n deriveLine: CURLING_CONTRIBUTION.deriveLine,\n correctableFields: CORRECTABLE_FIELDS,\n nextRound: nextCurlingRound,\n finalizationReadiness,\n participation: (state, rosterPlayerIds, absentRosterPlayerIds) =>\n decideParticipation(CURLING_CONTRIBUTION, state, rosterPlayerIds, absentRosterPlayerIds),\n contribution: (game) => buildGameContribution(CURLING_CONTRIBUTION, game),\n};\n\n// ---------------------------------------------------------------------------------------------\n// Display\n// ---------------------------------------------------------------------------------------------\n\nexport const CURLING_ROUNDS: RoundsDisplay = { count: CURLING_ROUND_COUNT, label: 'Round' };\n\nexport const CURLING_EVENT_LABELS = {\n [CURLING_ROUND_SCORE_EVENT_TYPE]: { button: 'Score', play: 'Round score' },\n [CURLING_ROUND_BLANK_EVENT_TYPE]: { button: '+0 (Blank)', play: 'Blank end' },\n} as const satisfies Record<CurlingStatEventType, StatEventLabel>;\n\nexport const CURLING_FIELD_LABELS = {\n team: { score: 'Score' },\n player: {},\n} as const satisfies StatFieldLabels<CurlingTeamStatKey, never>;\n\nexport function describeCurlingEvent(event: GameStatEvent, resolve: StatEventNameResolver): string {\n const storeEvent = describeStoreEvent(event, resolve, CURLING_FIELD_LABELS);\n if (storeEvent !== null) return storeEvent;\n if (!isCurlingEvent(event)) return event.type;\n const round = `${CURLING_ROUNDS.label} ${event.roundNumber}`;\n if (event.type === CURLING_ROUND_BLANK_EVENT_TYPE)\n return `${round}: ${CURLING_EVENT_LABELS[event.type].play.toLowerCase()}`;\n return `${round}: ${resolve.team(event.teamId)} scored ${event.points}`;\n}\n\nexport const CURLING_SCOREBOARD: Scoreboard = {\n figures: [{ id: 'points', label: 'Points', value: (teamLine) => teamLine.score }],\n jumpBall: false,\n roundSlots: (teamLine) => ownTeamLine(teamLine).roundScores,\n};\n\nexport const CURLING_SCORING_PANEL: RoundsScoringPanel = {\n kind: 'rounds',\n scoreEventType: CURLING_ROUND_SCORE_EVENT_TYPE,\n blankEventType: CURLING_ROUND_BLANK_EVENT_TYPE,\n maxPoints: MAX_CURLING_ROUND_POINTS,\n};\n\n// ---------------------------------------------------------------------------------------------\n// Bulk entry — one row per round, a cell per team\n// ---------------------------------------------------------------------------------------------\n\nexport const CURLING_ROUND_POINTS_MESSAGE = `A round is worth 0 to ${MAX_CURLING_ROUND_POINTS} points.`;\nexport const CURLING_ONE_SCORING_TEAM_MESSAGE =\n 'A round is scored by one team (the other 0 or empty), blank (0 for every team), or left empty.';\nexport const CURLING_NO_SKIPPED_ROUND_MESSAGE =\n 'Rounds are recorded in order: fill in every earlier round first.';\n\ntype RoundOutcome =\n | { readonly kind: 'unrecorded' }\n | { readonly kind: 'blank' }\n | { readonly kind: 'scored'; readonly teamId: string; readonly points: number }\n | { readonly kind: 'invalid' };\n\nfunction roundOutcome(\n row: Readonly<Record<string, number | null | undefined>>,\n teamIds: readonly string[],\n): RoundOutcome {\n const cells = teamIds.map((teamId) => ({ teamId, points: row[teamId] ?? null }));\n if (cells.every((cell) => cell.points === null)) return { kind: 'unrecorded' };\n if (cells.every((cell) => cell.points === 0)) return { kind: 'blank' };\n const scorers = cells.filter((cell) => cell.points !== null && cell.points > 0);\n const [scorer] = scorers;\n if (scorers.length === 1 && scorer !== undefined && scorer.points !== null) {\n return { kind: 'scored', teamId: scorer.teamId, points: scorer.points };\n }\n return { kind: 'invalid' };\n}\n\nfunction sameOutcome(left: RoundOutcome, right: RoundOutcome): boolean {\n if (left.kind === 'scored' && right.kind === 'scored')\n return left.teamId === right.teamId && left.points === right.points;\n return left.kind === right.kind;\n}\n\nfunction storedRound(\n context: BulkEntryContext,\n roundNumber: number,\n): Record<string, number | null> {\n const state = ownStats(context.state);\n return Object.fromEntries(\n context.game.teamIds.map((teamId) => [\n teamId,\n state.teamStats[teamId]?.roundScores[roundNumber - 1] ?? null,\n ]),\n );\n}\n\nfunction roundDrafts(values: RoundsValues, context: BulkEntryContext): StatEventDraft[] {\n const { teamIds } = context.game;\n const liveEventByRound = new Map(\n context.liveRoundEvents.map((event) => [event.roundNumber, event.eventId]),\n );\n const voids: StatEventDraft[] = [];\n const recorded: StatEventDraft[] = [];\n values.rounds.forEach((row, index) => {\n const roundNumber = index + 1;\n const entered = roundOutcome(row, teamIds);\n if (sameOutcome(entered, roundOutcome(storedRound(context, roundNumber), teamIds))) return;\n const liveEventId = liveEventByRound.get(roundNumber);\n if (liveEventId !== undefined)\n voids.push({ type: STAT_VOID_EVENT_TYPE, targetEventId: liveEventId });\n if (entered.kind === 'scored') {\n recorded.push({\n type: CURLING_ROUND_SCORE_EVENT_TYPE,\n teamId: entered.teamId,\n roundNumber,\n points: entered.points,\n });\n } else if (entered.kind === 'blank') {\n recorded.push({ type: CURLING_ROUND_BLANK_EVENT_TYPE, roundNumber });\n }\n });\n // Every void first, then the new rounds in order: each changed round is unrecorded again before\n // any new event is validated, so each new event is the next unrecorded round when it arrives.\n return [...voids, ...recorded];\n}\n\nconst roundCell = bulkEntryCountField\n .pipe(z.number().max(MAX_CURLING_ROUND_POINTS, CURLING_ROUND_POINTS_MESSAGE))\n .nullable();\n\nexport const CURLING_BULK_ENTRY: RoundsBulkEntry = {\n kind: 'rounds',\n formDefaults: (context) => ({\n rounds: Array.from({ length: CURLING_ROUND_COUNT }, (_, index) =>\n storedRound(context, index + 1),\n ),\n }),\n formSchema: (context) => {\n const { teamIds } = context.game;\n const row = z.strictObject(Object.fromEntries(teamIds.map((teamId) => [teamId, roundCell])));\n return z\n .object({ rounds: z.array(row).length(CURLING_ROUND_COUNT) })\n .superRefine((values, issues) => {\n let unrecordedBefore = false;\n values.rounds.forEach((cells, index) => {\n const outcome = roundOutcome(cells, teamIds);\n if (outcome.kind === 'invalid') {\n issues.addIssue({\n code: 'custom',\n path: ['rounds', index],\n message: CURLING_ONE_SCORING_TEAM_MESSAGE,\n });\n } else if (outcome.kind === 'unrecorded') {\n unrecordedBefore = true;\n } else if (unrecordedBefore) {\n issues.addIssue({\n code: 'custom',\n path: ['rounds', index],\n message: CURLING_NO_SKIPPED_ROUND_MESSAGE,\n });\n }\n });\n if (roundDrafts(values, context).length > MAX_STAT_EVENTS_PER_WRITE) {\n issues.addIssue({\n code: 'custom',\n path: ['rounds'],\n message: BULK_ENTRY_TOO_LARGE_MESSAGE,\n });\n }\n });\n },\n toEvents: roundDrafts,\n};\n"]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// the game is finalized by hand. Bulk entry takes each team's final score.
|
|
4
4
|
//
|
|
5
5
|
// `apply` and `undo` are one function over an event's points with opposite signs, so they are exact
|
|
6
|
-
// inverses by construction (SPORT-
|
|
6
|
+
// inverses by construction (RULE-QS-SPORT-913).
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { FOOTBALL_POINTS_BY_EVENT_TYPE, MAX_STAT_EVENTS_PER_WRITE, TEAM_NOT_IN_GAME_MESSAGE, } from '../constants/sports.js';
|
|
9
9
|
import { FOOTBALL_BULK_EDIT_EVENT_TYPE, FOOTBALL_ONE_POINT_EVENT_TYPE, FOOTBALL_SCORE_EVENT_TYPE_KEYS, FOOTBALL_SIX_POINT_EVENT_TYPE, FOOTBALL_STAT_EVENT_TYPE_KEYS, FOOTBALL_TEAM_STAT_KEYS, FOOTBALL_TWO_POINT_EVENT_TYPE, } from '../types/sports.js';
|
|
@@ -15,7 +15,7 @@ function isFootballEvent(event) {
|
|
|
15
15
|
return isSportStatEventIn(FOOTBALL_STAT_EVENT_TYPE_KEYS, event);
|
|
16
16
|
}
|
|
17
17
|
// The registry chose this adapter by the game's `leagueType`, so the rollup it is handed is a
|
|
18
|
-
// football game's (SPORT-
|
|
18
|
+
// football game's (RULE-QS-SPORT-902).
|
|
19
19
|
function ownStats(state) {
|
|
20
20
|
return state;
|
|
21
21
|
}
|