aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2024-08-18 01:46:36 +0900
committernsfisis <nsfisis@gmail.com>2024-08-18 01:46:36 +0900
commited796dddde6db433c88b04a1ad154bf88a24faa4 (patch)
treede82ed4ae74c75ba7ddf9e478cb6129177ee4c89
parent7653eb2b28911a0479b3b673c9b63fd490aedb6b (diff)
parent9bd1f89febe6311781aca3e8ee2b6a706a606e3c (diff)
downloadphperkaigi-2025-albatross-ed796dddde6db433c88b04a1ad154bf88a24faa4.tar.gz
phperkaigi-2025-albatross-ed796dddde6db433c88b04a1ad154bf88a24faa4.tar.zst
phperkaigi-2025-albatross-ed796dddde6db433c88b04a1ad154bf88a24faa4.zip
Merge branch 'feat/watch-page'
-rw-r--r--backend/game/hub.go27
-rw-r--r--frontend/app/components/Gaming/CodeBlock.tsx11
-rw-r--r--frontend/app/components/Gaming/ExecStatusIndicatorIcon.tsx (renamed from frontend/app/components/ExecStatusIndicatorIcon.tsx)2
-rw-r--r--frontend/app/components/Gaming/ScoreBar.tsx25
-rw-r--r--frontend/app/components/Gaming/SubmitResult.tsx54
-rw-r--r--frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx52
-rw-r--r--frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx129
-rw-r--r--frontend/app/root.tsx1
-rw-r--r--frontend/app/routes/_index.tsx1
9 files changed, 136 insertions, 166 deletions
diff --git a/backend/game/hub.go b/backend/game/hub.go
index 7c06e05..d385746 100644
--- a/backend/game/hub.go
+++ b/backend/game/hub.go
@@ -138,7 +138,7 @@ func (hub *gameHub) run() {
}
}
-func (hub *gameHub) sendExecResultMessage(playerID int, testcaseID nullable.Nullable[int], status string, stdout string, stderr string) {
+func (hub *gameHub) sendExecResult(playerID int, testcaseID nullable.Nullable[int], status string, stdout string, stderr string) {
hub.sendToPlayer(playerID, &playerMessageS2CExecResult{
Type: playerMessageTypeS2CExecResult,
Data: playerMessageS2CExecResultPayload{
@@ -218,7 +218,7 @@ func (hub *gameHub) processTaskResults() {
case *taskqueue.TaskResultCompileSwiftToWasm:
err := hub.processTaskResultCompileSwiftToWasm(taskResult)
if err != nil {
- hub.sendExecResultMessage(
+ hub.sendExecResult(
taskResult.TaskPayload.UserID(),
nullable.NewNullNullable[int](),
err.Status,
@@ -234,7 +234,7 @@ func (hub *gameHub) processTaskResults() {
case *taskqueue.TaskResultCompileWasmToNativeExecutable:
err := hub.processTaskResultCompileWasmToNativeExecutable(taskResult)
if err != nil {
- hub.sendExecResultMessage(
+ hub.sendExecResult(
taskResult.TaskPayload.UserID(),
nullable.NewNullNullable[int](),
err.Status,
@@ -247,12 +247,11 @@ func (hub *gameHub) processTaskResults() {
nullable.NewNullNullable[int](),
)
} else {
- hub.sendExecResultMessage(
+ hub.sendExecResult(
taskResult.TaskPayload.UserID(),
nullable.NewNullNullable[int](),
"success",
- // TODO: inherit the command stdout/stderr.
- "Successfully compiled",
+ "",
"",
)
}
@@ -270,13 +269,12 @@ func (hub *gameHub) processTaskResults() {
Stderr: "",
})
if err != nil {
- hub.sendExecResultMessage(
+ hub.sendExecResult(
taskResult.TaskPayload.UserID(),
nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)),
"internal_error",
- // TODO: inherit the command stdout/stderr?
"",
- "internal error",
+ "",
)
hub.sendSubmitResult(
taskResult.TaskPayload.UserID(),
@@ -286,20 +284,19 @@ func (hub *gameHub) processTaskResults() {
continue
}
if err1 != nil {
- hub.sendExecResultMessage(
+ hub.sendExecResult(
taskResult.TaskPayload.UserID(),
nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)),
aggregatedStatus,
- err1.Stdout,
- err1.Stderr,
+ "",
+ "",
)
} else {
- hub.sendExecResultMessage(
+ hub.sendExecResult(
taskResult.TaskPayload.UserID(),
nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)),
"success",
- // TODO: inherit the command stdout/stderr?
- "Testcase passed",
+ "",
"",
)
}
diff --git a/frontend/app/components/Gaming/CodeBlock.tsx b/frontend/app/components/Gaming/CodeBlock.tsx
new file mode 100644
index 0000000..20cd425
--- /dev/null
+++ b/frontend/app/components/Gaming/CodeBlock.tsx
@@ -0,0 +1,11 @@
+type Props = {
+ code: string;
+};
+
+export default function CodeBlock({ code }: Props) {
+ return (
+ <pre className="bg-white resize-none h-full w-full rounded-lg border border-gray-300 p-2">
+ <code>{code}</code>
+ </pre>
+ );
+}
diff --git a/frontend/app/components/ExecStatusIndicatorIcon.tsx b/frontend/app/components/Gaming/ExecStatusIndicatorIcon.tsx
index 5277bfa..8daf48c 100644
--- a/frontend/app/components/ExecStatusIndicatorIcon.tsx
+++ b/frontend/app/components/Gaming/ExecStatusIndicatorIcon.tsx
@@ -6,7 +6,7 @@ import {
faRotate,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import type { ExecResultStatus } from "../models/ExecResult";
+import type { ExecResultStatus } from "../../models/ExecResult";
type Props = {
status: ExecResultStatus;
diff --git a/frontend/app/components/Gaming/ScoreBar.tsx b/frontend/app/components/Gaming/ScoreBar.tsx
new file mode 100644
index 0000000..4eac3ad
--- /dev/null
+++ b/frontend/app/components/Gaming/ScoreBar.tsx
@@ -0,0 +1,25 @@
+type Props = {
+ scoreA: number | null;
+ scoreB: number | null;
+ bgA: string;
+ bgB: string;
+};
+
+export default function ScoreBar({ scoreA, scoreB, bgA, bgB }: Props) {
+ let scoreRatio;
+ if (scoreA === null && scoreB === null) {
+ scoreRatio = 50;
+ } else if (scoreA === null) {
+ scoreRatio = 0;
+ } else if (scoreB === null) {
+ scoreRatio = 100;
+ } else {
+ scoreRatio = (scoreB / (scoreA + scoreB)) * 100;
+ }
+
+ return (
+ <div className={`w-full ${bgB}`}>
+ <div className={`h-6 ${bgA}`} style={{ width: `${scoreRatio}%` }}></div>
+ </div>
+ );
+}
diff --git a/frontend/app/components/Gaming/SubmitResult.tsx b/frontend/app/components/Gaming/SubmitResult.tsx
new file mode 100644
index 0000000..5b08ef1
--- /dev/null
+++ b/frontend/app/components/Gaming/SubmitResult.tsx
@@ -0,0 +1,54 @@
+import { faArrowDown } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import React from "react";
+import type { SubmitResult } from "../../models/SubmitResult";
+import BorderedContainer from "../BorderedContainer";
+import SubmitStatusLabel from "../SubmitStatusLabel";
+import ExecStatusIndicatorIcon from "./ExecStatusIndicatorIcon";
+
+type Props = {
+ result: SubmitResult;
+ submitButton?: React.ReactNode;
+};
+
+export default function SubmitResult({ result, submitButton }: Props) {
+ return (
+ <div className="flex flex-col gap-2">
+ <div className="flex">
+ {submitButton}
+ <div className="grow font-bold text-xl text-center">
+ <SubmitStatusLabel status={result.status} />
+ </div>
+ </div>
+ <ul className="flex flex-col gap-2">
+ {result.execResults.map((r, idx) => (
+ <li key={r.testcase_id ?? -1} className="flex gap-2">
+ <div className="flex flex-col gap-2 p-2">
+ <ExecStatusIndicatorIcon status={r.status} />
+ {idx !== result.execResults.length - 1 && (
+ <div>
+ <FontAwesomeIcon
+ icon={faArrowDown}
+ fixedWidth
+ className="text-gray-500"
+ />
+ </div>
+ )}
+ </div>
+ <div className="grow p-2">
+ <BorderedContainer>
+ <div className="font-semibold">{r.label}</div>
+ <div>
+ <code>
+ {r.stdout}
+ {r.stderr}
+ </code>
+ </div>
+ </BorderedContainer>
+ </div>
+ </li>
+ ))}
+ </ul>
+ </div>
+ );
+}
diff --git a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
index a6c4550..e9139ba 100644
--- a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
+++ b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
@@ -1,12 +1,9 @@
-import { faArrowDown } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "@remix-run/react";
import React, { useRef } from "react";
import SubmitButton from "../../components/SubmitButton";
import type { PlayerInfo } from "../../models/PlayerInfo";
import BorderedContainer from "../BorderedContainer";
-import ExecStatusIndicatorIcon from "../ExecStatusIndicatorIcon";
-import SubmitStatusLabel from "../SubmitStatusLabel";
+import SubmitResult from "../Gaming/SubmitResult";
type Props = {
gameDisplayName: string;
@@ -92,44 +89,15 @@ export default function GolfPlayAppGaming({
className="resize-none h-full w-full rounded-lg border border-gray-300 p-2 focus:outline-none focus:ring-2 focus:ring-gray-400 transition duration-300"
></textarea>
</div>
- <div className="p-4 flex flex-col gap-4">
- <div className="flex">
- <SubmitButton onClick={handleSubmitButtonClick}>提出</SubmitButton>
- <div className="grow font-bold text-xl text-center m-1">
- <SubmitStatusLabel status={playerInfo.submitResult.status} />
- </div>
- </div>
- <ul className="flex flex-col gap-2">
- {playerInfo.submitResult.execResults.map((r, idx) => (
- <li key={r.testcase_id ?? -1} className="flex gap-2">
- <div className="flex flex-col gap-2 p-2">
- <div className="w-6">
- <ExecStatusIndicatorIcon status={r.status} />
- </div>
- {idx !== playerInfo.submitResult.execResults.length - 1 && (
- <div>
- <FontAwesomeIcon
- icon={faArrowDown}
- fixedWidth
- className="text-gray-500"
- />
- </div>
- )}
- </div>
- <div className="grow p-2 overflow-x-scroll">
- <BorderedContainer>
- <div className="font-semibold">{r.label}</div>
- <div>
- <code>
- {r.stdout}
- {r.stderr}
- </code>
- </div>
- </BorderedContainer>
- </div>
- </li>
- ))}
- </ul>
+ <div className="p-4">
+ <SubmitResult
+ result={playerInfo.submitResult}
+ submitButton={
+ <SubmitButton onClick={handleSubmitButtonClick}>
+ 提出
+ </SubmitButton>
+ }
+ />
</div>
</div>
</div>
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
index 63c232b..abdc855 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
@@ -1,9 +1,8 @@
-import { faArrowDown } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { PlayerInfo } from "../../models/PlayerInfo";
import BorderedContainer from "../BorderedContainer";
-import ExecStatusIndicatorIcon from "../ExecStatusIndicatorIcon";
-import SubmitStatusLabel from "../SubmitStatusLabel";
+import CodeBlock from "../Gaming/CodeBlock";
+import ScoreBar from "../Gaming/ScoreBar";
+import SubmitResult from "../Gaming/SubmitResult";
type Props = {
gameDisplayName: string;
@@ -21,6 +20,8 @@ export default function GolfWatchAppGaming({
leftTimeSeconds,
playerInfoA,
playerInfoB,
+ problemTitle,
+ problemDescription,
}: Props) {
const leftTime = (() => {
const k = gameDurationSeconds + leftTimeSeconds;
@@ -29,20 +30,6 @@ export default function GolfWatchAppGaming({
return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
})();
- const scoreRatio = (() => {
- const scoreA = playerInfoA.score;
- const scoreB = playerInfoB.score;
- if (scoreA === null && scoreB === null) {
- return 50;
- } else if (scoreA === null) {
- return 0;
- } else if (scoreB === null) {
- return 100;
- } else {
- return (scoreB / (scoreA + scoreB)) * 100;
- }
- })();
-
return (
<div className="min-h-screen bg-gray-100 flex flex-col">
<div className="text-white bg-iosdc-japan grid grid-cols-3 px-4 py-2">
@@ -91,99 +78,27 @@ export default function GolfWatchAppGaming({
</div>
</div>
</div>
- <div className="w-full bg-purple-400">
- <div
- className="h-6 bg-orange-400"
- style={{ width: `${scoreRatio}%` }}
- ></div>
- </div>
- <div className="grow grid grid-cols-10 p-4 gap-4">
- <div className="col-span-3">
- <pre className="bg-white resize-none h-full w-full rounded-lg border border-gray-300 p-2">
- <code>{playerInfoA.code}</code>
- </pre>
- </div>
- <div className="col-span-2 flex flex-col gap-4">
- <div className="flex">
- <div className="grow font-bold text-xl text-center">
- <SubmitStatusLabel status={playerInfoA.submitResult.status} />
- </div>
+ <ScoreBar
+ scoreA={playerInfoA.score}
+ scoreB={playerInfoB.score}
+ bgA="bg-orange-400"
+ bgB="bg-purple-400"
+ />
+ <div className="grow grid grid-cols-3 p-4 gap-4">
+ <CodeBlock code={playerInfoA.code ?? ""} />
+ <div className="flex flex-col gap-4 justify-between">
+ <div className="grid grid-cols-2 gap-4">
+ <SubmitResult result={playerInfoA.submitResult} />
+ <SubmitResult result={playerInfoB.submitResult} />
</div>
- <ul className="flex flex-col gap-2">
- {playerInfoA.submitResult.execResults.map((r, idx) => (
- <li key={r.testcase_id ?? -1} className="flex gap-2">
- <div className="flex flex-col gap-2 p-2">
- <div className="w-6">
- <ExecStatusIndicatorIcon status={r.status} />
- </div>
- {idx !== playerInfoA.submitResult.execResults.length - 1 && (
- <div>
- <FontAwesomeIcon
- icon={faArrowDown}
- fixedWidth
- className="text-gray-500"
- />
- </div>
- )}
- </div>
- <div className="grow p-2 overflow-x-scroll">
- <BorderedContainer>
- <div className="font-semibold">{r.label}</div>
- <div>
- <code>
- {r.stdout}
- {r.stderr}
- </code>
- </div>
- </BorderedContainer>
- </div>
- </li>
- ))}
- </ul>
- </div>
- <div className="col-span-2 flex flex-col gap-4">
- <div className="flex">
- <div className="grow font-bold text-xl text-center">
- <SubmitStatusLabel status={playerInfoB.submitResult.status} />
+ <div>
+ <div className="mb-2 text-center text-xl font-bold">
+ {problemTitle}
</div>
+ <BorderedContainer>{problemDescription}</BorderedContainer>
</div>
- <ul className="flex flex-col gap-2">
- {playerInfoB.submitResult.execResults.map((r, idx) => (
- <li key={r.testcase_id ?? -1} className="flex gap-2">
- <div className="flex flex-col gap-2 p-2">
- <div className="w-6">
- <ExecStatusIndicatorIcon status={r.status} />
- </div>
- {idx !== playerInfoB.submitResult.execResults.length - 1 && (
- <div>
- <FontAwesomeIcon
- icon={faArrowDown}
- fixedWidth
- className="text-gray-500"
- />
- </div>
- )}
- </div>
- <div className="grow p-2 overflow-x-scroll">
- <BorderedContainer>
- <div className="font-semibold">{r.label}</div>
- <div>
- <code>
- {r.stdout}
- {r.stderr}
- </code>
- </div>
- </BorderedContainer>
- </div>
- </li>
- ))}
- </ul>
- </div>
- <div className="col-span-3">
- <pre className="bg-white resize-none h-full w-full rounded-lg border border-gray-300 p-2">
- <code>{playerInfoB.code}</code>
- </pre>
</div>
+ <CodeBlock code={playerInfoB.code ?? ""} />
</div>
</div>
);
diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx
index 7924ce7..c05020c 100644
--- a/frontend/app/root.tsx
+++ b/frontend/app/root.tsx
@@ -1,4 +1,5 @@
import { config } from "@fortawesome/fontawesome-svg-core";
+import "@fortawesome/fontawesome-svg-core/styles.css";
import type { LinksFunction } from "@remix-run/node";
import {
Links,
diff --git a/frontend/app/routes/_index.tsx b/frontend/app/routes/_index.tsx
index 24fff9f..baa3909 100644
--- a/frontend/app/routes/_index.tsx
+++ b/frontend/app/routes/_index.tsx
@@ -1,5 +1,4 @@
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
-import "@fortawesome/fontawesome-svg-core/styles.css";
import { ensureUserNotLoggedIn } from "../.server/auth";
import BorderedContainer from "../components/BorderedContainer";
import NavigateLink from "../components/NavigateLink";