aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2024-08-10 13:31:52 +0900
committernsfisis <nsfisis@gmail.com>2024-08-10 19:55:50 +0900
commit9477788709127ffd5611caed0fa7ee191326ce00 (patch)
tree8053dfed468d525ce471eabdff2471a325f4ce97
parent47f95342097e8828059053c168e06cd44e0ab43b (diff)
downloadiosdc-japan-2024-albatross-9477788709127ffd5611caed0fa7ee191326ce00.tar.gz
iosdc-japan-2024-albatross-9477788709127ffd5611caed0fa7ee191326ce00.tar.zst
iosdc-japan-2024-albatross-9477788709127ffd5611caed0fa7ee191326ce00.zip
feat(frontend): partially implement watch page
-rw-r--r--frontend/app/components/GolfWatchApp.client.tsx66
-rw-r--r--frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx137
-rw-r--r--frontend/app/components/GolfWatchApps/GolfWatchAppStarting.tsx6
-rw-r--r--frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx8
4 files changed, 171 insertions, 46 deletions
diff --git a/frontend/app/components/GolfWatchApp.client.tsx b/frontend/app/components/GolfWatchApp.client.tsx
index 829f709..355f7e3 100644
--- a/frontend/app/components/GolfWatchApp.client.tsx
+++ b/frontend/app/components/GolfWatchApp.client.tsx
@@ -3,7 +3,9 @@ import useWebSocket, { ReadyState } from "react-use-websocket";
import type { components } from "../.server/api/schema";
import GolfWatchAppConnecting from "./GolfWatchApps/GolfWatchAppConnecting";
import GolfWatchAppFinished from "./GolfWatchApps/GolfWatchAppFinished";
-import GolfWatchAppGaming from "./GolfWatchApps/GolfWatchAppGaming";
+import GolfWatchAppGaming, {
+ PlayerInfo,
+} from "./GolfWatchApps/GolfWatchAppGaming";
import GolfWatchAppStarting from "./GolfWatchApps/GolfWatchAppStarting";
import GolfWatchAppWaiting from "./GolfWatchApps/GolfWatchAppWaiting";
@@ -34,12 +36,12 @@ export default function GolfWatchApp({
const [startedAt, setStartedAt] = useState<number | null>(null);
- const [timeLeftSeconds, setTimeLeftSeconds] = useState<number | null>(null);
+ const [leftTimeSeconds, setLeftTimeSeconds] = useState<number | null>(null);
useEffect(() => {
if (gameState === "starting" && startedAt !== null) {
const timer1 = setInterval(() => {
- setTimeLeftSeconds((prev) => {
+ setLeftTimeSeconds((prev) => {
if (prev === null) {
return null;
}
@@ -68,10 +70,23 @@ export default function GolfWatchApp({
}
}, [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>("");
+ const playerA = game.players[0];
+ const playerB = game.players[1];
+
+ const [playerInfoA, setPlayerInfoA] = useState<PlayerInfo>({
+ displayName: playerA?.display_name ?? null,
+ iconPath: playerA?.icon_path ?? null,
+ score: null,
+ code: "",
+ submissionResult: undefined,
+ });
+ const [playerInfoB, setPlayerInfoB] = useState<PlayerInfo>({
+ displayName: playerB?.display_name ?? null,
+ iconPath: playerB?.icon_path ?? null,
+ score: null,
+ code: "",
+ submissionResult: undefined,
+ });
if (readyState === ReadyState.UNINSTANTIATED) {
throw new Error("WebSocket is not connected");
@@ -96,38 +111,51 @@ export default function GolfWatchApp({
const { start_at } = lastJsonMessage.data;
setStartedAt(start_at);
const nowSec = Math.floor(Date.now() / 1000);
- setTimeLeftSeconds(start_at - nowSec);
+ setLeftTimeSeconds(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);
+ if (player_id === playerA?.user_id) {
+ setPlayerInfoA((prev) => ({ ...prev, code }));
+ } else if (player_id === playerB?.user_id) {
+ setPlayerInfoB((prev) => ({ ...prev, code }));
+ } else {
+ throw new Error("Unknown player_id");
}
+ } 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]);
+ }, [
+ lastJsonMessage,
+ readyState,
+ gameState,
+ playerInfoA,
+ playerInfoB,
+ playerA?.user_id,
+ playerB?.user_id,
+ ]);
if (gameState === "connecting") {
return <GolfWatchAppConnecting />;
} else if (gameState === "waiting") {
return <GolfWatchAppWaiting />;
} else if (gameState === "starting") {
- return <GolfWatchAppStarting timeLeft={timeLeftSeconds!} />;
+ return <GolfWatchAppStarting leftTimeSeconds={leftTimeSeconds!} />;
} else if (gameState === "gaming") {
return (
<GolfWatchAppGaming
problem={game.problem!.description}
- codeA={codeA}
- scoreA={scoreA}
- codeB={codeB}
- scoreB={scoreB}
+ playerInfoA={playerInfoA}
+ playerInfoB={playerInfoB}
+ leftTimeSeconds={leftTimeSeconds!}
/>
);
} else if (gameState === "finished") {
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
index 22277f8..470a00c 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
@@ -1,41 +1,130 @@
type Props = {
problem: string;
- codeA: string;
- scoreA: number | null;
- codeB: string;
- scoreB: number | null;
+ playerInfoA: PlayerInfo;
+ playerInfoB: PlayerInfo;
+ leftTimeSeconds: number;
+};
+
+export type PlayerInfo = {
+ displayName: string | null;
+ iconPath: string | null;
+ score: number | null;
+ code: string | null;
+ submissionResult?: SubmissionResult;
+};
+
+type SubmissionResult = {
+ status: string;
+ nextScore: number;
+ executionResults: ExecutionResult[];
+};
+
+type ExecutionResult = {
+ status: string;
+ label: string;
+ output: string;
};
export default function GolfWatchAppGaming({
problem,
- codeA,
- scoreA,
- codeB,
- scoreB,
+ playerInfoA,
+ playerInfoB,
+ leftTimeSeconds,
}: Props) {
+ const leftTime = (() => {
+ const m = Math.floor(leftTimeSeconds / 60);
+ const s = leftTimeSeconds % 60;
+ return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
+ })();
+ const scoreRatio = (() => {
+ const scoreA = playerInfoA.score ?? 0;
+ const scoreB = playerInfoB.score ?? 0;
+ const totalScore = scoreA + scoreB;
+ return totalScore === 0 ? 50 : (scoreA / totalScore) * 100;
+ })();
+
return (
- <div style={{ display: "flex", flexDirection: "column" }}>
- <div style={{ display: "flex", flex: 1, justifyContent: "center" }}>
- <div>{problem}</div>
+ <div className="grid h-full w-full grid-rows-[auto_auto_1fr_auto]">
+ <div className="grid grid-cols-[1fr_auto_1fr]">
+ <div className="grid justify-start bg-red-500 p-2 text-white">
+ {playerInfoA.displayName}
+ </div>
+ <div className="grid justify-center p-2">{leftTime}</div>
+ <div className="grid justify-end bg-blue-500 p-2 text-white">
+ {playerInfoB.displayName}
+ </div>
</div>
- <div style={{ display: "flex", flex: 3 }}>
- <div style={{ display: "flex", flex: 3, flexDirection: "column" }}>
- <div style={{ flex: 1, justifyContent: "center" }}>{scoreA}</div>
- <div style={{ flex: 3 }}>
- <pre>
- <code>{codeA}</code>
- </pre>
+ <div className="grid grid-cols-[auto_1fr_auto]">
+ <div className="grid justify-start bg-red-500 p-2 text-lg font-bold text-white">
+ {playerInfoA.score ?? "-"}
+ </div>
+ <div className="w-full bg-blue-500">
+ <div
+ className="h-full bg-red-500"
+ style={{ width: `${scoreRatio}%` }}
+ ></div>
+ </div>
+ <div className="grid justify-end bg-blue-500 p-2 text-lg font-bold text-white">
+ {playerInfoB.score ?? "-"}
+ </div>
+ </div>
+ <div className="grid grid-cols-[2fr_1fr_2fr_1fr] p-2">
+ <div>
+ <pre>
+ <code>{playerInfoA.code}</code>
+ </pre>
+ </div>
+ <div>
+ <div>
+ {playerInfoA.submissionResult?.status}(
+ {playerInfoA.submissionResult?.nextScore})
+ </div>
+ <div>
+ <ol>
+ {playerInfoA.submissionResult?.executionResults.map(
+ (result, idx) => (
+ <li key={idx}>
+ <div>
+ <div>
+ {result.status} {result.label}
+ </div>
+ <div>{result.output}</div>
+ </div>
+ </li>
+ ),
+ )}
+ </ol>
</div>
</div>
- <div style={{ display: "flex", flex: 3, flexDirection: "column" }}>
- <div style={{ flex: 1, justifyContent: "center" }}>{scoreB}</div>
- <div style={{ flex: 3 }}>
- <pre>
- <code>{codeB}</code>
- </pre>
+ <div>
+ <pre>
+ <code>{playerInfoB.code}</code>
+ </pre>
+ </div>
+ <div>
+ <div>
+ {playerInfoB.submissionResult?.status}(
+ {playerInfoB.submissionResult?.nextScore})
+ </div>
+ <div>
+ <ol>
+ {playerInfoB.submissionResult?.executionResults.map(
+ (result, idx) => (
+ <li key={idx}>
+ <div>
+ <div>
+ {result.status} {result.label}
+ </div>
+ <div>{result.output}</div>
+ </div>
+ </li>
+ ),
+ )}
+ </ol>
</div>
</div>
</div>
+ <div className="grid justify-center p-2 bg-slate-300">{problem}</div>
</div>
);
}
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppStarting.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppStarting.tsx
index ef72cec..8282fb4 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppStarting.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppStarting.tsx
@@ -1,8 +1,10 @@
type Props = {
- timeLeft: number;
+ leftTimeSeconds: number;
};
-export default function GolfWatchAppStarting({ timeLeft }: Props) {
+export default function GolfWatchAppStarting({
+ leftTimeSeconds: timeLeft,
+}: Props) {
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<div className="text-center">
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx
index d58ec19..17ef2b9 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppWaiting.tsx
@@ -1,3 +1,9 @@
export default function GolfWatchAppWaiting() {
- return <div>Waiting...</div>;
+ return (
+ <div className="min-h-screen bg-gray-100 flex items-center justify-center">
+ <div className="text-center">
+ <h1 className="text-4xl font-bold text-black-600 mb-4">Waiting...</h1>
+ </div>
+ </div>
+ );
}