From c7599c74722864d54c96a3f3d52e28290abb9eac Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Sun, 11 Aug 2024 22:51:03 +0900
Subject: refactor(frontend): add stronger typing to useWebSocket()

---
 frontend/app/components/GolfPlayApp.client.tsx | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

(limited to 'frontend/app/components/GolfPlayApp.client.tsx')

diff --git a/frontend/app/components/GolfPlayApp.client.tsx b/frontend/app/components/GolfPlayApp.client.tsx
index 70c463f..4e200e2 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");
 
-- 
cgit v1.2.3-70-g09d2


From 34d61fcc7035ebd7ffb636d13308166c90b474b2 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Sun, 11 Aug 2024 22:15:36 +0900
Subject: feat: simplify starting procedure

---
 Makefile                                       |   2 +-
 backend/admin/handler.go                       |   2 +-
 backend/admin/templates/game_edit.html         |   4 +-
 backend/api/generated.go                       | 160 +++++--------------------
 backend/fixtures/dev.sql                       |  14 +--
 backend/game/hub.go                            | 120 +++++--------------
 backend/game/message.go                        |  14 ---
 backend/game/models.go                         |  12 +-
 frontend/app/.server/api/schema.d.ts           |  18 +--
 frontend/app/components/GolfPlayApp.client.tsx |   7 +-
 openapi/api-server.yaml                        |  31 +----
 11 files changed, 85 insertions(+), 299 deletions(-)

(limited to 'frontend/app/components/GolfPlayApp.client.tsx')

diff --git a/Makefile b/Makefile
index 5cda0b3..9e776e9 100644
--- a/Makefile
+++ b/Makefile
@@ -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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",
+	"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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 4e200e2..eafbd1d 100644
--- a/frontend/app/components/GolfPlayApp.client.tsx
+++ b/frontend/app/components/GolfPlayApp.client.tsx
@@ -104,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" &&
@@ -131,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/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:
-- 
cgit v1.2.3-70-g09d2

