diff options
Diffstat (limited to 'frontend/app/states/watch.ts')
| -rw-r--r-- | frontend/app/states/watch.ts | 267 |
1 files changed, 42 insertions, 225 deletions
diff --git a/frontend/app/states/watch.ts b/frontend/app/states/watch.ts index 5f5c4db..d3cc723 100644 --- a/frontend/app/states/watch.ts +++ b/frontend/app/states/watch.ts @@ -1,255 +1,72 @@ import { atom } from "jotai"; -import { AudioController } from "../.client/audio/AudioController"; -import type { components } from "../.server/api/schema"; -import type { SubmitResult } from "../types/SubmitResult"; +import type { components } from "../api/schema"; -type RawGameState = - | { - kind: "connecting"; - startedAtTimestamp: null; - } - | { - kind: "waiting"; - startedAtTimestamp: null; - } - | { - kind: "starting"; - startedAtTimestamp: number; - }; - -const rawGameStateAtom = atom<RawGameState>({ - kind: "connecting", - startedAtTimestamp: null, -}); +const gameStartedAtAtom = atom<number | null>(null); +export const setGameStartedAtAtom = atom(null, (_, set, value: number | null) => + set(gameStartedAtAtom, value), +); -export type GameStateKind = - | "connecting" - | "waiting" - | "starting" - | "gaming" - | "finished"; +export type GameStateKind = "waiting" | "starting" | "gaming" | "finished"; +type LatestGameState = components["schemas"]["LatestGameState"]; +type RankingEntry = components["schemas"]["RankingEntry"]; export const gameStateKindAtom = atom<GameStateKind>((get) => { - const { kind: rawKind, startedAtTimestamp } = get(rawGameStateAtom); - if (rawKind === "connecting" || rawKind === "waiting") { - return rawKind; - } else { - const durationSeconds = get(rawDurationSecondsAtom); - const finishedAtTimestamp = startedAtTimestamp + durationSeconds; - const currentTimestamp = get(rawCurrentTimestampAtom); - if (currentTimestamp < startedAtTimestamp) { - return "starting"; - } else if (currentTimestamp < finishedAtTimestamp) { - return "gaming"; - } else { - return "finished"; - } + const startedAt = get(gameStartedAtAtom); + if (!startedAt) { + return "waiting"; } -}); -export const gameStartAtom = atom(null, (get, set, value: number) => { - const { kind } = get(rawGameStateAtom); - if (kind === "starting") { - return; + const durationSeconds = get(durationSecondsAtom); + const finishedAt = startedAt + durationSeconds; + const now = get(currentTimestampAtom); + if (now < startedAt) { + return "starting"; + } else if (now < finishedAt) { + return "gaming"; + } else { + return "finished"; } - set(rawGameStateAtom, { - kind: "starting", - startedAtTimestamp: value, - }); }); -export const setGameStateConnectingAtom = atom(null, (_, set) => - set(rawGameStateAtom, { kind: "connecting", startedAtTimestamp: null }), -); -export const setGameStateWaitingAtom = atom(null, (_, set) => - set(rawGameStateAtom, { kind: "waiting", startedAtTimestamp: null }), -); -const rawCurrentTimestampAtom = atom(0); +const currentTimestampAtom = atom(0); export const setCurrentTimestampAtom = atom(null, (_, set) => - set(rawCurrentTimestampAtom, Math.floor(Date.now() / 1000)), + set(currentTimestampAtom, Math.floor(Date.now() / 1000)), ); -const rawDurationSecondsAtom = atom<number>(0); +const durationSecondsAtom = atom<number>(0); export const setDurationSecondsAtom = atom(null, (_, set, value: number) => - set(rawDurationSecondsAtom, value), + set(durationSecondsAtom, value), ); export const startingLeftTimeSecondsAtom = atom<number | null>((get) => { - const { startedAtTimestamp } = get(rawGameStateAtom); - if (startedAtTimestamp === null) { + const startedAt = get(gameStartedAtAtom); + if (startedAt === null) { return null; } - const currentTimestamp = get(rawCurrentTimestampAtom); - return Math.max(0, startedAtTimestamp - currentTimestamp); + const currentTimestamp = get(currentTimestampAtom); + return Math.max(0, startedAt - currentTimestamp); }); export const gamingLeftTimeSecondsAtom = atom<number | null>((get) => { - const { startedAtTimestamp } = get(rawGameStateAtom); - if (startedAtTimestamp === null) { + const startedAt = get(gameStartedAtAtom); + if (startedAt === null) { return null; } - const durationSeconds = get(rawDurationSecondsAtom); - const finishedAtTimestamp = startedAtTimestamp + durationSeconds; - const currentTimestamp = get(rawCurrentTimestampAtom); - return Math.min( - durationSeconds, - Math.max(0, finishedAtTimestamp - currentTimestamp), - ); -}); - -export const handleWsConnectionClosedAtom = atom(null, (get, set) => { - const kind = get(gameStateKindAtom); - if (kind !== "finished") { - set(setGameStateConnectingAtom); - } -}); - -export const codeAAtom = atom(""); -export const codeBAtom = atom(""); -export const scoreAAtom = atom<number | null>(null); -export const scoreBAtom = atom<number | null>(null); -export const submitResultAAtom = atom<SubmitResult>({ - status: "waiting_submission", - execResults: [], -}); -export const submitResultBAtom = atom<SubmitResult>({ - status: "waiting_submission", - execResults: [], + const durationSeconds = get(durationSecondsAtom); + const finishedAt = startedAt + durationSeconds; + const currentTimestamp = get(currentTimestampAtom); + return Math.min(durationSeconds, Math.max(0, finishedAt - currentTimestamp)); }); -type GameWatcherMessageS2CSubmitPayload = - components["schemas"]["GameWatcherMessageS2CSubmitPayload"]; -type GameWatcherMessageS2CCodePayload = - components["schemas"]["GameWatcherMessageS2CCodePayload"]; -type GameWatcherMessageS2CExecResultPayload = - components["schemas"]["GameWatcherMessageS2CExecResultPayload"]; -type GameWatcherMessageS2CSubmitResultPayload = - components["schemas"]["GameWatcherMessageS2CSubmitResultPayload"]; +export const rankingAtom = atom<RankingEntry[]>([]); -export const handleWsCodeMessageAtom = atom( +const rawLatestGameStatesAtom = atom<{ + [key: string]: LatestGameState | undefined; +}>({}); +export const latestGameStatesAtom = atom((get) => get(rawLatestGameStatesAtom)); +export const setLatestGameStatesAtom = atom( null, - ( - _, - set, - data: GameWatcherMessageS2CCodePayload, - getTarget: <T>(player_id: number, atomA: T, atomB: T) => T, - callback: (player_id: number, code: string) => void, - ) => { - const { player_id, code } = data; - const codeAtom = getTarget(player_id, codeAAtom, codeBAtom); - set(codeAtom, code); - callback(player_id, code); + (_, set, value: { [key: string]: LatestGameState | undefined }) => { + set(rawLatestGameStatesAtom, value); }, ); - -export const handleWsSubmitMessageAtom = atom( - null, - ( - get, - set, - data: GameWatcherMessageS2CSubmitPayload, - getTarget: <T>(player_id: number, atomA: T, atomB: T) => T, - callback: (player_id: number, submissionResult: SubmitResult) => void, - ) => { - const { player_id } = data; - const submitResultAtom = getTarget( - player_id, - submitResultAAtom, - submitResultBAtom, - ); - const prev = get(submitResultAtom); - const newResult = { - status: "running" as const, - execResults: prev.execResults.map((r) => ({ - ...r, - status: "running" as const, - stdout: "", - stderr: "", - })), - }; - set(submitResultAtom, newResult); - callback(player_id, newResult); - }, -); - -export const handleWsExecResultMessageAtom = atom( - null, - ( - get, - set, - data: GameWatcherMessageS2CExecResultPayload, - getTarget: <T>(player_id: number, atomA: T, atomB: T) => T, - callback: (player_id: number, submissionResult: SubmitResult) => void, - ) => { - const { player_id, testcase_id, status, stdout, stderr } = data; - const submitResultAtom = getTarget( - player_id, - submitResultAAtom, - submitResultBAtom, - ); - const prev = get(submitResultAtom); - const newResult = { - ...prev, - execResults: prev.execResults.map((r) => - r.testcase_id === testcase_id && r.status === "running" - ? { - ...r, - status, - stdout, - stderr, - } - : r, - ), - }; - set(submitResultAtom, newResult); - callback(player_id, newResult); - }, -); - -export const handleWsSubmitResultMessageAtom = atom( - null, - ( - get, - set, - data: GameWatcherMessageS2CSubmitResultPayload, - getTarget: <T>(player_id: number, atomA: T, atomB: T) => T, - callback: ( - player_id: number, - submissionResult: SubmitResult, - score: number | null, - ) => void, - ) => { - const { player_id, status, score } = data; - const submitResultAtom = getTarget( - player_id, - submitResultAAtom, - submitResultBAtom, - ); - const scoreAtom = getTarget(player_id, scoreAAtom, scoreBAtom); - const prev = get(submitResultAtom); - const newResult = { - ...prev, - status, - }; - if (status !== "success") { - newResult.execResults = prev.execResults.map((r) => - r.status === "running" ? { ...r, status: "canceled" } : r, - ); - } else { - newResult.execResults = prev.execResults.map((r) => ({ - ...r, - status: "success", - })); - } - set(submitResultAtom, newResult); - if (status === "success" && score !== null) { - const currentScore = get(scoreAtom); - if (currentScore === null || score < currentScore) { - set(scoreAtom, score); - } - } - callback(player_id, newResult, score); - }, -); - -export const audioControllerAtom = atom<AudioController | null>(null); |
