diff options
| author | nsfisis <nsfisis@gmail.com> | 2024-08-11 23:11:02 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2024-08-11 23:11:02 +0900 |
| commit | e65ee1dd3403b1572bb64217912b4bc466f65957 (patch) | |
| tree | cf120caf0b1d27b4067b1e1133b787618906d8fc | |
| parent | fe8b14ccc77c829a2baa4034edb22daff9d5d8f8 (diff) | |
| parent | 34d61fcc7035ebd7ffb636d13308166c90b474b2 (diff) | |
| download | phperkaigi-2025-albatross-e65ee1dd3403b1572bb64217912b4bc466f65957.tar.gz phperkaigi-2025-albatross-e65ee1dd3403b1572bb64217912b4bc466f65957.tar.zst phperkaigi-2025-albatross-e65ee1dd3403b1572bb64217912b4bc466f65957.zip | |
Merge branch 'feat/simplify-starting-procedure'
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | backend/admin/handler.go | 2 | ||||
| -rw-r--r-- | backend/admin/templates/game_edit.html | 4 | ||||
| -rw-r--r-- | backend/api/generated.go | 160 | ||||
| -rw-r--r-- | backend/fixtures/dev.sql | 14 | ||||
| -rw-r--r-- | backend/game/hub.go | 120 | ||||
| -rw-r--r-- | backend/game/message.go | 14 | ||||
| -rw-r--r-- | backend/game/models.go | 12 | ||||
| -rw-r--r-- | frontend/app/.server/api/schema.d.ts | 18 | ||||
| -rw-r--r-- | frontend/app/components/GolfPlayApp.client.tsx | 18 | ||||
| -rw-r--r-- | frontend/app/components/GolfWatchApp.client.tsx | 13 | ||||
| -rw-r--r-- | frontend/app/hooks/useWebSocket.ts | 14 | ||||
| -rw-r--r-- | openapi/api-server.yaml | 31 |
13 files changed, 113 insertions, 309 deletions
@@ -3,7 +3,7 @@ DOCKER_COMPOSE := docker compose -f compose.local.yaml all: down build reset up reset: - echo "UPDATE games SET state = 'waiting_entries', started_at = NULL WHERE game_id = 7;" | make psql-query + echo "UPDATE games SET state = 'waiting', started_at = NULL WHERE game_id = 7;" | make psql-query .PHONY: build build: diff --git a/backend/admin/handler.go b/backend/admin/handler.go index d540f57..5398107 100644 --- a/backend/admin/handler.go +++ b/backend/admin/handler.go @@ -231,7 +231,7 @@ func (h *Handler) postGameEdit(c echo.Context) error { { // TODO: - if state != row.State && state == "prepare" { + if state != row.State && state == "starting" { err := h.hubs.StartGame(int(gameID)) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) diff --git a/backend/admin/templates/game_edit.html b/backend/admin/templates/game_edit.html index c1e38a6..66f3b9e 100644 --- a/backend/admin/templates/game_edit.html +++ b/backend/admin/templates/game_edit.html @@ -25,9 +25,7 @@ <label>State</label> <select name="state" required> <option value="closed"{{ if eq .Game.State "closed" }} selected{{ end }}>Closed</option> - <option value="waiting_entries"{{ if eq .Game.State "waiting_entries" }} selected{{ end }}>Waiting Entries</option> - <option value="waiting_start"{{ if eq .Game.State "waiting_start" }} selected{{ end }}>Waiting Start</option> - <option value="prepare"{{ if eq .Game.State "prepare" }} selected{{ end }}>Prepare</option> + <option value="waiting"{{ if eq .Game.State "waiting" }} selected{{ end }}>Waiting</option> <option value="starting"{{ if eq .Game.State "starting" }} selected{{ end }}>Starting</option> <option value="gaming"{{ if eq .Game.State "gaming" }} selected{{ end }}>Gaming</option> <option value="finished"{{ if eq .Game.State "finished" }} selected{{ end }}>Finished</option> diff --git a/backend/api/generated.go b/backend/api/generated.go index a68b13b..ac536c0 100644 --- a/backend/api/generated.go +++ b/backend/api/generated.go @@ -30,13 +30,11 @@ const ( // Defines values for GameState. const ( - Closed GameState = "closed" - Finished GameState = "finished" - Gaming GameState = "gaming" - Prepare GameState = "prepare" - Starting GameState = "starting" - WaitingEntries GameState = "waiting_entries" - WaitingStart GameState = "waiting_start" + Closed GameState = "closed" + Finished GameState = "finished" + Gaming GameState = "gaming" + Starting GameState = "starting" + Waiting GameState = "waiting" ) // Defines values for GamePlayerMessageS2CExecResultPayloadStatus. @@ -114,16 +112,6 @@ type GamePlayerMessageC2SCodePayload struct { Code string `json:"code"` } -// GamePlayerMessageC2SEntry defines model for GamePlayerMessageC2SEntry. -type GamePlayerMessageC2SEntry struct { - Type string `json:"type"` -} - -// GamePlayerMessageC2SReady defines model for GamePlayerMessageC2SReady. -type GamePlayerMessageC2SReady struct { - Type string `json:"type"` -} - // GamePlayerMessageC2SSubmit defines model for GamePlayerMessageC2SSubmit. type GamePlayerMessageC2SSubmit struct { Data GamePlayerMessageC2SSubmitPayload `json:"data"` @@ -155,11 +143,6 @@ type GamePlayerMessageS2CExecResultPayload struct { // GamePlayerMessageS2CExecResultPayloadStatus defines model for GamePlayerMessageS2CExecResultPayload.Status. type GamePlayerMessageS2CExecResultPayloadStatus string -// GamePlayerMessageS2CPrepare defines model for GamePlayerMessageS2CPrepare. -type GamePlayerMessageS2CPrepare struct { - Type string `json:"type"` -} - // GamePlayerMessageS2CStart defines model for GamePlayerMessageS2CStart. type GamePlayerMessageS2CStart struct { Data GamePlayerMessageS2CStartPayload `json:"data"` @@ -376,58 +359,6 @@ func (t *GamePlayerMessage) UnmarshalJSON(b []byte) error { return err } -// AsGamePlayerMessageC2SEntry returns the union data inside the GamePlayerMessageC2S as a GamePlayerMessageC2SEntry -func (t GamePlayerMessageC2S) AsGamePlayerMessageC2SEntry() (GamePlayerMessageC2SEntry, error) { - var body GamePlayerMessageC2SEntry - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromGamePlayerMessageC2SEntry overwrites any union data inside the GamePlayerMessageC2S as the provided GamePlayerMessageC2SEntry -func (t *GamePlayerMessageC2S) FromGamePlayerMessageC2SEntry(v GamePlayerMessageC2SEntry) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeGamePlayerMessageC2SEntry performs a merge with any union data inside the GamePlayerMessageC2S, using the provided GamePlayerMessageC2SEntry -func (t *GamePlayerMessageC2S) MergeGamePlayerMessageC2SEntry(v GamePlayerMessageC2SEntry) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsGamePlayerMessageC2SReady returns the union data inside the GamePlayerMessageC2S as a GamePlayerMessageC2SReady -func (t GamePlayerMessageC2S) AsGamePlayerMessageC2SReady() (GamePlayerMessageC2SReady, error) { - var body GamePlayerMessageC2SReady - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromGamePlayerMessageC2SReady overwrites any union data inside the GamePlayerMessageC2S as the provided GamePlayerMessageC2SReady -func (t *GamePlayerMessageC2S) FromGamePlayerMessageC2SReady(v GamePlayerMessageC2SReady) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeGamePlayerMessageC2SReady performs a merge with any union data inside the GamePlayerMessageC2S, using the provided GamePlayerMessageC2SReady -func (t *GamePlayerMessageC2S) MergeGamePlayerMessageC2SReady(v GamePlayerMessageC2SReady) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - // AsGamePlayerMessageC2SCode returns the union data inside the GamePlayerMessageC2S as a GamePlayerMessageC2SCode func (t GamePlayerMessageC2S) AsGamePlayerMessageC2SCode() (GamePlayerMessageC2SCode, error) { var body GamePlayerMessageC2SCode @@ -490,32 +421,6 @@ func (t *GamePlayerMessageC2S) UnmarshalJSON(b []byte) error { return err } -// AsGamePlayerMessageS2CPrepare returns the union data inside the GamePlayerMessageS2C as a GamePlayerMessageS2CPrepare -func (t GamePlayerMessageS2C) AsGamePlayerMessageS2CPrepare() (GamePlayerMessageS2CPrepare, error) { - var body GamePlayerMessageS2CPrepare - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromGamePlayerMessageS2CPrepare overwrites any union data inside the GamePlayerMessageS2C as the provided GamePlayerMessageS2CPrepare -func (t *GamePlayerMessageS2C) FromGamePlayerMessageS2CPrepare(v GamePlayerMessageS2CPrepare) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeGamePlayerMessageS2CPrepare performs a merge with any union data inside the GamePlayerMessageS2C, using the provided GamePlayerMessageS2CPrepare -func (t *GamePlayerMessageS2C) MergeGamePlayerMessageS2CPrepare(v GamePlayerMessageS2CPrepare) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - // AsGamePlayerMessageS2CStart returns the union data inside the GamePlayerMessageS2C as a GamePlayerMessageS2CStart func (t GamePlayerMessageS2C) AsGamePlayerMessageS2CStart() (GamePlayerMessageS2CStart, error) { var body GamePlayerMessageS2CStart @@ -1203,34 +1108,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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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/backend/fixtures/dev.sql b/backend/fixtures/dev.sql index 9ba7f73..c7d1080 100644 --- a/backend/fixtures/dev.sql +++ b/backend/fixtures/dev.sql @@ -29,13 +29,13 @@ VALUES INSERT INTO games (game_type, state, display_name, duration_seconds, problem_id) VALUES - ('1v1', 'waiting_entries', 'TEST game 1', 180, 1), - ('1v1', 'closed', 'TEST game 2', 180, 2), - ('1v1', 'finished', 'TEST game 3', 180, 3), - ('multiplayer', 'waiting_start', 'TEST game 4', 180, 4), - ('multiplayer', 'closed', 'TEST game 5', 180, 5), - ('multiplayer', 'finished', 'TEST game 6', 180, 6), - ('multiplayer', 'waiting_entries', 'TEST game 7', 180, 7); + ('1v1', 'waiting', 'TEST game 1', 180, 1), + ('1v1', 'closed', 'TEST game 2', 180, 2), + ('1v1', 'finished', 'TEST game 3', 180, 3), + ('multiplayer', 'waiting', 'TEST game 4', 180, 4), + ('multiplayer', 'closed', 'TEST game 5', 180, 5), + ('multiplayer', 'finished', 'TEST game 6', 180, 6), + ('multiplayer', 'waiting', 'TEST game 7', 180, 7); INSERT INTO game_players (game_id, user_id) diff --git a/backend/game/hub.go b/backend/game/hub.go index b454bb5..97303ec 100644 --- a/backend/game/hub.go +++ b/backend/game/hub.go @@ -17,20 +17,12 @@ import ( "github.com/nsfisis/iosdc-japan-2024-albatross/backend/taskqueue" ) -type playerClientState int - -const ( - playerClientStateWaitingEntries playerClientState = iota - playerClientStateEntried - playerClientStateReady -) - type gameHub struct { ctx context.Context game *game q *db.Queries taskQueue *taskqueue.Queue - players map[*playerClient]playerClientState + players map[*playerClient]bool registerPlayer chan *playerClient unregisterPlayer chan *playerClient playerC2SMessages chan *playerMessageC2SWithClient @@ -46,7 +38,7 @@ func newGameHub(ctx context.Context, game *game, q *db.Queries, taskQueue *taskq game: game, q: q, taskQueue: taskQueue, - players: make(map[*playerClient]playerClientState), + players: make(map[*playerClient]bool), registerPlayer: make(chan *playerClient), unregisterPlayer: make(chan *playerClient), playerC2SMessages: make(chan *playerMessageC2SWithClient), @@ -66,7 +58,7 @@ func (hub *gameHub) run() { for { select { case player := <-hub.registerPlayer: - hub.players[player] = playerClientStateWaitingEntries + hub.players[player] = true case player := <-hub.unregisterPlayer: if _, ok := hub.players[player]; ok { hub.closePlayerClient(player) @@ -79,73 +71,6 @@ func (hub *gameHub) run() { } case message := <-hub.playerC2SMessages: switch msg := message.message.(type) { - case *playerMessageC2SEntry: - log.Printf("entry: %v", message.message) - // TODO: assert state is waiting_entries - hub.players[message.client] = playerClientStateEntried - entriedPlayerCount := 0 - for _, state := range hub.players { - if playerClientStateEntried <= state { - entriedPlayerCount++ - } - } - if entriedPlayerCount == hub.game.playerCount { - err := hub.q.UpdateGameState(hub.ctx, db.UpdateGameStateParams{ - GameID: int32(hub.game.gameID), - State: string(gameStateWaitingStart), - }) - if err != nil { - log.Fatalf("failed to set game state: %v", err) - } - hub.game.state = gameStateWaitingStart - } - case *playerMessageC2SReady: - log.Printf("ready: %v", message.message) - // TODO: assert state is prepare - hub.players[message.client] = playerClientStateReady - readyPlayerCount := 0 - for _, state := range hub.players { - if playerClientStateReady <= state { - readyPlayerCount++ - } - } - if readyPlayerCount == hub.game.playerCount { - startAt := time.Now().Add(11 * time.Second).UTC() - for player := range hub.players { - player.s2cMessages <- &playerMessageS2CStart{ - Type: playerMessageTypeS2CStart, - Data: playerMessageS2CStartPayload{ - StartAt: int(startAt.Unix()), - }, - } - } - hub.broadcastToWatchers(&watcherMessageS2CStart{ - Type: watcherMessageTypeS2CStart, - Data: watcherMessageS2CStartPayload{ - StartAt: int(startAt.Unix()), - }, - }) - err := hub.q.UpdateGameStartedAt(hub.ctx, db.UpdateGameStartedAtParams{ - GameID: int32(hub.game.gameID), - StartedAt: pgtype.Timestamp{ - Time: startAt, - InfinityModifier: pgtype.Finite, - Valid: true, - }, - }) - if err != nil { - log.Fatalf("failed to set game state: %v", err) - } - hub.game.startedAt = &startAt - err = hub.q.UpdateGameState(hub.ctx, db.UpdateGameStateParams{ - GameID: int32(hub.game.gameID), - State: string(gameStateStarting), - }) - if err != nil { - log.Fatalf("failed to set game state: %v", err) - } - hub.game.state = gameStateStarting - } case *playerMessageC2SCode: // TODO: assert game state is gaming log.Printf("code: %v", message.message) @@ -608,26 +533,47 @@ func (hub *gameHub) processTaskResultRunTestcase( } func (hub *gameHub) startGame() error { + startAt := time.Now().Add(11 * time.Second).UTC() for player := range hub.players { - player.s2cMessages <- &playerMessageS2CPrepare{ - Type: playerMessageTypeS2CPrepare, + player.s2cMessages <- &playerMessageS2CStart{ + Type: playerMessageTypeS2CStart, + Data: playerMessageS2CStartPayload{ + StartAt: int(startAt.Unix()), + }, } } - - err := hub.q.UpdateGameState(hub.ctx, db.UpdateGameStateParams{ + hub.broadcastToWatchers(&watcherMessageS2CStart{ + Type: watcherMessageTypeS2CStart, + Data: watcherMessageS2CStartPayload{ + StartAt: int(startAt.Unix()), + }, + }) + err := hub.q.UpdateGameStartedAt(hub.ctx, db.UpdateGameStartedAtParams{ GameID: int32(hub.game.gameID), - State: string(gameStatePrepare), + StartedAt: pgtype.Timestamp{ + Time: startAt, + InfinityModifier: pgtype.Finite, + Valid: true, + }, }) if err != nil { - return err + log.Fatalf("failed to set game state: %v", err) } - hub.game.state = gameStatePrepare + hub.game.startedAt = &startAt + err = hub.q.UpdateGameState(hub.ctx, db.UpdateGameStateParams{ + GameID: int32(hub.game.gameID), + State: string(gameStateStarting), + }) + if err != nil { + log.Fatalf("failed to set game state: %v", err) + } + hub.game.state = gameStateStarting return nil } func (hub *gameHub) close() { - for client := range hub.players { - hub.closePlayerClient(client) + for player := range hub.players { + hub.closePlayerClient(player) } close(hub.registerPlayer) close(hub.unregisterPlayer) diff --git a/backend/game/message.go b/backend/game/message.go index 2895119..4877ac4 100644 --- a/backend/game/message.go +++ b/backend/game/message.go @@ -8,11 +8,8 @@ import ( ) const ( - playerMessageTypeS2CPrepare = "player:s2c:prepare" playerMessageTypeS2CStart = "player:s2c:start" playerMessageTypeS2CExecResult = "player:s2c:execresult" - playerMessageTypeC2SEntry = "player:c2s:entry" - playerMessageTypeC2SReady = "player:c2s:ready" playerMessageTypeC2SCode = "player:c2s:code" playerMessageTypeC2SSubmit = "player:c2s:submit" ) @@ -23,15 +20,12 @@ type playerMessageC2SWithClient struct { } type playerMessageS2C = interface{} -type playerMessageS2CPrepare = api.GamePlayerMessageS2CPrepare type playerMessageS2CStart = api.GamePlayerMessageS2CStart type playerMessageS2CStartPayload = api.GamePlayerMessageS2CStartPayload type playerMessageS2CExecResult = api.GamePlayerMessageS2CExecResult type playerMessageS2CExecResultPayload = api.GamePlayerMessageS2CExecResultPayload type playerMessageC2S = interface{} -type playerMessageC2SEntry = api.GamePlayerMessageC2SEntry -type playerMessageC2SReady = api.GamePlayerMessageC2SReady type playerMessageC2SCode = api.GamePlayerMessageC2SCode type playerMessageC2SCodePayload = api.GamePlayerMessageC2SCodePayload type playerMessageC2SSubmit = api.GamePlayerMessageC2SSubmit @@ -44,14 +38,6 @@ func asPlayerMessageC2S(raw map[string]json.RawMessage) (playerMessageC2S, error } switch typ { - case playerMessageTypeC2SEntry: - return &playerMessageC2SEntry{ - Type: playerMessageTypeC2SEntry, - }, nil - case playerMessageTypeC2SReady: - return &playerMessageC2SReady{ - Type: playerMessageTypeC2SReady, - }, nil case playerMessageTypeC2SCode: var payload playerMessageC2SCodePayload if err := json.Unmarshal(raw["data"], &payload); err != nil { diff --git a/backend/game/models.go b/backend/game/models.go index 4080482..f232fa5 100644 --- a/backend/game/models.go +++ b/backend/game/models.go @@ -13,13 +13,11 @@ const ( gameType1v1 = api.N1V1 gameTypeMultiplayer = api.Multiplayer - gameStateClosed gameState = api.Closed - gameStateWaitingEntries gameState = api.WaitingEntries - gameStateWaitingStart gameState = api.WaitingStart - gameStatePrepare gameState = api.Prepare - gameStateStarting gameState = api.Starting - gameStateGaming gameState = api.Gaming - gameStateFinished gameState = api.Finished + gameStateClosed gameState = api.Closed + gameStateWaiting gameState = api.Waiting + gameStateStarting gameState = api.Starting + gameStateGaming gameState = api.Gaming + gameStateFinished gameState = api.Finished ) type game struct { diff --git a/frontend/app/.server/api/schema.d.ts b/frontend/app/.server/api/schema.d.ts index 93385f2..2d116b9 100644 --- a/frontend/app/.server/api/schema.d.ts +++ b/frontend/app/.server/api/schema.d.ts @@ -104,7 +104,7 @@ export interface components { * @example closed * @enum {string} */ - state: "closed" | "waiting_entries" | "waiting_start" | "prepare" | "starting" | "gaming" | "finished"; + state: "closed" | "waiting" | "starting" | "gaming" | "finished"; /** @example Game 1 */ display_name: string; /** @example 360 */ @@ -130,11 +130,7 @@ export interface components { description: string; }; GamePlayerMessage: components["schemas"]["GamePlayerMessageS2C"] | components["schemas"]["GamePlayerMessageC2S"]; - GamePlayerMessageS2C: components["schemas"]["GamePlayerMessageS2CPrepare"] | components["schemas"]["GamePlayerMessageS2CStart"] | components["schemas"]["GamePlayerMessageS2CExecResult"]; - GamePlayerMessageS2CPrepare: { - /** @constant */ - type: "player:s2c:prepare"; - }; + GamePlayerMessageS2C: components["schemas"]["GamePlayerMessageS2CStart"] | components["schemas"]["GamePlayerMessageS2CExecResult"]; GamePlayerMessageS2CStart: { /** @constant */ type: "player:s2c:start"; @@ -158,15 +154,7 @@ export interface components { /** @example 100 */ score: number | null; }; - GamePlayerMessageC2S: components["schemas"]["GamePlayerMessageC2SEntry"] | components["schemas"]["GamePlayerMessageC2SReady"] | components["schemas"]["GamePlayerMessageC2SCode"] | components["schemas"]["GamePlayerMessageC2SSubmit"]; - GamePlayerMessageC2SEntry: { - /** @constant */ - type: "player:c2s:entry"; - }; - GamePlayerMessageC2SReady: { - /** @constant */ - type: "player:c2s:ready"; - }; + GamePlayerMessageC2S: components["schemas"]["GamePlayerMessageC2SCode"] | components["schemas"]["GamePlayerMessageC2SSubmit"]; GamePlayerMessageC2SCode: { /** @constant */ type: "player:c2s:code"; diff --git a/frontend/app/components/GolfPlayApp.client.tsx b/frontend/app/components/GolfPlayApp.client.tsx index 70c463f..eafbd1d 100644 --- a/frontend/app/components/GolfPlayApp.client.tsx +++ b/frontend/app/components/GolfPlayApp.client.tsx @@ -1,14 +1,15 @@ import { useEffect, useState } from "react"; -import useWebSocket, { ReadyState } from "react-use-websocket"; import { useDebouncedCallback } from "use-debounce"; import type { components } from "../.server/api/schema"; +import useWebSocket, { ReadyState } from "../hooks/useWebSocket"; import GolfPlayAppConnecting from "./GolfPlayApps/GolfPlayAppConnecting"; import GolfPlayAppFinished from "./GolfPlayApps/GolfPlayAppFinished"; import GolfPlayAppGaming from "./GolfPlayApps/GolfPlayAppGaming"; import GolfPlayAppStarting from "./GolfPlayApps/GolfPlayAppStarting"; import GolfPlayAppWaiting from "./GolfPlayApps/GolfPlayAppWaiting"; -type WebSocketMessage = components["schemas"]["GamePlayerMessageS2C"]; +type GamePlayerMessageS2C = components["schemas"]["GamePlayerMessageS2C"]; +type GamePlayerMessageC2S = components["schemas"]["GamePlayerMessageC2S"]; type Game = components["schemas"]["Game"]; @@ -26,8 +27,10 @@ export default function GolfPlayApp({ ? `ws://localhost:8002/iosdc-japan/2024/code-battle/sock/golf/${game.game_id}/play?token=${sockToken}` : `wss://t.nil.ninja/iosdc-japan/2024/code-battle/sock/golf/${game.game_id}/play?token=${sockToken}`; - const { sendJsonMessage, lastJsonMessage, readyState } = - useWebSocket<WebSocketMessage>(socketUrl, {}); + const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket< + GamePlayerMessageS2C, + GamePlayerMessageC2S + >(socketUrl); const [gameState, setGameState] = useState<GameState>("connecting"); @@ -101,10 +104,7 @@ export default function GolfPlayApp({ } else if (readyState === ReadyState.OPEN) { if (lastJsonMessage !== null) { console.log(lastJsonMessage.type); - if (lastJsonMessage.type === "player:s2c:prepare") { - console.log("player:c2s:ready"); - sendJsonMessage({ type: "player:c2s:ready" }); - } else if (lastJsonMessage.type === "player:s2c:start") { + if (lastJsonMessage.type === "player:s2c:start") { if ( gameState !== "starting" && gameState !== "gaming" && @@ -128,8 +128,6 @@ export default function GolfPlayApp({ } } else { setGameState("waiting"); - console.log("player:c2s:entry"); - sendJsonMessage({ type: "player:c2s:entry" }); } } }, [sendJsonMessage, lastJsonMessage, readyState, gameState, currentScore]); diff --git a/frontend/app/components/GolfWatchApp.client.tsx b/frontend/app/components/GolfWatchApp.client.tsx index a9c9989..cef6f9a 100644 --- a/frontend/app/components/GolfWatchApp.client.tsx +++ b/frontend/app/components/GolfWatchApp.client.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import useWebSocket, { ReadyState } from "react-use-websocket"; import type { components } from "../.server/api/schema"; +import useWebSocket, { ReadyState } from "../hooks/useWebSocket"; import GolfWatchAppConnecting from "./GolfWatchApps/GolfWatchAppConnecting"; import GolfWatchAppFinished from "./GolfWatchApps/GolfWatchAppFinished"; import GolfWatchAppGaming, { @@ -9,7 +9,8 @@ import GolfWatchAppGaming, { import GolfWatchAppStarting from "./GolfWatchApps/GolfWatchAppStarting"; import GolfWatchAppWaiting from "./GolfWatchApps/GolfWatchAppWaiting"; -type WebSocketMessage = components["schemas"]["GameWatcherMessageS2C"]; +type GameWatcherMessageS2C = components["schemas"]["GameWatcherMessageS2C"]; +type GameWatcherMessageC2S = never; type Game = components["schemas"]["Game"]; @@ -27,10 +28,10 @@ export default function GolfWatchApp({ ? `ws://localhost:8002/iosdc-japan/2024/code-battle/sock/golf/${game.game_id}/watch?token=${sockToken}` : `wss://t.nil.ninja/iosdc-japan/2024/code-battle/sock/golf/${game.game_id}/watch?token=${sockToken}`; - const { lastJsonMessage, readyState } = useWebSocket<WebSocketMessage>( - socketUrl, - {}, - ); + const { lastJsonMessage, readyState } = useWebSocket< + GameWatcherMessageS2C, + GameWatcherMessageC2S + >(socketUrl); const [gameState, setGameState] = useState<GameState>("connecting"); diff --git a/frontend/app/hooks/useWebSocket.ts b/frontend/app/hooks/useWebSocket.ts new file mode 100644 index 0000000..8fe688f --- /dev/null +++ b/frontend/app/hooks/useWebSocket.ts @@ -0,0 +1,14 @@ +import useWebSocketOriginal, { ReadyState } from "react-use-websocket"; + +export { ReadyState }; + +// Typed version of useWebSocket() hook. +export default function useWebSocket<ReceiveMessage, SendMessage>( + url: string, +): { + sendJsonMessage: (message: SendMessage) => void; + lastJsonMessage: ReceiveMessage; + readyState: ReadyState; +} { + return useWebSocketOriginal(url); +} diff --git a/openapi/api-server.yaml b/openapi/api-server.yaml index 16dd7c5..826c4ba 100644 --- a/openapi/api-server.yaml +++ b/openapi/api-server.yaml @@ -199,9 +199,7 @@ components: example: "closed" enum: - closed - - waiting_entries - - waiting_start - - prepare + - waiting - starting - gaming - finished @@ -268,17 +266,8 @@ components: - $ref: '#/components/schemas/GamePlayerMessageC2S' GamePlayerMessageS2C: oneOf: - - $ref: '#/components/schemas/GamePlayerMessageS2CPrepare' - $ref: '#/components/schemas/GamePlayerMessageS2CStart' - $ref: '#/components/schemas/GamePlayerMessageS2CExecResult' - GamePlayerMessageS2CPrepare: - type: object - properties: - type: - type: string - const: "player:s2c:prepare" - required: - - type GamePlayerMessageS2CStart: type: object properties: @@ -331,26 +320,8 @@ components: - score GamePlayerMessageC2S: oneOf: - - $ref: '#/components/schemas/GamePlayerMessageC2SEntry' - - $ref: '#/components/schemas/GamePlayerMessageC2SReady' - $ref: '#/components/schemas/GamePlayerMessageC2SCode' - $ref: '#/components/schemas/GamePlayerMessageC2SSubmit' - GamePlayerMessageC2SEntry: - type: object - properties: - type: - type: string - const: "player:c2s:entry" - required: - - type - GamePlayerMessageC2SReady: - type: object - properties: - type: - type: string - const: "player:c2s:ready" - required: - - type GamePlayerMessageC2SCode: type: object properties: |
