refactor(arcade): remove non-productive debug logging from memory-quiz
Removed verbose console.log statements added during settings persistence debugging: - socket-server.ts: Removed JSON.stringify logging of gameConfig flow - RoomMemoryQuizProvider.tsx: Removed logging from mergedInitialState useMemo and setConfig - MemoryQuizGameValidator.ts: Removed logging from validateAcceptNumber The actual fix (playMode parameter addition) is preserved. Debug logging was only needed to identify the root cause and is no longer necessary. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -303,23 +303,171 @@ if (body.gameConfig !== undefined) {
|
||||
}
|
||||
```
|
||||
|
||||
## Schema Refactoring: Separate Table for Game Configs
|
||||
|
||||
### Current Problem
|
||||
|
||||
All game configs are stored in a single JSON column in `arcade_rooms.gameConfig`:
|
||||
|
||||
```json
|
||||
{
|
||||
"matching": { "gameType": "...", "difficulty": 15 },
|
||||
"memory-quiz": { "selectedCount": 8, "playMode": "competitive" }
|
||||
}
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
- No schema validation
|
||||
- Inefficient updates (read/parse/modify/serialize entire blob)
|
||||
- Grows without bounds as more games added
|
||||
- Can't query or index individual game settings
|
||||
- No audit trail
|
||||
- Potential concurrent update race conditions
|
||||
|
||||
### Recommended: Separate Table
|
||||
|
||||
Create `room_game_configs` table with one row per game per room:
|
||||
|
||||
```typescript
|
||||
// src/db/schema/room-game-configs.ts
|
||||
|
||||
export const roomGameConfigs = sqliteTable('room_game_configs', {
|
||||
id: text('id').primaryKey().$defaultFn(() => createId()),
|
||||
roomId: text('room_id')
|
||||
.notNull()
|
||||
.references(() => arcadeRooms.id, { onDelete: 'cascade' }),
|
||||
gameName: text('game_name', {
|
||||
enum: ['matching', 'memory-quiz', 'complement-race'],
|
||||
}).notNull(),
|
||||
config: text('config', { mode: 'json' }).notNull(), // Game-specific JSON
|
||||
createdAt: integer('created_at', { mode: 'timestamp' })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
updatedAt: integer('updated_at', { mode: 'timestamp' })
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date()),
|
||||
}, (table) => ({
|
||||
uniqueRoomGame: uniqueIndex('room_game_idx').on(table.roomId, table.gameName),
|
||||
}))
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Smaller rows (only configs for games that have been used)
|
||||
- ✅ Easier updates (single row, not entire JSON blob)
|
||||
- ✅ Can track updatedAt per game
|
||||
- ✅ Better concurrency (no lock contention between games)
|
||||
- ✅ Foundation for future audit trail
|
||||
|
||||
**Migration Strategy:**
|
||||
1. Create new table
|
||||
2. Migrate existing data from `arcade_rooms.gameConfig`
|
||||
3. Update all config read/write code
|
||||
4. Deploy and test
|
||||
5. Drop old `gameConfig` column from `arcade_rooms`
|
||||
|
||||
See migration SQL below.
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
1. **HIGH:** Create shared config types (`game-configs.ts`) - Prevents type mismatches
|
||||
2. **HIGH:** Create helper functions (`game-config-helpers.ts`) - Reduces duplication
|
||||
3. **MEDIUM:** Update validators to use shared types - Enforces consistency
|
||||
4. **MEDIUM:** Add exhaustiveness checking - Catches missing fields at compile time
|
||||
5. **LOW:** Add runtime validation - Prevents invalid data from being saved
|
||||
### Phase 1: Schema Migration (HIGHEST PRIORITY)
|
||||
1. **Create new table** - Add `room_game_configs` schema
|
||||
2. **Create migration** - SQL to migrate existing data
|
||||
3. **Update helper functions** - Adapt to new table structure
|
||||
4. **Update all read/write code** - Use new table
|
||||
5. **Test thoroughly** - Verify all settings persist correctly
|
||||
6. **Drop old column** - Remove `gameConfig` from `arcade_rooms`
|
||||
|
||||
### Phase 2: Type Safety (HIGH)
|
||||
1. **Create shared config types** (`game-configs.ts`) - Prevents type mismatches
|
||||
2. **Create helper functions** (`game-config-helpers.ts`) - Now queries new table
|
||||
3. **Update validators** to use shared types - Enforces consistency
|
||||
|
||||
### Phase 3: Compile-Time Safety (MEDIUM)
|
||||
1. **Add exhaustiveness checking** - Catches missing fields at compile time
|
||||
2. **Enforce validator config types** - Use shared types
|
||||
|
||||
### Phase 4: Runtime Safety (LOW)
|
||||
1. **Add runtime validation** - Prevents invalid data from being saved
|
||||
|
||||
## Detailed Migration SQL
|
||||
|
||||
```sql
|
||||
-- drizzle/migrations/XXXX_split_game_configs.sql
|
||||
|
||||
-- Create new table
|
||||
CREATE TABLE room_game_configs (
|
||||
id TEXT PRIMARY KEY,
|
||||
room_id TEXT NOT NULL REFERENCES arcade_rooms(id) ON DELETE CASCADE,
|
||||
game_name TEXT NOT NULL CHECK(game_name IN ('matching', 'memory-quiz', 'complement-race')),
|
||||
config TEXT NOT NULL, -- JSON
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX room_game_idx ON room_game_configs(room_id, game_name);
|
||||
|
||||
-- Migrate existing 'matching' configs
|
||||
INSERT INTO room_game_configs (id, room_id, game_name, config, created_at, updated_at)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))),
|
||||
id,
|
||||
'matching',
|
||||
json_extract(game_config, '$.matching'),
|
||||
created_at,
|
||||
last_activity
|
||||
FROM arcade_rooms
|
||||
WHERE json_extract(game_config, '$.matching') IS NOT NULL;
|
||||
|
||||
-- Migrate existing 'memory-quiz' configs
|
||||
INSERT INTO room_game_configs (id, room_id, game_name, config, created_at, updated_at)
|
||||
SELECT
|
||||
lower(hex(randomblob(16))),
|
||||
id,
|
||||
'memory-quiz',
|
||||
json_extract(game_config, '$."memory-quiz"'),
|
||||
created_at,
|
||||
last_activity
|
||||
FROM arcade_rooms
|
||||
WHERE json_extract(game_config, '$."memory-quiz"') IS NOT NULL;
|
||||
|
||||
-- After testing and verifying all works:
|
||||
-- ALTER TABLE arcade_rooms DROP COLUMN game_config;
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
1. Create new files without modifying existing code
|
||||
2. Add shared types and helpers
|
||||
3. Migrate validators one at a time
|
||||
4. Migrate providers one at a time
|
||||
5. Migrate socket server
|
||||
6. Remove old inline config reading logic
|
||||
7. Add runtime validation last (optional)
|
||||
### Step-by-Step with Checkpoints
|
||||
|
||||
**Checkpoint 1: Schema & Migration**
|
||||
1. Create `src/db/schema/room-game-configs.ts`
|
||||
2. Export from `src/db/schema/index.ts`
|
||||
3. Generate and apply migration
|
||||
4. Verify data migrated correctly
|
||||
|
||||
**Checkpoint 2: Helper Functions**
|
||||
1. Create shared config types in `src/lib/arcade/game-configs.ts`
|
||||
2. Create helper functions in `src/lib/arcade/game-config-helpers.ts`
|
||||
3. Add unit tests for helpers
|
||||
|
||||
**Checkpoint 3: Update Config Reads**
|
||||
1. Update socket-server.ts to read from new table
|
||||
2. Update RoomMemoryQuizProvider to read from new table
|
||||
3. Update RoomMemoryPairsProvider to read from new table
|
||||
4. Test: Load room and verify settings appear
|
||||
|
||||
**Checkpoint 4: Update Config Writes**
|
||||
1. Update useRoomData.ts updateGameConfig to write to new table
|
||||
2. Update settings API to write to new table
|
||||
3. Test: Change settings and verify they persist
|
||||
|
||||
**Checkpoint 5: Update Validators**
|
||||
1. Update validators to use shared config types
|
||||
2. Test: All games work correctly
|
||||
|
||||
**Checkpoint 6: Cleanup**
|
||||
1. Remove old gameConfig column references
|
||||
2. Drop gameConfig column from arcade_rooms table
|
||||
3. Final testing of all games
|
||||
|
||||
## Benefits Summary
|
||||
|
||||
|
||||
@@ -210,36 +210,19 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
|
||||
// Settings are scoped by game name to preserve settings when switching games
|
||||
const mergedInitialState = useMemo(() => {
|
||||
const gameConfig = roomData?.gameConfig as Record<string, any> | null | undefined
|
||||
console.log(
|
||||
'[RoomMemoryQuizProvider] Initializing - Full roomData.gameConfig:',
|
||||
JSON.stringify(gameConfig, null, 2)
|
||||
)
|
||||
|
||||
if (!gameConfig) {
|
||||
console.log(
|
||||
'[RoomMemoryQuizProvider] No gameConfig, using initialState with playMode:',
|
||||
initialState.playMode
|
||||
)
|
||||
return initialState
|
||||
}
|
||||
|
||||
// Get settings for this specific game (memory-quiz)
|
||||
const savedConfig = gameConfig['memory-quiz'] as Record<string, any> | null | undefined
|
||||
console.log(
|
||||
'[RoomMemoryQuizProvider] Extracted memory-quiz config:',
|
||||
JSON.stringify(savedConfig, null, 2)
|
||||
)
|
||||
console.log('[RoomMemoryQuizProvider] savedConfig.playMode value:', savedConfig?.playMode)
|
||||
|
||||
if (!savedConfig) {
|
||||
console.log(
|
||||
'[RoomMemoryQuizProvider] No saved config for memory-quiz, using initialState with playMode:',
|
||||
initialState.playMode
|
||||
)
|
||||
return initialState
|
||||
}
|
||||
|
||||
const merged = {
|
||||
return {
|
||||
...initialState,
|
||||
// Restore settings from saved config
|
||||
selectedCount: savedConfig.selectedCount ?? initialState.selectedCount,
|
||||
@@ -247,22 +230,6 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
|
||||
selectedDifficulty: savedConfig.selectedDifficulty ?? initialState.selectedDifficulty,
|
||||
playMode: savedConfig.playMode ?? initialState.playMode,
|
||||
}
|
||||
console.log(
|
||||
'[RoomMemoryQuizProvider] Merged state:',
|
||||
JSON.stringify(
|
||||
{
|
||||
selectedCount: merged.selectedCount,
|
||||
displayTime: merged.displayTime,
|
||||
selectedDifficulty: merged.selectedDifficulty,
|
||||
playMode: merged.playMode,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
console.log('[RoomMemoryQuizProvider] Final merged.playMode:', merged.playMode)
|
||||
|
||||
return merged
|
||||
}, [roomData?.gameConfig])
|
||||
|
||||
// Arcade session integration WITH room sync
|
||||
@@ -420,26 +387,19 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
|
||||
// Settings are scoped by game name to preserve settings when switching games
|
||||
if (roomData?.id) {
|
||||
const currentGameConfig = (roomData.gameConfig as Record<string, any>) || {}
|
||||
console.log('[RoomMemoryQuizProvider] Current gameConfig:', currentGameConfig)
|
||||
|
||||
const currentMemoryQuizConfig =
|
||||
(currentGameConfig['memory-quiz'] as Record<string, any>) || {}
|
||||
console.log('[RoomMemoryQuizProvider] Current memory-quiz config:', currentMemoryQuizConfig)
|
||||
|
||||
const updatedConfig = {
|
||||
...currentGameConfig,
|
||||
'memory-quiz': {
|
||||
...currentMemoryQuizConfig,
|
||||
[field]: value,
|
||||
},
|
||||
}
|
||||
console.log('[RoomMemoryQuizProvider] Saving updated gameConfig:', updatedConfig)
|
||||
updateGameConfig({
|
||||
roomId: roomData.id,
|
||||
gameConfig: updatedConfig,
|
||||
gameConfig: {
|
||||
...currentGameConfig,
|
||||
'memory-quiz': {
|
||||
...currentMemoryQuizConfig,
|
||||
[field]: value,
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
console.warn('[RoomMemoryQuizProvider] No roomData.id, cannot save config')
|
||||
}
|
||||
},
|
||||
[viewerId, sendMove, roomData?.id, roomData?.gameConfig, updateGameConfig]
|
||||
|
||||
@@ -180,12 +180,6 @@ export class MemoryQuizGameValidator
|
||||
}
|
||||
|
||||
// Number must be in correct answers
|
||||
console.log('[MemoryQuizValidator] Checking number:', {
|
||||
number,
|
||||
correctAnswers: state.correctAnswers,
|
||||
includes: state.correctAnswers.includes(number),
|
||||
})
|
||||
|
||||
if (!state.correctAnswers.includes(number)) {
|
||||
return {
|
||||
valid: false,
|
||||
@@ -407,12 +401,7 @@ export class MemoryQuizGameValidator
|
||||
selectedDifficulty: DifficultyLevel
|
||||
playMode?: 'cooperative' | 'competitive'
|
||||
}): SorobanQuizState {
|
||||
console.log(
|
||||
'[MemoryQuizValidator] getInitialState called with config:',
|
||||
JSON.stringify(config, null, 2)
|
||||
)
|
||||
|
||||
const initialState: SorobanQuizState = {
|
||||
return {
|
||||
cards: [],
|
||||
quizCards: [],
|
||||
correctAnswers: [],
|
||||
@@ -439,9 +428,6 @@ export class MemoryQuizGameValidator
|
||||
testingMode: false,
|
||||
showOnScreenKeyboard: false,
|
||||
}
|
||||
|
||||
console.log('[MemoryQuizValidator] getInitialState returning playMode:', initialState.playMode)
|
||||
return initialState
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,35 +94,12 @@ export function initializeSocketServer(httpServer: HTTPServer) {
|
||||
} else if (room.gameName === 'memory-quiz') {
|
||||
// Access nested gameConfig: { 'memory-quiz': { selectedCount, displayTime, selectedDifficulty, playMode } }
|
||||
const memoryQuizConfig = (room.gameConfig as any)?.['memory-quiz'] || {}
|
||||
console.log(
|
||||
'[join-arcade-session] memory-quiz - Full room.gameConfig:',
|
||||
JSON.stringify(room.gameConfig, null, 2)
|
||||
)
|
||||
console.log(
|
||||
'[join-arcade-session] memory-quiz - Extracted memoryQuizConfig:',
|
||||
JSON.stringify(memoryQuizConfig, null, 2)
|
||||
)
|
||||
console.log(
|
||||
'[join-arcade-session] memory-quiz - playMode from config:',
|
||||
memoryQuizConfig.playMode
|
||||
)
|
||||
|
||||
const configToPass = {
|
||||
initialState = validator.getInitialState({
|
||||
selectedCount: memoryQuizConfig.selectedCount || 5,
|
||||
displayTime: memoryQuizConfig.displayTime || 2.0,
|
||||
selectedDifficulty: memoryQuizConfig.selectedDifficulty || 'easy',
|
||||
playMode: memoryQuizConfig.playMode || 'cooperative',
|
||||
}
|
||||
console.log(
|
||||
'[join-arcade-session] memory-quiz - Config being passed to getInitialState:',
|
||||
JSON.stringify(configToPass, null, 2)
|
||||
)
|
||||
|
||||
initialState = validator.getInitialState(configToPass)
|
||||
console.log(
|
||||
'[join-arcade-session] memory-quiz - initialState.playMode after getInitialState:',
|
||||
initialState.playMode
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// Fallback for other games
|
||||
initialState = validator.getInitialState(room.gameConfig || {})
|
||||
@@ -152,17 +129,7 @@ export function initializeSocketServer(httpServer: HTTPServer) {
|
||||
roomId,
|
||||
version: session.version,
|
||||
sessionUserId: session.userId,
|
||||
gameName: session.currentGame,
|
||||
})
|
||||
|
||||
// Log playMode specifically for memory-quiz
|
||||
if (session.currentGame === 'memory-quiz') {
|
||||
console.log(
|
||||
'[join-arcade-session] memory-quiz session - gameState.playMode:',
|
||||
(session.gameState as any).playMode
|
||||
)
|
||||
}
|
||||
|
||||
socket.emit('session-state', {
|
||||
gameState: session.gameState,
|
||||
currentGame: session.currentGame,
|
||||
|
||||
Reference in New Issue
Block a user