From 5964fa404909550a2dd42a75633fef223fdb05fb Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Mon, 12 Aug 2024 01:23:54 +0900
Subject: feat: do not show preliminary score

---
 frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

(limited to 'frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx')

diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
index 65cd35e..173e8e3 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
@@ -24,7 +24,6 @@ type SubmissionResult = {
 		| "compile_error"
 		| "runtime_error"
 		| "internal_error";
-	preliminaryScore: number;
 	verificationResults: VerificationResult[];
 };
 
@@ -120,8 +119,7 @@ export default function GolfWatchAppGaming({
 					<div>
 						{submissionResultStatusToLabel(
 							playerInfoA.submissionResult?.status ?? null,
-						)}{" "}
-						({playerInfoA.submissionResult?.preliminaryScore})
+						)}
 					</div>
 					<div>
 						<ol>
@@ -153,8 +151,7 @@ export default function GolfWatchAppGaming({
 					<div>
 						{submissionResultStatusToLabel(
 							playerInfoB.submissionResult?.status ?? null,
-						)}{" "}
-						({playerInfoB.submissionResult?.preliminaryScore ?? "-"})
+						)}
 					</div>
 					<div>
 						<ol>
-- 
cgit v1.2.3-70-g09d2


From b97b245861b93a5ab5f8bde095d9920fabd0cbbd Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Mon, 12 Aug 2024 01:34:20 +0900
Subject: refactor(frontend): move some types to app/models

---
 .../app/components/ExecStatusIndicatorIcon.tsx     |  3 +-
 frontend/app/components/GolfWatchApp.client.tsx    |  5 +-
 .../GolfWatchApps/GolfWatchAppGaming.tsx           | 61 +---------------------
 frontend/app/models/PlayerInfo.ts                  |  9 ++++
 frontend/app/models/SubmissionResult.ts            | 38 ++++++++++++++
 frontend/app/models/VerificationResult.ts          | 17 ++++++
 6 files changed, 70 insertions(+), 63 deletions(-)
 create mode 100644 frontend/app/models/PlayerInfo.ts
 create mode 100644 frontend/app/models/SubmissionResult.ts
 create mode 100644 frontend/app/models/VerificationResult.ts

(limited to 'frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx')

