From b3d91170662accdd48cdfc42ca2180521b096669 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 12 Aug 2024 01:56:57 +0900 Subject: refactor: rename verification_steps for consistant naming --- backend/api/handler.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'backend/api/handler.go') diff --git a/backend/api/handler.go b/backend/api/handler.go index 4150ba6..7ef77e5 100644 --- a/backend/api/handler.go +++ b/backend/api/handler.go @@ -146,12 +146,12 @@ func (h *Handler) GetGame(ctx context.Context, request GetGameRequestObject, use if err != nil { return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - verificationSteps := make([]VerificationStep, len(testcaseIDs)+1) - verificationSteps[0] = VerificationStep{ + execSteps := make([]ExecStep, len(testcaseIDs)+1) + execSteps[0] = ExecStep{ Label: "Compile", } for i, testcaseID := range testcaseIDs { - verificationSteps[i+1] = VerificationStep{ + execSteps[i+1] = ExecStep{ TestcaseID: nullable.NewNullableWithValue(int(testcaseID)), Label: fmt.Sprintf("Testcase %d", i+1), } @@ -168,8 +168,8 @@ func (h *Handler) GetGame(ctx context.Context, request GetGameRequestObject, use Title: row.Title, Description: row.Description, }, - Players: players, - VerificationSteps: verificationSteps, + Players: players, + ExecSteps: execSteps, } return GetGame200JSONResponse{ Game: game, -- cgit v1.2.3-70-g09d2 From 8cbb00ae115545a5803f6d08283985ca089d7e41 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 12 Aug 2024 02:11:30 +0900 Subject: feat: add `submitresult` message --- backend/api/generated.go | 125 ++++++++++++----- backend/api/handler.go | 3 +- backend/game/hub.go | 150 +++++++++++++-------- backend/game/message.go | 11 +- frontend/app/.server/api/schema.d.ts | 22 ++- frontend/app/components/GolfPlayApp.client.tsx | 82 +++++++++-- .../components/GolfPlayApps/GolfPlayAppGaming.tsx | 13 +- frontend/app/components/GolfWatchApp.client.tsx | 8 +- openapi/api-server.yaml | 42 +++++- 9 files changed, 328 insertions(+), 128 deletions(-) (limited to 'backend/api/handler.go') diff --git a/backend/api/generated.go b/backend/api/generated.go index f3be897..7991f97 100644 --- a/backend/api/generated.go +++ b/backend/api/generated.go @@ -40,13 +40,23 @@ const ( // Defines values for GamePlayerMessageS2CExecResultPayloadStatus. const ( GamePlayerMessageS2CExecResultPayloadStatusCompileError GamePlayerMessageS2CExecResultPayloadStatus = "compile_error" - GamePlayerMessageS2CExecResultPayloadStatusFailure GamePlayerMessageS2CExecResultPayloadStatus = "failure" GamePlayerMessageS2CExecResultPayloadStatusInternalError GamePlayerMessageS2CExecResultPayloadStatus = "internal_error" + GamePlayerMessageS2CExecResultPayloadStatusRuntimeError GamePlayerMessageS2CExecResultPayloadStatus = "runtime_error" GamePlayerMessageS2CExecResultPayloadStatusSuccess GamePlayerMessageS2CExecResultPayloadStatus = "success" GamePlayerMessageS2CExecResultPayloadStatusTimeout GamePlayerMessageS2CExecResultPayloadStatus = "timeout" GamePlayerMessageS2CExecResultPayloadStatusWrongAnswer GamePlayerMessageS2CExecResultPayloadStatus = "wrong_answer" ) +// Defines values for GamePlayerMessageS2CSubmitResultPayloadStatus. +const ( + GamePlayerMessageS2CSubmitResultPayloadStatusCompileError GamePlayerMessageS2CSubmitResultPayloadStatus = "compile_error" + GamePlayerMessageS2CSubmitResultPayloadStatusInternalError GamePlayerMessageS2CSubmitResultPayloadStatus = "internal_error" + GamePlayerMessageS2CSubmitResultPayloadStatusRuntimeError GamePlayerMessageS2CSubmitResultPayloadStatus = "runtime_error" + GamePlayerMessageS2CSubmitResultPayloadStatusSuccess GamePlayerMessageS2CSubmitResultPayloadStatus = "success" + GamePlayerMessageS2CSubmitResultPayloadStatusTimeout GamePlayerMessageS2CSubmitResultPayloadStatus = "timeout" + GamePlayerMessageS2CSubmitResultPayloadStatusWrongAnswer GamePlayerMessageS2CSubmitResultPayloadStatus = "wrong_answer" +) + // Defines values for GameWatcherMessageS2CExecResultPayloadStatus. const ( GameWatcherMessageS2CExecResultPayloadStatusCompileError GameWatcherMessageS2CExecResultPayloadStatus = "compile_error" @@ -59,12 +69,12 @@ const ( // Defines values for GameWatcherMessageS2CSubmitResultPayloadStatus. const ( - CompileError GameWatcherMessageS2CSubmitResultPayloadStatus = "compile_error" - InternalError GameWatcherMessageS2CSubmitResultPayloadStatus = "internal_error" - RuntimeError GameWatcherMessageS2CSubmitResultPayloadStatus = "runtime_error" - Success GameWatcherMessageS2CSubmitResultPayloadStatus = "success" - Timeout GameWatcherMessageS2CSubmitResultPayloadStatus = "timeout" - WrongAnswer GameWatcherMessageS2CSubmitResultPayloadStatus = "wrong_answer" + GameWatcherMessageS2CSubmitResultPayloadStatusCompileError GameWatcherMessageS2CSubmitResultPayloadStatus = "compile_error" + GameWatcherMessageS2CSubmitResultPayloadStatusInternalError GameWatcherMessageS2CSubmitResultPayloadStatus = "internal_error" + GameWatcherMessageS2CSubmitResultPayloadStatusRuntimeError GameWatcherMessageS2CSubmitResultPayloadStatus = "runtime_error" + GameWatcherMessageS2CSubmitResultPayloadStatusSuccess GameWatcherMessageS2CSubmitResultPayloadStatus = "success" + GameWatcherMessageS2CSubmitResultPayloadStatusTimeout GameWatcherMessageS2CSubmitResultPayloadStatus = "timeout" + GameWatcherMessageS2CSubmitResultPayloadStatusWrongAnswer GameWatcherMessageS2CSubmitResultPayloadStatus = "wrong_answer" ) // Error defines model for Error. @@ -142,8 +152,10 @@ type GamePlayerMessageS2CExecResult struct { // GamePlayerMessageS2CExecResultPayload defines model for GamePlayerMessageS2CExecResultPayload. type GamePlayerMessageS2CExecResultPayload struct { - Score nullable.Nullable[int] `json:"score"` - Status GamePlayerMessageS2CExecResultPayloadStatus `json:"status"` + Status GamePlayerMessageS2CExecResultPayloadStatus `json:"status"` + Stderr string `json:"stderr"` + Stdout string `json:"stdout"` + TestcaseID nullable.Nullable[int] `json:"testcase_id"` } // GamePlayerMessageS2CExecResultPayloadStatus defines model for GamePlayerMessageS2CExecResultPayload.Status. @@ -160,6 +172,21 @@ type GamePlayerMessageS2CStartPayload struct { StartAt int `json:"start_at"` } +// GamePlayerMessageS2CSubmitResult defines model for GamePlayerMessageS2CSubmitResult. +type GamePlayerMessageS2CSubmitResult struct { + Data GamePlayerMessageS2CSubmitResultPayload `json:"data"` + Type string `json:"type"` +} + +// GamePlayerMessageS2CSubmitResultPayload defines model for GamePlayerMessageS2CSubmitResultPayload. +type GamePlayerMessageS2CSubmitResultPayload struct { + Score nullable.Nullable[int] `json:"score"` + Status GamePlayerMessageS2CSubmitResultPayloadStatus `json:"status"` +} + +// GamePlayerMessageS2CSubmitResultPayloadStatus defines model for GamePlayerMessageS2CSubmitResultPayload.Status. +type GamePlayerMessageS2CSubmitResultPayloadStatus string + // GameWatcherMessage defines model for GameWatcherMessage. type GameWatcherMessage struct { union json.RawMessage @@ -473,6 +500,32 @@ func (t *GamePlayerMessageS2C) MergeGamePlayerMessageS2CExecResult(v GamePlayerM return err } +// AsGamePlayerMessageS2CSubmitResult returns the union data inside the GamePlayerMessageS2C as a GamePlayerMessageS2CSubmitResult +func (t GamePlayerMessageS2C) AsGamePlayerMessageS2CSubmitResult() (GamePlayerMessageS2CSubmitResult, error) { + var body GamePlayerMessageS2CSubmitResult + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromGamePlayerMessageS2CSubmitResult overwrites any union data inside the GamePlayerMessageS2C as the provided GamePlayerMessageS2CSubmitResult +func (t *GamePlayerMessageS2C) FromGamePlayerMessageS2CSubmitResult(v GamePlayerMessageS2CSubmitResult) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeGamePlayerMessageS2CSubmitResult performs a merge with any union data inside the GamePlayerMessageS2C, using the provided GamePlayerMessageS2CSubmitResult +func (t *GamePlayerMessageS2C) MergeGamePlayerMessageS2CSubmitResult(v GamePlayerMessageS2CSubmitResult) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + func (t GamePlayerMessageS2C) MarshalJSON() ([]byte, error) { b, err := t.union.MarshalJSON() return b, err @@ -1108,33 +1161,33 @@ func (sh *strictHandler) GetToken(ctx echo.Context, params GetTokenParams) error // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/UzJPYQeNuY8CzBN0/TfAAAA//9NY96x1CQAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/backend/api/handler.go b/backend/api/handler.go index 7ef77e5..eca6006 100644 --- a/backend/api/handler.go +++ b/backend/api/handler.go @@ -148,7 +148,8 @@ func (h *Handler) GetGame(ctx context.Context, request GetGameRequestObject, use } execSteps := make([]ExecStep, len(testcaseIDs)+1) execSteps[0] = ExecStep{ - Label: "Compile", + TestcaseID: nullable.NewNullNullable[int](), + Label: "Compile", } for i, testcaseID := range testcaseIDs { execSteps[i+1] = ExecStep{ diff --git a/backend/game/hub.go b/backend/game/hub.go index 670b05f..490b79e 100644 --- a/backend/game/hub.go +++ b/backend/game/hub.go @@ -137,6 +137,15 @@ func (hub *gameHub) run() { } } +func (hub *gameHub) sendToPlayer(playerID int, msg playerMessageS2C) { + for player := range hub.players { + if player.playerID == playerID { + player.s2cMessages <- msg + return + } + } +} + func (hub *gameHub) broadcastToWatchers(msg watcherMessageS2C) { for watcher := range hub.watchers { watcher.s2cMessages <- msg @@ -159,18 +168,12 @@ func (hub *gameHub) processTaskResults() { case *taskqueue.TaskResultCreateSubmissionRecord: err := hub.processTaskResultCreateSubmissionRecord(taskResult) if err != nil { - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status), - }, - } - } + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CSubmitResult{ + Type: playerMessageTypeS2CSubmitResult, + Data: playerMessageS2CSubmitResultPayload{ + Status: api.GamePlayerMessageS2CSubmitResultPayloadStatus(err.Status), + }, + }) hub.broadcastToWatchers(&watcherMessageS2CSubmitResult{ Type: watcherMessageTypeS2CSubmitResult, Data: watcherMessageS2CSubmitResultPayload{ @@ -182,18 +185,20 @@ func (hub *gameHub) processTaskResults() { case *taskqueue.TaskResultCompileSwiftToWasm: err := hub.processTaskResultCompileSwiftToWasm(taskResult) if err != nil { - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status), - }, - } - } + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CExecResult{ + Type: playerMessageTypeS2CExecResult, + Data: playerMessageS2CExecResultPayload{ + Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status), + Stdout: err.Stdout, + Stderr: err.Stderr, + }, + }) + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CSubmitResult{ + Type: playerMessageTypeS2CSubmitResult, + Data: playerMessageS2CSubmitResultPayload{ + Status: api.GamePlayerMessageS2CSubmitResultPayloadStatus(err.Status), + }, + }) hub.broadcastToWatchers(&watcherMessageS2CExecResult{ Type: watcherMessageTypeS2CExecResult, Data: watcherMessageS2CExecResultPayload{ @@ -214,18 +219,20 @@ func (hub *gameHub) processTaskResults() { case *taskqueue.TaskResultCompileWasmToNativeExecutable: err := hub.processTaskResultCompileWasmToNativeExecutable(taskResult) if err != nil { - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status), - }, - } - } + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CExecResult{ + Type: playerMessageTypeS2CExecResult, + Data: playerMessageS2CExecResultPayload{ + Status: api.GamePlayerMessageS2CExecResultPayloadStatus(err.Status), + Stdout: err.Stdout, + Stderr: err.Stderr, + }, + }) + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CSubmitResult{ + Type: playerMessageTypeS2CSubmitResult, + Data: playerMessageS2CSubmitResultPayload{ + Status: api.GamePlayerMessageS2CSubmitResultPayloadStatus(err.Status), + }, + }) hub.broadcastToWatchers(&watcherMessageS2CExecResult{ Type: watcherMessageTypeS2CExecResult, Data: watcherMessageS2CExecResultPayload{ @@ -243,6 +250,15 @@ func (hub *gameHub) processTaskResults() { }, }) } else { + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CExecResult{ + Type: playerMessageTypeS2CExecResult, + Data: playerMessageS2CExecResultPayload{ + Status: api.GamePlayerMessageS2CExecResultPayloadStatus("success"), + // TODO: inherit the command stdout/stderr. + Stdout: "Successfully compiled", + Stderr: "", + }, + }) hub.broadcastToWatchers(&watcherMessageS2CExecResult{ Type: watcherMessageTypeS2CExecResult, Data: watcherMessageS2CExecResultPayload{ @@ -268,18 +284,22 @@ func (hub *gameHub) processTaskResults() { Stderr: "", }) if err != nil { - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ - Type: playerMessageTypeS2CExecResult, - Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus("internal_error"), - }, - } - } + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CExecResult{ + Type: playerMessageTypeS2CExecResult, + Data: playerMessageS2CExecResultPayload{ + TestcaseID: nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)), + Status: api.GamePlayerMessageS2CExecResultPayloadStatus("internal_error"), + // TODO: inherit the command stdout/stderr? + Stdout: "", + Stderr: "internal error", + }, + }) + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CSubmitResult{ + Type: playerMessageTypeS2CSubmitResult, + Data: playerMessageS2CSubmitResultPayload{ + Status: api.GamePlayerMessageS2CSubmitResultPayloadStatus("internal_error"), + }, + }) hub.broadcastToWatchers(&watcherMessageS2CExecResult{ Type: watcherMessageTypeS2CExecResult, Data: watcherMessageS2CExecResultPayload{ @@ -300,19 +320,16 @@ func (hub *gameHub) processTaskResults() { }) continue } - for player := range hub.players { - if player.playerID != taskResult.TaskPayload.UserID() { - continue - } - player.s2cMessages <- &playerMessageS2CExecResult{ + if err1 != nil { + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CExecResult{ Type: playerMessageTypeS2CExecResult, Data: playerMessageS2CExecResultPayload{ - Score: nil, - Status: api.GamePlayerMessageS2CExecResultPayloadStatus(aggregatedStatus), + TestcaseID: nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)), + Status: api.GamePlayerMessageS2CExecResultPayloadStatus(aggregatedStatus), + Stdout: err1.Stdout, + Stderr: err1.Stderr, }, - } - } - if err1 != nil { + }) hub.broadcastToWatchers(&watcherMessageS2CExecResult{ Type: watcherMessageTypeS2CExecResult, Data: watcherMessageS2CExecResultPayload{ @@ -324,6 +341,16 @@ func (hub *gameHub) processTaskResults() { }, }) } else { + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CExecResult{ + Type: playerMessageTypeS2CExecResult, + Data: playerMessageS2CExecResultPayload{ + TestcaseID: nullable.NewNullableWithValue(int(taskResult.TaskPayload.TestcaseID)), + Status: api.GamePlayerMessageS2CExecResultPayloadStatus("success"), + // TODO: inherit the command stdout/stderr? + Stdout: "Testcase passed", + Stderr: "", + }, + }) hub.broadcastToWatchers(&watcherMessageS2CExecResult{ Type: watcherMessageTypeS2CExecResult, Data: watcherMessageS2CExecResultPayload{ @@ -344,6 +371,13 @@ func (hub *gameHub) processTaskResults() { score = nullable.NewNullableWithValue(int(codeSize)) } } + hub.sendToPlayer(taskResult.TaskPayload.UserID(), &playerMessageS2CSubmitResult{ + Type: playerMessageTypeS2CSubmitResult, + Data: playerMessageS2CSubmitResultPayload{ + Status: api.GamePlayerMessageS2CSubmitResultPayloadStatus(aggregatedStatus), + Score: score, + }, + }) hub.broadcastToWatchers(&watcherMessageS2CSubmitResult{ Type: watcherMessageTypeS2CSubmitResult, Data: watcherMessageS2CSubmitResultPayload{ diff --git a/backend/game/message.go b/backend/game/message.go index 4877ac4..b535c1d 100644 --- a/backend/game/message.go +++ b/backend/game/message.go @@ -8,10 +8,11 @@ import ( ) const ( - playerMessageTypeS2CStart = "player:s2c:start" - playerMessageTypeS2CExecResult = "player:s2c:execresult" - playerMessageTypeC2SCode = "player:c2s:code" - playerMessageTypeC2SSubmit = "player:c2s:submit" + playerMessageTypeS2CStart = "player:s2c:start" + playerMessageTypeS2CExecResult = "player:s2c:execresult" + playerMessageTypeS2CSubmitResult = "player:s2c:submitresult" + playerMessageTypeC2SCode = "player:c2s:code" + playerMessageTypeC2SSubmit = "player:c2s:submit" ) type playerMessageC2SWithClient struct { @@ -24,6 +25,8 @@ type playerMessageS2CStart = api.GamePlayerMessageS2CStart type playerMessageS2CStartPayload = api.GamePlayerMessageS2CStartPayload type playerMessageS2CExecResult = api.GamePlayerMessageS2CExecResult type playerMessageS2CExecResultPayload = api.GamePlayerMessageS2CExecResultPayload +type playerMessageS2CSubmitResult = api.GamePlayerMessageS2CSubmitResult +type playerMessageS2CSubmitResultPayload = api.GamePlayerMessageS2CSubmitResultPayload type playerMessageC2S = interface{} type playerMessageC2SCode = api.GamePlayerMessageC2SCode diff --git a/frontend/app/.server/api/schema.d.ts b/frontend/app/.server/api/schema.d.ts index 5b37081..7fd612e 100644 --- a/frontend/app/.server/api/schema.d.ts +++ b/frontend/app/.server/api/schema.d.ts @@ -130,7 +130,7 @@ export interface components { description: string; }; GamePlayerMessage: components["schemas"]["GamePlayerMessageS2C"] | components["schemas"]["GamePlayerMessageC2S"]; - GamePlayerMessageS2C: components["schemas"]["GamePlayerMessageS2CStart"] | components["schemas"]["GamePlayerMessageS2CExecResult"]; + GamePlayerMessageS2C: components["schemas"]["GamePlayerMessageS2CStart"] | components["schemas"]["GamePlayerMessageS2CExecResult"] | components["schemas"]["GamePlayerMessageS2CSubmitResult"]; GamePlayerMessageS2CStart: { /** @constant */ type: "player:s2c:start"; @@ -146,11 +146,29 @@ export interface components { data: components["schemas"]["GamePlayerMessageS2CExecResultPayload"]; }; GamePlayerMessageS2CExecResultPayload: { + /** @example 1 */ + testcase_id: number | null; /** * @example success * @enum {string} */ - status: "success" | "failure" | "timeout" | "internal_error" | "compile_error" | "wrong_answer"; + status: "success" | "wrong_answer" | "timeout" | "runtime_error" | "internal_error" | "compile_error"; + /** @example Hello, world! */ + stdout: string; + /** @example */ + stderr: string; + }; + GamePlayerMessageS2CSubmitResult: { + /** @constant */ + type: "player:s2c:submitresult"; + data: components["schemas"]["GamePlayerMessageS2CSubmitResultPayload"]; + }; + GamePlayerMessageS2CSubmitResultPayload: { + /** + * @example success + * @enum {string} + */ + status: "success" | "wrong_answer" | "timeout" | "runtime_error" | "internal_error" | "compile_error"; /** @example 100 */ score: number | null; }; diff --git a/frontend/app/components/GolfPlayApp.client.tsx b/frontend/app/components/GolfPlayApp.client.tsx index ef3a229..dbc8c1b 100644 --- a/frontend/app/components/GolfPlayApp.client.tsx +++ b/frontend/app/components/GolfPlayApp.client.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import { useDebouncedCallback } from "use-debounce"; import type { components } from "../.server/api/schema"; import useWebSocket, { ReadyState } from "../hooks/useWebSocket"; +import type { PlayerInfo } from "../models/PlayerInfo"; import GolfPlayAppConnecting from "./GolfPlayApps/GolfPlayAppConnecting"; import GolfPlayAppFinished from "./GolfPlayApps/GolfPlayAppFinished"; import GolfPlayAppGaming from "./GolfPlayApps/GolfPlayAppGaming"; @@ -73,9 +74,21 @@ export default function GolfPlayApp({ } }, [gameState, startedAt, game.duration_seconds]); - const [currentScore, setCurrentScore] = useState(null); - - const [lastExecStatus, setLastExecStatus] = useState(null); + const [playerInfo, setPlayerInfo] = useState>({ + displayName: player.display_name, + iconPath: player.icon_path ?? null, + score: null, + submitResult: { + status: "waiting_submission", + execResults: game.exec_steps.map((r) => ({ + testcase_id: r.testcase_id, + status: "waiting_submission", + label: r.label, + stdout: "", + stderr: "", + })), + }, + }); const onCodeChange = useDebouncedCallback((code: string) => { console.log("player:c2s:code"); @@ -94,6 +107,18 @@ export default function GolfPlayApp({ type: "player:c2s:submit", data: { code }, }); + setPlayerInfo((prev) => ({ + ...prev, + submitResult: { + status: "running", + execResults: prev.submitResult.execResults.map((r) => ({ + ...r, + status: "running", + stdout: "", + stderr: "", + })), + }, + })); }, 1000); if (readyState === ReadyState.UNINSTANTIATED) { @@ -123,14 +148,46 @@ export default function GolfPlayApp({ setGameState("starting"); } } else if (lastJsonMessage.type === "player:s2c:execresult") { + const { testcase_id, status, stdout, stderr } = lastJsonMessage.data; + setPlayerInfo((prev) => { + const ret = { ...prev }; + ret.submitResult = { + ...prev.submitResult, + execResults: prev.submitResult.execResults.map((r) => + r.testcase_id === testcase_id && r.status === "running" + ? { + ...r, + status, + stdout, + stderr, + } + : r, + ), + }; + return ret; + }); + } else if (lastJsonMessage.type === "player:s2c:submitresult") { const { status, score } = lastJsonMessage.data; - if ( - score !== null && - (currentScore === null || score < currentScore) - ) { - setCurrentScore(score); - } - setLastExecStatus(status); + setPlayerInfo((prev) => { + const ret = { ...prev }; + ret.submitResult = { + ...prev.submitResult, + status, + }; + if (status === "success") { + if (score) { + if (ret.score === null || score < ret.score) { + ret.score = score; + } + } + } else { + ret.submitResult.execResults = prev.submitResult.execResults.map( + (r) => + r.status === "running" ? { ...r, status: "canceled" } : r, + ); + } + return ret; + }); } } else { if (game.started_at) { @@ -165,7 +222,6 @@ export default function GolfPlayApp({ lastJsonMessage, readyState, gameState, - currentScore, ]); if (gameState === "connecting") { @@ -178,13 +234,11 @@ export default function GolfPlayApp({ return ( ); } else if (gameState === "finished") { diff --git a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx index 03acf5a..08490a6 100644 --- a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx +++ b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx @@ -1,28 +1,25 @@ 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"; type Props = { gameDisplayName: string; - playerDisplayName: string; + playerInfo: Omit; problemTitle: string; problemDescription: string; onCodeChange: (code: string) => void; onCodeSubmit: (code: string) => void; - currentScore: number | null; - lastExecStatus: string | null; }; export default function GolfPlayAppGaming({ gameDisplayName, - playerDisplayName, + playerInfo, problemTitle, problemDescription, onCodeChange, onCodeSubmit, - currentScore, - lastExecStatus, }: Props) { const textareaRef = useRef(null); @@ -45,7 +42,7 @@ export default function GolfPlayAppGaming({
- {playerDisplayName} + {playerInfo.displayName}
@@ -69,7 +66,7 @@ export default function GolfPlayAppGaming({ 提出
- Score: {currentScore ?? "-"} ({lastExecStatus ?? "-"}) + Score: {playerInfo.score ?? "-"}
diff --git a/frontend/app/components/GolfWatchApp.client.tsx b/frontend/app/components/GolfWatchApp.client.tsx index 7f582c9..9d3f752 100644 --- a/frontend/app/components/GolfWatchApp.client.tsx +++ b/frontend/app/components/GolfWatchApp.client.tsx @@ -161,8 +161,8 @@ export default function GolfWatchApp({ setter((prev) => { const ret = { ...prev }; ret.submitResult = { - ...ret.submitResult, - execResults: ret.submitResult.execResults.map((r) => + ...prev.submitResult, + execResults: prev.submitResult.execResults.map((r) => r.testcase_id === testcase_id && r.status === "running" ? { ...r, @@ -182,7 +182,7 @@ export default function GolfWatchApp({ setter((prev) => { const ret = { ...prev }; ret.submitResult = { - ...ret.submitResult, + ...prev.submitResult, status, }; if (status === "success") { @@ -192,7 +192,7 @@ export default function GolfWatchApp({ } } } else { - ret.submitResult.execResults = ret.submitResult.execResults.map( + ret.submitResult.execResults = prev.submitResult.execResults.map( (r) => r.status === "running" ? { ...r, status: "canceled" } : r, ); diff --git a/openapi/api-server.yaml b/openapi/api-server.yaml index d13d738..036d8d6 100644 --- a/openapi/api-server.yaml +++ b/openapi/api-server.yaml @@ -268,6 +268,7 @@ components: oneOf: - $ref: '#/components/schemas/GamePlayerMessageS2CStart' - $ref: '#/components/schemas/GamePlayerMessageS2CExecResult' + - $ref: '#/components/schemas/GamePlayerMessageS2CSubmitResult' GamePlayerMessageS2CStart: type: object properties: @@ -301,16 +302,55 @@ components: GamePlayerMessageS2CExecResultPayload: type: object properties: + testcase_id: + type: integer + nullable: true + example: 1 status: type: string example: "success" enum: - success - - failure + - wrong_answer - timeout + - runtime_error - internal_error - compile_error + stdout: + type: string + example: "Hello, world!" + stderr: + type: string + example: "" + required: + - testcase_id + - status + - stdout + - stderr + GamePlayerMessageS2CSubmitResult: + type: object + properties: + type: + type: string + const: "player:s2c:submitresult" + data: + $ref: '#/components/schemas/GamePlayerMessageS2CSubmitResultPayload' + required: + - type + - data + GamePlayerMessageS2CSubmitResultPayload: + type: object + properties: + status: + type: string + example: "success" + enum: + - success - wrong_answer + - timeout + - runtime_error + - internal_error + - compile_error score: type: integer nullable: true -- cgit v1.2.3-70-g09d2 From b37d6f213c2f3b19631e5067f39a7106859faaed Mon Sep 17 00:00:00 2001 From: nsfisis 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 'backend/api/handler.go') 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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", + "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(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 ( ; 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 (
{gameDisplayName}
-
03:21
+
{leftTime}
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(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 ( ); } 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