import { useAtomValue, useSetAtom } from "jotai"; import { useEffect } from "react"; import { useTimer } from "react-use-precision-timer"; import { useDebouncedCallback } from "use-debounce"; import type { components } from "../.server/api/schema"; import useWebSocket, { ReadyState } from "../hooks/useWebSocket"; import { gameStartAtom, gameStateKindAtom, handleSubmitCodeAtom, handleWsConnectionClosedAtom, handleWsExecResultMessageAtom, handleWsSubmitResultMessageAtom, setCurrentTimestampAtom, setGameStateConnectingAtom, setGameStateWaitingAtom, } from "../states/play"; import GolfPlayAppConnecting from "./GolfPlayApps/GolfPlayAppConnecting"; import GolfPlayAppFinished from "./GolfPlayApps/GolfPlayAppFinished"; import GolfPlayAppGaming from "./GolfPlayApps/GolfPlayAppGaming"; import GolfPlayAppStarting from "./GolfPlayApps/GolfPlayAppStarting"; import GolfPlayAppWaiting from "./GolfPlayApps/GolfPlayAppWaiting"; type GamePlayerMessageS2C = components["schemas"]["GamePlayerMessageS2C"]; type GamePlayerMessageC2S = components["schemas"]["GamePlayerMessageC2S"]; type Game = components["schemas"]["Game"]; type User = components["schemas"]["User"]; type Props = { game: Game; player: User; initialCode: string; sockToken: string; }; export default function GolfPlayApp({ game, player, initialCode, sockToken, }: Props) { const socketUrl = process.env.NODE_ENV === "development" ? `ws://localhost:8003/phperkaigi/2025/code-battle/sock/golf/${game.game_id}/play?token=${sockToken}` : `wss://t.nil.ninja/phperkaigi/2025/code-battle/sock/golf/${game.game_id}/play?token=${sockToken}`; const gameStateKind = useAtomValue(gameStateKindAtom); const setCurrentTimestamp = useSetAtom(setCurrentTimestampAtom); const gameStart = useSetAtom(gameStartAtom); const setGameStateConnecting = useSetAtom(setGameStateConnectingAtom); const setGameStateWaiting = useSetAtom(setGameStateWaitingAtom); const handleWsConnectionClosed = useSetAtom(handleWsConnectionClosedAtom); const handleWsExecResultMessage = useSetAtom(handleWsExecResultMessageAtom); const handleWsSubmitResultMessage = useSetAtom( handleWsSubmitResultMessageAtom, ); const handleSubmitCode = useSetAtom(handleSubmitCodeAtom); useTimer({ delay: 1000, startImmediately: true }, setCurrentTimestamp); const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket< GamePlayerMessageS2C, GamePlayerMessageC2S >(socketUrl); const playerProfile = { displayName: player.display_name, iconPath: player.icon_path ?? null, }; const onCodeChange = useDebouncedCallback((code: string) => { console.log("player:c2s:code"); sendJsonMessage({ type: "player:c2s:code", data: { code }, }); const baseKey = `playerState:${game.game_id}:${player.user_id}`; window.localStorage.setItem(`${baseKey}:code`, code); }, 1000); const onCodeSubmit = useDebouncedCallback((code: string) => { if (code === "") { return; } console.log("player:c2s:submit"); sendJsonMessage({ type: "player:c2s:submit", data: { code }, }); handleSubmitCode(); }, 1000); if (readyState === ReadyState.UNINSTANTIATED) { throw new Error("WebSocket is not connected"); } useEffect(() => { if (readyState === ReadyState.CLOSING || readyState === ReadyState.CLOSED) { handleWsConnectionClosed(); } else if (readyState === ReadyState.CONNECTING) { setGameStateConnecting(); } else if (readyState === ReadyState.OPEN) { if (lastJsonMessage !== null) { console.log(lastJsonMessage.type); console.log(lastJsonMessage.data); if (lastJsonMessage.type === "player:s2c:start") { const { start_at } = lastJsonMessage.data; gameStart(start_at); } else if (lastJsonMessage.type === "player:s2c:execresult") { handleWsExecResultMessage( lastJsonMessage.data, (submissionResult) => { const baseKey = `playerState:${game.game_id}:${player.user_id}`; window.localStorage.setItem( `${baseKey}:submissionResult`, JSON.stringify(submissionResult), ); }, ); } else if (lastJsonMessage.type === "player:s2c:submitresult") { handleWsSubmitResultMessage( lastJsonMessage.data, (submissionResult, score) => { const baseKey = `playerState:${game.game_id}:${player.user_id}`; window.localStorage.setItem( `${baseKey}:submissionResult`, JSON.stringify(submissionResult), ); window.localStorage.setItem( `${baseKey}:score`, score === null ? "" : score.toString(), ); }, ); } } else { if (game.started_at) { gameStart(game.started_at); } else { setGameStateWaiting(); } } } }, [ game.game_id, game.started_at, player.user_id, sendJsonMessage, lastJsonMessage, readyState, gameStart, handleWsConnectionClosed, handleWsExecResultMessage, handleWsSubmitResultMessage, setGameStateConnecting, setGameStateWaiting, ]); if (gameStateKind === "connecting") { return ; } else if (gameStateKind === "waiting") { return ( ); } else if (gameStateKind === "starting") { return ; } else if (gameStateKind === "gaming") { return ( ); } else if (gameStateKind === "finished") { return ; } else { return null; } }