diff --git a/frontend/app/components/ExecStatusIndicatorIcon.tsx b/frontend/app/components/ExecStatusIndicatorIcon.tsx
index a76e957..c5b37f8 100644
--- a/frontend/app/components/ExecStatusIndicatorIcon.tsx
+++ b/frontend/app/components/ExecStatusIndicatorIcon.tsx
@@ -5,9 +5,10 @@ import {
 	faRotate,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { VerificationResultStatus } from "../models/VerificationResult";
 
 type Props = {
-	status: string;
+	status: VerificationResultStatus;
 };
 
 export default function ExecStatusIndicatorIcon({ status }: Props) {
diff --git a/frontend/app/components/GolfWatchApp.client.tsx b/frontend/app/components/GolfWatchApp.client.tsx
index 1da2066..3f97d2a 100644
--- a/frontend/app/components/GolfWatchApp.client.tsx
+++ b/frontend/app/components/GolfWatchApp.client.tsx
@@ -1,11 +1,10 @@
 import { useEffect, useState } from "react";
 import type { components } from "../.server/api/schema";
 import useWebSocket, { ReadyState } from "../hooks/useWebSocket";
+import type { PlayerInfo } from "../models/PlayerInfo";
 import GolfWatchAppConnecting from "./GolfWatchApps/GolfWatchAppConnecting";
 import GolfWatchAppFinished from "./GolfWatchApps/GolfWatchAppFinished";
-import GolfWatchAppGaming, {
-	PlayerInfo,
-} from "./GolfWatchApps/GolfWatchAppGaming";
+import GolfWatchAppGaming from "./GolfWatchApps/GolfWatchAppGaming";
 import GolfWatchAppStarting from "./GolfWatchApps/GolfWatchAppStarting";
 import GolfWatchAppWaiting from "./GolfWatchApps/GolfWatchAppWaiting";
 
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
index 173e8e3..5ca778b 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
@@ -1,3 +1,5 @@
+import { PlayerInfo } from "../../models/PlayerInfo";
+import { submissionResultStatusToLabel } from "../../models/SubmissionResult";
 import ExecStatusIndicatorIcon from "../ExecStatusIndicatorIcon";
 
 type Props = {
@@ -7,65 +9,6 @@ type Props = {
 	leftTimeSeconds: number;
 };
 
-export type PlayerInfo = {
-	displayName: string | null;
-	iconPath: string | null;
-	score: number | null;
-	code: string | null;
-	submissionResult?: SubmissionResult;
-};
-
-type SubmissionResult = {
-	status:
-		| "running"
-		| "success"
-		| "wrong_answer"
-		| "timeout"
-		| "compile_error"
-		| "runtime_error"
-		| "internal_error";
-	verificationResults: VerificationResult[];
-};
-
-type VerificationResult = {
-	testcase_id: number | null;
-	status:
-		| "running"
-		| "success"
-		| "wrong_answer"
-		| "timeout"
-		| "compile_error"
-		| "runtime_error"
-		| "internal_error"
-		| "canceled";
-	label: string;
-	stdout: string;
-	stderr: string;
-};
-
-function submissionResultStatusToLabel(
-	status: SubmissionResult["status"] | null,
-) {
-	switch (status) {
-		case null:
-			return "-";
-		case "running":
-			return "Running...";
-		case "success":
-			return "Accepted";
-		case "wrong_answer":
-			return "Wrong Answer";
-		case "timeout":
-			return "Time Limit Exceeded";
-		case "compile_error":
-			return "Compile Error";
-		case "runtime_error":
-			return "Runtime Error";
-		case "internal_error":
-			return "Internal Error";
-	}
-}
-
 export default function GolfWatchAppGaming({
 	problem,
 	playerInfoA,
diff --git a/frontend/app/models/PlayerInfo.ts b/frontend/app/models/PlayerInfo.ts
new file mode 100644
index 0000000..7206a8b
--- /dev/null
+++ b/frontend/app/models/PlayerInfo.ts
@@ -0,0 +1,9 @@
+import type { SubmissionResult } from "./SubmissionResult";
+
+export type PlayerInfo = {
+	displayName: string | null;
+	iconPath: string | null;
+	score: number | null;
+	code: string | null;
+	submissionResult?: SubmissionResult;
+};
diff --git a/frontend/app/models/SubmissionResult.ts b/frontend/app/models/SubmissionResult.ts
new file mode 100644
index 0000000..7311494
--- /dev/null
+++ b/frontend/app/models/SubmissionResult.ts
@@ -0,0 +1,38 @@
+import type { VerificationResult } from "./VerificationResult";
+
+export type SubmissionResultStatus =
+	| "running"
+	| "success"
+	| "wrong_answer"
+	| "timeout"
+	| "compile_error"
+	| "runtime_error"
+	| "internal_error";
+
+export type SubmissionResult = {
+	status: SubmissionResultStatus;
+	verificationResults: VerificationResult[];
+};
+
+export function submissionResultStatusToLabel(
+	status: SubmissionResultStatus | null,
+) {
+	switch (status) {
+		case null:
+			return "-";
+		case "running":
+			return "Running...";
+		case "success":
+			return "Accepted";
+		case "wrong_answer":
+			return "Wrong Answer";
+		case "timeout":
+			return "Time Limit Exceeded";
+		case "compile_error":
+			return "Compile Error";
+		case "runtime_error":
+			return "Runtime Error";
+		case "internal_error":
+			return "Internal Error";
+	}
+}
diff --git a/frontend/app/models/VerificationResult.ts b/frontend/app/models/VerificationResult.ts
new file mode 100644
index 0000000..09a7ef4
--- /dev/null
+++ b/frontend/app/models/VerificationResult.ts
@@ -0,0 +1,17 @@
+export type VerificationResultStatus =
+	| "running"
+	| "success"
+	| "wrong_answer"
+	| "timeout"
+	| "compile_error"
+	| "runtime_error"
+	| "internal_error"
+	| "canceled";
+
+export type VerificationResult = {
+	testcase_id: number | null;
+	status: VerificationResultStatus;
+	label: string;
+	stdout: string;
+	stderr: string;
+};
-- 
cgit v1.2.3-70-g09d2


From ab6abc3a7619be89dda1bc3ab0b3f19ff69b575f Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Mon, 12 Aug 2024 01:54:36 +0900
Subject: refactor(frontend): rename SubmissionResult and VerificationResult
 for consistant naming

---
 .../app/components/ExecStatusIndicatorIcon.tsx     |  4 +-
 frontend/app/components/GolfWatchApp.client.tsx    | 51 +++++++++---------
 .../GolfWatchApps/GolfWatchAppGaming.tsx           | 62 ++++++++++------------
 frontend/app/models/ExecResult.ts                  | 17 ++++++
 frontend/app/models/PlayerInfo.ts                  |  4 +-
 frontend/app/models/SubmissionResult.ts            | 38 -------------
 frontend/app/models/SubmitResult.ts                | 36 +++++++++++++
 frontend/app/models/VerificationResult.ts          | 17 ------
 8 files changed, 111 insertions(+), 118 deletions(-)
 create mode 100644 frontend/app/models/ExecResult.ts
 delete mode 100644 frontend/app/models/SubmissionResult.ts
 create mode 100644 frontend/app/models/SubmitResult.ts
 delete mode 100644 frontend/app/models/VerificationResult.ts

(limited to 'frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx')

diff --git a/frontend/app/components/ExecStatusIndicatorIcon.tsx b/frontend/app/components/ExecStatusIndicatorIcon.tsx
index c5b37f8..b5c53ec 100644
--- a/frontend/app/components/ExecStatusIndicatorIcon.tsx
+++ b/frontend/app/components/ExecStatusIndicatorIcon.tsx
@@ -5,10 +5,10 @@ import {
 	faRotate,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { VerificationResultStatus } from "../models/VerificationResult";
+import { ExecResultStatus } from "../models/ExecResult";
 
 type Props = {
-	status: VerificationResultStatus;
+	status: ExecResultStatus;
 };
 
 export default function ExecStatusIndicatorIcon({ status }: Props) {
diff --git a/frontend/app/components/GolfWatchApp.client.tsx b/frontend/app/components/GolfWatchApp.client.tsx
index 3f97d2a..7041572 100644
--- a/frontend/app/components/GolfWatchApp.client.tsx
+++ b/frontend/app/components/GolfWatchApp.client.tsx
@@ -78,14 +78,14 @@ export default function GolfWatchApp({
 		iconPath: playerA?.icon_path ?? null,
 		score: null,
 		code: "",
-		submissionResult: undefined,
+		submitResult: undefined,
 	});
 	const [playerInfoB, setPlayerInfoB] = useState<PlayerInfo>({
 		displayName: playerB?.display_name ?? null,
 		iconPath: playerB?.icon_path ?? null,
 		score: null,
 		code: "",
-		submissionResult: undefined,
+		submitResult: undefined,
 	});
 
 	if (readyState === ReadyState.UNINSTANTIATED) {
@@ -125,12 +125,12 @@ export default function GolfWatchApp({
 						player_id === playerA?.user_id ? setPlayerInfoA : setPlayerInfoB;
 					setter((prev) => ({
 						...prev,
-						submissionResult: {
+						submitResult: {
 							status: "running",
-							verificationResults: game.verification_steps.map((v) => ({
-								testcase_id: v.testcase_id,
+							execResults: game.verification_steps.map((r) => ({
+								testcase_id: r.testcase_id,
 								status: "running",
-								label: v.label,
+								label: r.label,
 								stdout: "",
 								stderr: "",
 							})),
@@ -143,21 +143,20 @@ export default function GolfWatchApp({
 						player_id === playerA?.user_id ? setPlayerInfoA : setPlayerInfoB;
 					setter((prev) => {
 						const ret = { ...prev };
-						if (ret.submissionResult === undefined) {
+						if (ret.submitResult === undefined) {
 							return ret;
 						}
-						ret.submissionResult = {
-							...ret.submissionResult,
-							verificationResults: ret.submissionResult.verificationResults.map(
-								(v) =>
-									v.testcase_id === testcase_id && v.status === "running"
-										? {
-												...v,
-												status,
-												stdout,
-												stderr,
-											}
-										: v,
+						ret.submitResult = {
+							...ret.submitResult,
+							execResults: ret.submitResult.execResults.map((r) =>
+								r.testcase_id === testcase_id && r.status === "running"
+									? {
+											...r,
+											status,
+											stdout,
+											stderr,
+										}
+									: r,
 							),
 						};
 						return ret;
@@ -168,11 +167,11 @@ export default function GolfWatchApp({
 						player_id === playerA?.user_id ? setPlayerInfoA : setPlayerInfoB;
 					setter((prev) => {
 						const ret = { ...prev };
-						if (ret.submissionResult === undefined) {
+						if (ret.submitResult === undefined) {
 							return ret;
 						}
-						ret.submissionResult = {
-							...ret.submissionResult,
+						ret.submitResult = {
+							...ret.submitResult,
 							status,
 						};
 						if (status === "success") {
@@ -182,10 +181,10 @@ export default function GolfWatchApp({
 								}
 							}
 						} else {
-							ret.submissionResult.verificationResults =
-								ret.submissionResult.verificationResults.map((v) =>
-									v.status === "running" ? { ...v, status: "canceled" } : v,
-								);
+							ret.submitResult.execResults = ret.submitResult.execResults.map(
+								(r) =>
+									r.status === "running" ? { ...r, status: "canceled" } : r,
+							);
 						}
 						return ret;
 					});
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
index 5ca778b..bdd1ac3 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
@@ -1,5 +1,5 @@
 import { PlayerInfo } from "../../models/PlayerInfo";
-import { submissionResultStatusToLabel } from "../../models/SubmissionResult";
+import { submitResultStatusToLabel } from "../../models/SubmitResult";
 import ExecStatusIndicatorIcon from "../ExecStatusIndicatorIcon";
 
 type Props = {
@@ -60,28 +60,26 @@ export default function GolfWatchAppGaming({
 				</div>
 				<div>
 					<div>
-						{submissionResultStatusToLabel(
-							playerInfoA.submissionResult?.status ?? null,
+						{submitResultStatusToLabel(
+							playerInfoA.submitResult?.status ?? null,
 						)}
 					</div>
 					<div>
 						<ol>
-							{playerInfoA.submissionResult?.verificationResults.map(
-								(result) => (
-									<li key={result.testcase_id ?? -1}>
+							{playerInfoA.submitResult?.execResults.map((result) => (
+								<li key={result.testcase_id ?? -1}>
+									<div>
 										<div>
-											<div>
-												<ExecStatusIndicatorIcon status={result.status} />{" "}
-												{result.label}
-											</div>
-											<div>
-												{result.stdout}
-												{result.stderr}
-											</div>
+											<ExecStatusIndicatorIcon status={result.status} />{" "}
+											{result.label}
 										</div>
-									</li>
-								),
-							)}
+										<div>
+											{result.stdout}
+											{result.stderr}
+										</div>
+									</div>
+								</li>
+							))}
 						</ol>
 					</div>
 				</div>
@@ -92,28 +90,26 @@ export default function GolfWatchAppGaming({
 				</div>
 				<div>
 					<div>
-						{submissionResultStatusToLabel(
-							playerInfoB.submissionResult?.status ?? null,
+						{submitResultStatusToLabel(
+							playerInfoB.submitResult?.status ?? null,
 						)}
 					</div>
 					<div>
 						<ol>
-							{playerInfoB.submissionResult?.verificationResults.map(
-								(result, idx) => (
-									<li key={idx}>
+							{playerInfoB.submitResult?.execResults.map((result, idx) => (
+								<li key={idx}>
+									<div>
+										<div>
+											<ExecStatusIndicatorIcon status={result.status} />{" "}
+											{result.label}
+										</div>
 										<div>
-											<div>
-												<ExecStatusIndicatorIcon status={result.status} />{" "}
-												{result.label}
-											</div>
-											<div>
-												{result.stdout}
-												{result.stderr}
-											</div>
+											{result.stdout}
+											{result.stderr}
 										</div>
-									</li>
-								),
-							)}
+									</div>
+								</li>
+							))}
 						</ol>
 					</div>
 				</div>
diff --git a/frontend/app/models/ExecResult.ts b/frontend/app/models/ExecResult.ts
new file mode 100644
index 0000000..59b8ee8
--- /dev/null
+++ b/frontend/app/models/ExecResult.ts
@@ -0,0 +1,17 @@
+export type ExecResultStatus =
+	| "running"
+	| "success"
+	| "wrong_answer"
+	| "timeout"
+	| "compile_error"
+	| "runtime_error"
+	| "internal_error"
+	| "canceled";
+
+export type ExecResult = {
+	testcase_id: number | null;
+	status: ExecResultStatus;
+	label: string;
+	stdout: string;
+	stderr: string;
+};
diff --git a/frontend/app/models/PlayerInfo.ts b/frontend/app/models/PlayerInfo.ts
index 7206a8b..30cccea 100644
--- a/frontend/app/models/PlayerInfo.ts
+++ b/frontend/app/models/PlayerInfo.ts
@@ -1,9 +1,9 @@
-import type { SubmissionResult } from "./SubmissionResult";
+import type { SubmitResult } from "./SubmitResult";
 
 export type PlayerInfo = {
 	displayName: string | null;
 	iconPath: string | null;
 	score: number | null;
 	code: string | null;
-	submissionResult?: SubmissionResult;
+	submitResult?: SubmitResult;
 };
diff --git a/frontend/app/models/SubmissionResult.ts b/frontend/app/models/SubmissionResult.ts
deleted file mode 100644
index 7311494..0000000
--- a/frontend/app/models/SubmissionResult.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import type { VerificationResult } from "./VerificationResult";
-
-export type SubmissionResultStatus =
-	| "running"
-	| "success"
-	| "wrong_answer"
-	| "timeout"
-	| "compile_error"
-	| "runtime_error"
-	| "internal_error";
-
-export type SubmissionResult = {
-	status: SubmissionResultStatus;
-	verificationResults: VerificationResult[];
-};
-
-export function submissionResultStatusToLabel(
-	status: SubmissionResultStatus | null,
-) {
-	switch (status) {
-		case null:
-			return "-";
-		case "running":
-			return "Running...";
-		case "success":
-			return "Accepted";
-		case "wrong_answer":
-			return "Wrong Answer";
-		case "timeout":
-			return "Time Limit Exceeded";
-		case "compile_error":
-			return "Compile Error";
-		case "runtime_error":
-			return "Runtime Error";
-		case "internal_error":
-			return "Internal Error";
-	}
-}
diff --git a/frontend/app/models/SubmitResult.ts b/frontend/app/models/SubmitResult.ts
new file mode 100644
index 0000000..403a0db
--- /dev/null
+++ b/frontend/app/models/SubmitResult.ts
@@ -0,0 +1,36 @@
+import type { ExecResult } from "./ExecResult";
+
+export type SubmitResultStatus =
+	| "running"
+	| "success"
+	| "wrong_answer"
+	| "timeout"
+	| "compile_error"
+	| "runtime_error"
+	| "internal_error";
+
+export type SubmitResult = {
+	status: SubmitResultStatus;
+	execResults: ExecResult[];
+};
+
+export function submitResultStatusToLabel(status: SubmitResultStatus | null) {
+	switch (status) {
+		case null:
+			return "-";
+		case "running":
+			return "Running...";
+		case "success":
+			return "Accepted";
+		case "wrong_answer":
+			return "Wrong Answer";
+		case "timeout":
+			return "Time Limit Exceeded";
+		case "compile_error":
+			return "Compile Error";
+		case "runtime_error":
+			return "Runtime Error";
+		case "internal_error":
+			return "Internal Error";
+	}
+}
diff --git a/frontend/app/models/VerificationResult.ts b/frontend/app/models/VerificationResult.ts
deleted file mode 100644
index 09a7ef4..0000000
--- a/frontend/app/models/VerificationResult.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export type VerificationResultStatus =
-	| "running"
-	| "success"
-	| "wrong_answer"
-	| "timeout"
-	| "compile_error"
-	| "runtime_error"
-	| "internal_error"
-	| "canceled";
-
-export type VerificationResult = {
-	testcase_id: number | null;
-	status: VerificationResultStatus;
-	label: string;
-	stdout: string;
-	stderr: string;
-};
-- 
cgit v1.2.3-70-g09d2


From 9f022ce7e59213284896a07a53bfef232a6e62d3 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Mon, 12 Aug 2024 02:03:17 +0900
Subject: refactor(frontend): create SubmitStatusLabel component

---
 .../app/components/ExecStatusIndicatorIcon.tsx     |  2 +-
 .../GolfWatchApps/GolfWatchAppGaming.tsx           | 14 ++++++++-----
 frontend/app/components/SubmitStatusLabel.tsx      | 24 ++++++++++++++++++++++
 frontend/app/models/SubmitResult.ts                | 21 -------------------
 4 files changed, 34 insertions(+), 27 deletions(-)
 create mode 100644 frontend/app/components/SubmitStatusLabel.tsx

(limited to 'frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx')

diff --git a/frontend/app/components/ExecStatusIndicatorIcon.tsx b/frontend/app/components/ExecStatusIndicatorIcon.tsx
index b5c53ec..13343b8 100644
--- a/frontend/app/components/ExecStatusIndicatorIcon.tsx
+++ b/frontend/app/components/ExecStatusIndicatorIcon.tsx
@@ -5,7 +5,7 @@ import {
 	faRotate,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { ExecResultStatus } from "../models/ExecResult";
+import type { ExecResultStatus } from "../models/ExecResult";
 
 type Props = {
 	status: ExecResultStatus;
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
index bdd1ac3..b436842 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
@@ -1,6 +1,6 @@
 import { PlayerInfo } from "../../models/PlayerInfo";
-import { submitResultStatusToLabel } from "../../models/SubmitResult";
 import ExecStatusIndicatorIcon from "../ExecStatusIndicatorIcon";
+import SubmitStatusLabel from "../SubmitStatusLabel";
 
 type Props = {
 	problem: string;
@@ -60,8 +60,10 @@ export default function GolfWatchAppGaming({
 				</div>
 				<div>
 					<div>
-						{submitResultStatusToLabel(
-							playerInfoA.submitResult?.status ?? null,
+						{playerInfoA.submitResult?.status ? (
+							<SubmitStatusLabel status={playerInfoA.submitResult?.status} />
+						) : (
+							<span>-</span>
 						)}
 					</div>
 					<div>
@@ -90,8 +92,10 @@ export default function GolfWatchAppGaming({
 				</div>
 				<div>
 					<div>
-						{submitResultStatusToLabel(
-							playerInfoB.submitResult?.status ?? null,
+						{playerInfoB.submitResult?.status ? (
+							<SubmitStatusLabel status={playerInfoB.submitResult?.status} />
+						) : (
+							<span>-</span>
 						)}
 					</div>
 					<div>
diff --git a/frontend/app/components/SubmitStatusLabel.tsx b/frontend/app/components/SubmitStatusLabel.tsx
new file mode 100644
index 0000000..f6e19bb
--- /dev/null
+++ b/frontend/app/components/SubmitStatusLabel.tsx
@@ -0,0 +1,24 @@
+import type { SubmitResultStatus } from "../models/SubmitResult";
+
+type Props = {
+	status: SubmitResultStatus;
+};
+
+export default function SubmitStatusLabel({ status }: Props) {
+	switch (status) {
+		case "running":
+			return <span>Running...</span>;
+		case "success":
+			return <span>Accepted</span>;
+		case "wrong_answer":
+			return <span>Wrong Answer</span>;
+		case "timeout":
+			return <span>Time Limit Exceeded</span>;
+		case "compile_error":
+			return <span>Compile Error</span>;
+		case "runtime_error":
+			return <span>Runtime Error</span>;
+		case "internal_error":
+			return <span>Internal Error</span>;
+	}
+}
diff --git a/frontend/app/models/SubmitResult.ts b/frontend/app/models/SubmitResult.ts
index 403a0db..0cb7f9b 100644
--- a/frontend/app/models/SubmitResult.ts
+++ b/frontend/app/models/SubmitResult.ts
@@ -13,24 +13,3 @@ export type SubmitResult = {
 	status: SubmitResultStatus;
 	execResults: ExecResult[];
 };
-
-export function submitResultStatusToLabel(status: SubmitResultStatus | null) {
-	switch (status) {
-		case null:
-			return "-";
-		case "running":
-			return "Running...";
-		case "success":
-			return "Accepted";
-		case "wrong_answer":
-			return "Wrong Answer";
-		case "timeout":
-			return "Time Limit Exceeded";
-		case "compile_error":
-			return "Compile Error";
-		case "runtime_error":
-			return "Runtime Error";
-		case "internal_error":
-			return "Internal Error";
-	}
-}
-- 
cgit v1.2.3-70-g09d2


From 699c5ce665bae6bcc406a0f7de994bb218a9977e Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Mon, 12 Aug 2024 02:08:55 +0900
Subject: feat(frontend): show submission result at first rendering

---
 .../app/components/ExecStatusIndicatorIcon.tsx     |  5 ++++
 frontend/app/components/GolfWatchApp.client.tsx    | 34 ++++++++++++++--------
 .../GolfWatchApps/GolfWatchAppGaming.tsx           | 12 ++------
 frontend/app/components/SubmitStatusLabel.tsx      |  2 ++
 frontend/app/models/ExecResult.ts                  |  1 +
 frontend/app/models/PlayerInfo.ts                  |  2 +-
 frontend/app/models/SubmitResult.ts                |  1 +
 7 files changed, 34 insertions(+), 23 deletions(-)

(limited to 'frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx')

diff --git a/frontend/app/components/ExecStatusIndicatorIcon.tsx b/frontend/app/components/ExecStatusIndicatorIcon.tsx
index 13343b8..5277bfa 100644
--- a/frontend/app/components/ExecStatusIndicatorIcon.tsx
+++ b/frontend/app/components/ExecStatusIndicatorIcon.tsx
@@ -1,5 +1,6 @@
 import {
 	faBan,
+	faCircle,
 	faCircleCheck,
 	faCircleExclamation,
 	faRotate,
@@ -13,6 +14,10 @@ type Props = {
 
 export default function ExecStatusIndicatorIcon({ status }: Props) {
 	switch (status) {
+		case "waiting_submission":
+			return (
+				<FontAwesomeIcon icon={faCircle} fixedWidth className="text-gray-400" />
+			);
 		case "running":
 			return (
 				<FontAwesomeIcon
diff --git a/frontend/app/components/GolfWatchApp.client.tsx b/frontend/app/components/GolfWatchApp.client.tsx
index 481b997..7f582c9 100644
--- a/frontend/app/components/GolfWatchApp.client.tsx
+++ b/frontend/app/components/GolfWatchApp.client.tsx
@@ -78,14 +78,32 @@ export default function GolfWatchApp({
 		iconPath: playerA?.icon_path ?? null,
 		score: null,
 		code: "",
-		submitResult: undefined,
+		submitResult: {
+			status: "waiting_submission",
+			execResults: game.exec_steps.map((r) => ({
+				testcase_id: r.testcase_id,
+				status: "waiting_submission",
+				label: r.label,
+				stdout: "",
+				stderr: "",
+			})),
+		},
 	});
 	const [playerInfoB, setPlayerInfoB] = useState<PlayerInfo>({
 		displayName: playerB?.display_name ?? null,
 		iconPath: playerB?.icon_path ?? null,
 		score: null,
 		code: "",
-		submitResult: undefined,
+		submitResult: {
+			status: "waiting_submission",
+			execResults: game.exec_steps.map((r) => ({
+				testcase_id: r.testcase_id,
+				status: "waiting_submission",
+				label: r.label,
+				stdout: "",
+				stderr: "",
+			})),
+		},
 	});
 
 	if (readyState === ReadyState.UNINSTANTIATED) {
@@ -127,10 +145,9 @@ export default function GolfWatchApp({
 						...prev,
 						submitResult: {
 							status: "running",
-							execResults: game.exec_steps.map((r) => ({
-								testcase_id: r.testcase_id,
+							execResults: prev.submitResult.execResults.map((r) => ({
+								...r,
 								status: "running",
-								label: r.label,
 								stdout: "",
 								stderr: "",
 							})),
@@ -143,9 +160,6 @@ export default function GolfWatchApp({
 						player_id === playerA?.user_id ? setPlayerInfoA : setPlayerInfoB;
 					setter((prev) => {
 						const ret = { ...prev };
-						if (ret.submitResult === undefined) {
-							return ret;
-						}
 						ret.submitResult = {
 							...ret.submitResult,
 							execResults: ret.submitResult.execResults.map((r) =>
@@ -167,9 +181,6 @@ export default function GolfWatchApp({
 						player_id === playerA?.user_id ? setPlayerInfoA : setPlayerInfoB;
 					setter((prev) => {
 						const ret = { ...prev };
-						if (ret.submitResult === undefined) {
-							return ret;
-						}
 						ret.submitResult = {
 							...ret.submitResult,
 							status,
@@ -217,7 +228,6 @@ export default function GolfWatchApp({
 			}
 		}
 	}, [
-		game.exec_steps,
 		game.started_at,
 		lastJsonMessage,
 		readyState,
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
index b436842..2a852e0 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
@@ -60,11 +60,7 @@ export default function GolfWatchAppGaming({
 				</div>
 				<div>
 					<div>
-						{playerInfoA.submitResult?.status ? (
-							<SubmitStatusLabel status={playerInfoA.submitResult?.status} />
-						) : (
-							<span>-</span>
-						)}
+						<SubmitStatusLabel status={playerInfoA.submitResult.status} />
 					</div>
 					<div>
 						<ol>
@@ -92,11 +88,7 @@ export default function GolfWatchAppGaming({
 				</div>
 				<div>
 					<div>
-						{playerInfoB.submitResult?.status ? (
-							<SubmitStatusLabel status={playerInfoB.submitResult?.status} />
-						) : (
-							<span>-</span>
-						)}
+						<SubmitStatusLabel status={playerInfoB.submitResult.status} />
 					</div>
 					<div>
 						<ol>
diff --git a/frontend/app/components/SubmitStatusLabel.tsx b/frontend/app/components/SubmitStatusLabel.tsx
index f6e19bb..2d4890d 100644
--- a/frontend/app/components/SubmitStatusLabel.tsx
+++ b/frontend/app/components/SubmitStatusLabel.tsx
@@ -6,6 +6,8 @@ type Props = {
 
 export default function SubmitStatusLabel({ status }: Props) {
 	switch (status) {
+		case "waiting_submission":
+			return <span>-</span>;
 		case "running":
 			return <span>Running...</span>;
 		case "success":
diff --git a/frontend/app/models/ExecResult.ts b/frontend/app/models/ExecResult.ts
index 59b8ee8..e0b6bb4 100644
--- a/frontend/app/models/ExecResult.ts
+++ b/frontend/app/models/ExecResult.ts
@@ -1,4 +1,5 @@
 export type ExecResultStatus =
+	| "waiting_submission"
 	| "running"
 	| "success"
 	| "wrong_answer"
diff --git a/frontend/app/models/PlayerInfo.ts b/frontend/app/models/PlayerInfo.ts
index 30cccea..8092ab3 100644
--- a/frontend/app/models/PlayerInfo.ts
+++ b/frontend/app/models/PlayerInfo.ts
@@ -5,5 +5,5 @@ export type PlayerInfo = {
 	iconPath: string | null;
 	score: number | null;
 	code: string | null;
-	submitResult?: SubmitResult;
+	submitResult: SubmitResult;
 };
diff --git a/frontend/app/models/SubmitResult.ts b/frontend/app/models/SubmitResult.ts
index 0cb7f9b..6df00b6 100644
--- a/frontend/app/models/SubmitResult.ts
+++ b/frontend/app/models/SubmitResult.ts
@@ -1,6 +1,7 @@
 import type { ExecResult } from "./ExecResult";
 
 export type SubmitResultStatus =
+	| "waiting_submission"
 	| "running"
 	| "success"
 	| "wrong_answer"
-- 
cgit v1.2.3-70-g09d2


From b37d6f213c2f3b19631e5067f39a7106859faaed Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Mon, 12 Aug 2024 05:54:37 +0900
Subject: feat: show left time in play page

---
 backend/admin/handler.go                           | 20 +++----
 backend/api/generated.go                           | 61 +++++++++++-----------
 backend/api/handler.go                             |  8 +--
 backend/game/hub.go                                |  4 +-
 frontend/app/components/GolfPlayApp.client.tsx     | 11 ++--
 .../components/GolfPlayApps/GolfPlayAppGaming.tsx  | 19 +++++--
 frontend/app/components/GolfWatchApp.client.tsx    | 14 ++---
 .../GolfWatchApps/GolfWatchAppGaming.tsx           | 16 +++---
 openapi/api-server.yaml                            |  3 ++
 9 files changed, 90 insertions(+), 66 deletions(-)

(limited to 'frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx')

diff --git a/backend/admin/handler.go b/backend/admin/handler.go
index 5398107..41eacd4 100644
--- a/backend/admin/handler.go
+++ b/backend/admin/handler.go
@@ -229,16 +229,6 @@ func (h *Handler) postGameEdit(c echo.Context) error {
 		}
 	}
 
-	{
-		// TODO:
-		if state != row.State && state == "starting" {
-			err := h.hubs.StartGame(int(gameID))
-			if err != nil {
-				return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
-			}
-		}
-	}
-
 	err = h.q.UpdateGame(c.Request().Context(), db.UpdateGameParams{
 		GameID:          int32(gameID),
 		GameType:        gameType,
@@ -252,5 +242,15 @@ func (h *Handler) postGameEdit(c echo.Context) error {
 		return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
 	}
 
+	{
+		// TODO:
+		if state != row.State && state == "starting" {
+			err := h.hubs.StartGame(int(gameID))
+			if err != nil {
+				return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
+			}
+		}
+	}
+
 	return c.Redirect(http.StatusSeeOther, c.Request().URL.Path)
 }
diff --git a/backend/api/generated.go b/backend/api/generated.go
index 7991f97..7eb5940 100644
--- a/backend/api/generated.go
+++ b/backend/api/generated.go
@@ -97,7 +97,7 @@ type Game struct {
 	GameType        GameGameType `json:"game_type"`
 	Players         []User       `json:"players"`
 	Problem         Problem      `json:"problem"`
-	StartedAt       *int         `json:"started_at,omitempty"`
+	StartedAt       *int64       `json:"started_at,omitempty"`
 	State           GameState    `json:"state"`
 }
 
@@ -169,7 +169,7 @@ type GamePlayerMessageS2CStart struct {
 
 // GamePlayerMessageS2CStartPayload defines model for GamePlayerMessageS2CStartPayload.
 type GamePlayerMessageS2CStartPayload struct {
-	StartAt int `json:"start_at"`
+	StartAt int64 `json:"start_at"`
 }
 
 // GamePlayerMessageS2CSubmitResult defines model for GamePlayerMessageS2CSubmitResult.
@@ -235,7 +235,7 @@ type GameWatcherMessageS2CStart struct {
 
 // GameWatcherMessageS2CStartPayload defines model for GameWatcherMessageS2CStartPayload.
 type GameWatcherMessageS2CStartPayload struct {
-	StartAt int `json:"start_at"`
+	StartAt int64 `json:"start_at"`
 }
 
 // GameWatcherMessageS2CSubmit defines model for GameWatcherMessageS2CSubmit.
@@ -1161,33 +1161,34 @@ func (sh *strictHandler) GetToken(ctx echo.Context, params GetTokenParams) error
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"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",
+	"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",
+	"AA==",
 }
 
 // GetSwagger returns the content of the embedded swagger specification file
diff --git a/backend/api/handler.go b/backend/api/handler.go
index eca6006..a0ecd4c 100644
--- a/backend/api/handler.go
+++ b/backend/api/handler.go
@@ -84,9 +84,9 @@ func (h *Handler) GetGames(ctx context.Context, _ GetGamesRequestObject, user *a
 	}
 	games := make([]Game, len(gameRows))
 	for i, row := range gameRows {
-		var startedAt *int
+		var startedAt *int64
 		if row.StartedAt.Valid {
-			startedAtTimestamp := int(row.StartedAt.Time.Unix())
+			startedAtTimestamp := row.StartedAt.Time.Unix()
 			startedAt = &startedAtTimestamp
 		}
 		games[i] = Game{
@@ -123,9 +123,9 @@ func (h *Handler) GetGame(ctx context.Context, request GetGameRequestObject, use
 		}
 		return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error())
 	}
-	var startedAt *int
+	var startedAt *int64
 	if row.StartedAt.Valid {
-		startedAtTimestamp := int(row.StartedAt.Time.Unix())
+		startedAtTimestamp := row.StartedAt.Time.Unix()
 		startedAt = &startedAtTimestamp
 	}
 	playerRows, err := h.q.ListGamePlayers(ctx, int32(gameID))
diff --git a/backend/game/hub.go b/backend/game/hub.go
index e08ed4a..7c06e05 100644
--- a/backend/game/hub.go
+++ b/backend/game/hub.go
@@ -509,14 +509,14 @@ func (hub *gameHub) startGame() error {
 		player.s2cMessages <- &playerMessageS2CStart{
 			Type: playerMessageTypeS2CStart,
 			Data: playerMessageS2CStartPayload{
-				StartAt: int(startAt.Unix()),
+				StartAt: startAt.Unix(),
 			},
 		}
 	}
 	hub.broadcastToWatchers(&watcherMessageS2CStart{
 		Type: watcherMessageTypeS2CStart,
 		Data: watcherMessageS2CStartPayload{
-			StartAt: int(startAt.Unix()),
+			StartAt: startAt.Unix(),
 		},
 	})
 	err := hub.q.UpdateGameStartedAt(hub.ctx, db.UpdateGameStartedAtParams{
diff --git a/frontend/app/components/GolfPlayApp.client.tsx b/frontend/app/components/GolfPlayApp.client.tsx
index dbc8c1b..d527e07 100644
--- a/frontend/app/components/GolfPlayApp.client.tsx
+++ b/frontend/app/components/GolfPlayApp.client.tsx
@@ -43,16 +43,17 @@ export default function GolfPlayApp({
 	const [leftTimeSeconds, setLeftTimeSeconds] = useState<number | null>(null);
 
 	useEffect(() => {
-		if (gameState === "starting" && startedAt !== null) {
+		if (
+			(gameState === "starting" || gameState === "gaming") &&
+			startedAt !== null
+		) {
 			const timer1 = setInterval(() => {
 				setLeftTimeSeconds((prev) => {
 					if (prev === null) {
 						return null;
 					}
 					if (prev <= 1) {
-						clearInterval(timer1);
 						setGameState("gaming");
-						return 0;
 					}
 					return prev - 1;
 				});
@@ -196,7 +197,7 @@ export default function GolfPlayApp({
 						// The game has already started.
 						if (gameState !== "gaming" && gameState !== "finished") {
 							setStartedAt(game.started_at);
-							setLeftTimeSeconds(0);
+							setLeftTimeSeconds(game.started_at - nowSec);
 							setGameState("gaming");
 						}
 					} else {
@@ -234,6 +235,8 @@ export default function GolfPlayApp({
 		return (
 			<GolfPlayAppGaming
 				gameDisplayName={game.display_name}
+				gameDurationSeconds={game.duration_seconds}
+				leftTimeSeconds={leftTimeSeconds!}
 				playerInfo={playerInfo}
 				problemTitle={game.problem.title}
 				problemDescription={game.problem.description}
diff --git a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
index 667ada2..4730583 100644
--- a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
+++ b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
@@ -1,15 +1,17 @@
+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 SubmitStatusLabel from "../SubmitStatusLabel";
 import ExecStatusIndicatorIcon from "../ExecStatusIndicatorIcon";
-import { faArrowDown } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import SubmitStatusLabel from "../SubmitStatusLabel";
 
 type Props = {
 	gameDisplayName: string;
+	gameDurationSeconds: number;
+	leftTimeSeconds: number;
 	playerInfo: Omit<PlayerInfo, "code">;
 	problemTitle: string;
 	problemDescription: string;
@@ -19,6 +21,8 @@ type Props = {
 
 export default function GolfPlayAppGaming({
 	gameDisplayName,
+	gameDurationSeconds,
+	leftTimeSeconds,
 	playerInfo,
 	problemTitle,
 	problemDescription,
@@ -37,12 +41,19 @@ export default function GolfPlayAppGaming({
 		}
 	};
 
+	const leftTime = (() => {
+		const k = gameDurationSeconds + leftTimeSeconds;
+		const m = Math.floor(k / 60);
+		const s = k % 60;
+		return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
+	})();
+
 	return (
 		<div className="min-h-screen bg-gray-100 flex flex-col">
 			<div className="text-white bg-iosdc-japan flex flex-row justify-between px-4 py-2">
 				<div className="font-bold">
 					<div className="text-gray-100">{gameDisplayName}</div>
-					<div className="text-2xl">03:21</div>
+					<div className="text-2xl">{leftTime}</div>
 				</div>
 				<div className="font-bold text-end">
 					<Link to={"/dashboard"} className="text-gray-100">
diff --git a/frontend/app/components/GolfWatchApp.client.tsx b/frontend/app/components/GolfWatchApp.client.tsx
index 9d3f752..b2f3b69 100644
--- a/frontend/app/components/GolfWatchApp.client.tsx
+++ b/frontend/app/components/GolfWatchApp.client.tsx
@@ -39,16 +39,17 @@ export default function GolfWatchApp({
 	const [leftTimeSeconds, setLeftTimeSeconds] = useState<number | null>(null);
 
 	useEffect(() => {
-		if (gameState === "starting" && startedAt !== null) {
+		if (
+			(gameState === "starting" || gameState === "gaming") &&
+			startedAt !== null
+		) {
 			const timer1 = setInterval(() => {
 				setLeftTimeSeconds((prev) => {
 					if (prev === null) {
 						return null;
 					}
 					if (prev <= 1) {
-						clearInterval(timer1);
 						setGameState("gaming");
-						return 0;
 					}
 					return prev - 1;
 				});
@@ -207,7 +208,7 @@ export default function GolfWatchApp({
 						// The game has already started.
 						if (gameState !== "gaming" && gameState !== "finished") {
 							setStartedAt(game.started_at);
-							setLeftTimeSeconds(0);
+							setLeftTimeSeconds(game.started_at - nowSec);
 							setGameState("gaming");
 						}
 					} else {
@@ -245,10 +246,11 @@ export default function GolfWatchApp({
 	} else if (gameState === "gaming") {
 		return (
 			<GolfWatchAppGaming
-				problem={game.problem!.description}
+				gameDurationSeconds={game.duration_seconds}
+				leftTimeSeconds={leftTimeSeconds!}
 				playerInfoA={playerInfoA}
 				playerInfoB={playerInfoB}
-				leftTimeSeconds={leftTimeSeconds!}
+				problem={game.problem!.description}
 			/>
 		);
 	} else if (gameState === "finished") {
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
index 2a852e0..f9647b3 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming.tsx
@@ -3,23 +3,27 @@ import ExecStatusIndicatorIcon from "../ExecStatusIndicatorIcon";
 import SubmitStatusLabel from "../SubmitStatusLabel";
 
 type Props = {
-	problem: string;
+	gameDurationSeconds: number;
+	leftTimeSeconds: number;
 	playerInfoA: PlayerInfo;
 	playerInfoB: PlayerInfo;
-	leftTimeSeconds: number;
+	problem: string;
 };
 
 export default function GolfWatchAppGaming({
-	problem,
+	gameDurationSeconds,
+	leftTimeSeconds,
 	playerInfoA,
 	playerInfoB,
-	leftTimeSeconds,
+	problem,
 }: Props) {
 	const leftTime = (() => {
-		const m = Math.floor(leftTimeSeconds / 60);
-		const s = leftTimeSeconds % 60;
+		const k = gameDurationSeconds + leftTimeSeconds;
+		const m = Math.floor(k / 60);
+		const s = k % 60;
 		return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
 	})();
+
 	const scoreRatio = (() => {
 		const scoreA = playerInfoA.score ?? 0;
 		const scoreB = playerInfoB.score ?? 0;
diff --git a/openapi/api-server.yaml b/openapi/api-server.yaml
index 036d8d6..94083d3 100644
--- a/openapi/api-server.yaml
+++ b/openapi/api-server.yaml
@@ -212,6 +212,7 @@ components:
         started_at:
           type: integer
           example: 946684800
+          x-go-type: int64
         problem:
           $ref: '#/components/schemas/Problem'
         players:
@@ -286,6 +287,7 @@ components:
         start_at:
           type: integer
           example: 946684800
+          x-go-type: int64
       required:
         - start_at
     GamePlayerMessageS2CExecResult:
@@ -428,6 +430,7 @@ components:
         start_at:
           type: integer
           example: 946684800
+          x-go-type: int64
       required:
         - start_at
     GameWatcherMessageS2CCode:
-- 
cgit v1.2.3-70-g09d2

