aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app/components/GolfWatchApp.tsx
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2024-07-29 03:44:10 +0900
committernsfisis <nsfisis@gmail.com>2024-07-29 19:38:39 +0900
commitd73fd8bf5bf589a4a391c867e980761fadb647ce (patch)
tree15f2454b48cae461a6d8acc7edb2c2111d445d3e /frontend/app/components/GolfWatchApp.tsx
parent3f95e0e6d62267cf8863e98f3ab7de8971a91000 (diff)
downloadiosdc-japan-2025-albatross-d73fd8bf5bf589a4a391c867e980761fadb647ce.tar.gz
iosdc-japan-2025-albatross-d73fd8bf5bf589a4a391c867e980761fadb647ce.tar.zst
iosdc-japan-2025-albatross-d73fd8bf5bf589a4a391c867e980761fadb647ce.zip
feat: partially implement watching
Diffstat (limited to 'frontend/app/components/GolfWatchApp.tsx')
-rw-r--r--frontend/app/components/GolfWatchApp.tsx136
1 files changed, 136 insertions, 0 deletions
diff --git a/frontend/app/components/GolfWatchApp.tsx b/frontend/app/components/GolfWatchApp.tsx
new file mode 100644
index 0000000..bcd1f0f
--- /dev/null
+++ b/frontend/app/components/GolfWatchApp.tsx
@@ -0,0 +1,136 @@
+import type { components } from "../.server/api/schema";
+import { useState, useEffect } from "react";
+import useWebSocket, { ReadyState } from "react-use-websocket";
+import GolfWatchAppConnecting from "./GolfWatchApps/GolfWatchAppConnecting";
+import GolfWatchAppWaiting from "./GolfWatchApps/GolfWatchAppWaiting";
+import GolfWatchAppStarting from "./GolfWatchApps/GolfWatchAppStarting";
+import GolfWatchAppGaming from "./GolfWatchApps/GolfWatchAppGaming";
+import GolfWatchAppFinished from "./GolfWatchApps/GolfWatchAppFinished";
+
+type WebSocketMessage = components["schemas"]["GameWatcherMessageS2C"];
+
+type Game = components["schemas"]["Game"];
+type Problem = components["schemas"]["Problem"];
+
+type GameState = "connecting" | "waiting" | "starting" | "gaming" | "finished";
+
+export default function GolfWatchApp({ game }: { game: Game }) {
+ // const socketUrl = `wss://t.nil.ninja/iosdc/2024/sock/golf/${game.game_id}/play`;
+ const socketUrl =
+ process.env.NODE_ENV === "development"
+ ? `ws://localhost:8002/sock/golf/${game.game_id}/play`
+ : `ws://api-server/sock/golf/${game.game_id}/play`;
+
+ const { lastJsonMessage, readyState } = useWebSocket<WebSocketMessage>(
+ socketUrl,
+ {},
+ );
+
+ const [gameState, setGameState] = useState<GameState>("connecting");
+
+ const [problem, setProblem] = useState<Problem | null>(null);
+
+ const [startedAt, setStartedAt] = useState<number | null>(null);
+
+ const [timeLeftSeconds, setTimeLeftSeconds] = useState<number | null>(null);
+
+ useEffect(() => {
+ if (gameState === "starting" && startedAt !== null) {
+ const timer1 = setInterval(() => {
+ setTimeLeftSeconds((prev) => {
+ if (prev === null) {
+ return null;
+ }
+ if (prev <= 1) {
+ clearInterval(timer1);
+ setGameState("gaming");
+ return 0;
+ }
+ return prev - 1;
+ });
+ }, 1000);
+
+ const timer2 = setInterval(() => {
+ const nowSec = Math.floor(Date.now() / 1000);
+ const finishedAt = startedAt + game.duration_seconds;
+ if (nowSec >= finishedAt) {
+ clearInterval(timer2);
+ setGameState("finished");
+ }
+ }, 1000);
+
+ return () => {
+ clearInterval(timer1);
+ clearInterval(timer2);
+ };
+ }
+ }, [gameState, startedAt, game.duration_seconds]);
+
+ const [scoreA, setScoreA] = useState<number | null>(null);
+ const [scoreB, setScoreB] = useState<number | null>(null);
+ const [codeA, setCodeA] = useState<string>("");
+ const [codeB, setCodeB] = useState<string>("");
+
+ if (readyState === ReadyState.UNINSTANTIATED) {
+ throw new Error("WebSocket is not connected");
+ }
+
+ useEffect(() => {
+ if (readyState === ReadyState.CLOSING || readyState === ReadyState.CLOSED) {
+ if (gameState !== "finished") {
+ setGameState("connecting");
+ }
+ } else if (readyState === ReadyState.CONNECTING) {
+ setGameState("connecting");
+ } else if (readyState === ReadyState.OPEN) {
+ if (lastJsonMessage !== null) {
+ console.log(lastJsonMessage.type);
+ if (lastJsonMessage.type === "watcher:s2c:start") {
+ if (
+ gameState !== "starting" &&
+ gameState !== "gaming" &&
+ gameState !== "finished"
+ ) {
+ const { start_at } = lastJsonMessage.data;
+ setStartedAt(start_at);
+ const nowSec = Math.floor(Date.now() / 1000);
+ setTimeLeftSeconds(start_at - nowSec);
+ setGameState("starting");
+ }
+ } else if (lastJsonMessage.type === "watcher:s2c:code") {
+ const { player_id, code } = lastJsonMessage.data;
+ setCodeA(code);
+ } else if (lastJsonMessage.type === "watcher:s2c:execresult") {
+ const { score } = lastJsonMessage.data;
+ if (score !== null && (scoreA === null || score < scoreA)) {
+ setScoreA(score);
+ }
+ }
+ } else {
+ setGameState("waiting");
+ }
+ }
+ }, [lastJsonMessage, readyState, gameState, scoreA]);
+
+ if (gameState === "connecting") {
+ return <GolfWatchAppConnecting />;
+ } else if (gameState === "waiting") {
+ return <GolfWatchAppWaiting />;
+ } else if (gameState === "starting") {
+ return <GolfWatchAppStarting timeLeft={timeLeftSeconds!} />;
+ } else if (gameState === "gaming") {
+ return (
+ <GolfWatchAppGaming
+ problem={problem!.description}
+ codeA={codeA}
+ scoreA={scoreA}
+ codeB={codeB}
+ scoreB={scoreB}
+ />
+ );
+ } else if (gameState === "finished") {
+ return <GolfWatchAppFinished />;
+ } else {
+ return null;
+ }
+}