All files / web/src/lib/arcade game-registry.ts

0% Statements 0/158
0% Branches 0/1
0% Functions 0/1
0% Lines 0/158

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159                                                                                                                                                                                                                                                                                                                             
/**
 * Game Registry
 *
 * Central registry for all arcade games.
 * Games are explicitly registered here after being defined.
 */

import type { GameConfig, GameDefinition, GameMove, GameState } from './game-sdk/types'

/**
 * Global game registry
 * Maps game name to game definition
 * Using `any` for generics to allow different game types
 */
const registry = new Map<string, GameDefinition<any, any, any>>()

/**
 * Register a game in the registry
 *
 * @param game - Game definition to register
 * @throws Error if game with same name already registered
 */
export function registerGame<
  TConfig extends GameConfig,
  TState extends GameState,
  TMove extends GameMove,
>(game: GameDefinition<TConfig, TState, TMove>): void {
  const { name } = game.manifest

  if (registry.has(name)) {
    throw new Error(`Game "${name}" is already registered`)
  }

  // Verify validator is also registered server-side
  try {
    const { hasValidator, getValidator } = require('./validators')
    if (!hasValidator(name)) {
      console.error(
        `⚠️  Game "${name}" registered but validator not found in server registry!` +
          `\n   Add to src/lib/arcade/validators.ts to enable multiplayer.`
      )
    } else {
      const serverValidator = getValidator(name)
      if (serverValidator !== game.validator) {
        console.warn(
          `⚠️  Game "${name}" has different validator instances (client vs server).` +
            `\n   This may cause issues. Ensure both use the same import.`
        )
      }
    }
  } catch (error) {
    // If validators.ts can't be imported (e.g., in browser), skip check
    // This is expected - validator registry is isomorphic but check only runs server-side
  }

  registry.set(name, game)
  console.log(`✅ Registered game: ${name}`)
}

/**
 * Get a game from the registry
 *
 * @param gameName - Internal game identifier
 * @returns Game definition or undefined if not found
 */
export function getGame(gameName: string): GameDefinition<any, any, any> | undefined {
  return registry.get(gameName)
}

/**
 * Get all registered games
 *
 * @returns Array of all game definitions
 */
export function getAllGames(): GameDefinition<any, any, any>[] {
  return Array.from(registry.values())
}

/**
 * Get all available games (where available: true)
 *
 * @returns Array of available game definitions
 */
export function getAvailableGames(): GameDefinition<any, any, any>[] {
  return getAllGames().filter((game) => game.manifest.available)
}

/**
 * Check if a game is registered
 *
 * @param gameName - Internal game identifier
 * @returns true if game is registered
 */
export function hasGame(gameName: string): boolean {
  return registry.has(gameName)
}

/**
 * Clear all games from registry (used for testing)
 */
export function clearRegistry(): void {
  registry.clear()
}

// ============================================================================
// Game Registrations
//
// Only practice-break-ready games are statically imported so the practice
// page bundle stays small.  All other games are lazy-loaded via
// ensureAllGamesRegistered().
// ============================================================================

import { matchingGame } from '@/arcade-games/matching'
import { musicMatchingGame } from '@/arcade-games/music-matching'
import { knowYourWorldGame } from '@/arcade-games/know-your-world'
import { memoryQuizGame } from '@/arcade-games/memory-quiz'
import { typeRacerJrGame } from '@/arcade-games/type-racer-jr'
import { constantExplorerGame } from '@/arcade-games/constant-explorer'

registerGame(matchingGame)
registerGame(musicMatchingGame)
registerGame(knowYourWorldGame)
registerGame(memoryQuizGame)
registerGame(typeRacerJrGame)
registerGame(constantExplorerGame)

// All other games are loaded on demand — only pages that need the full
// registry (arcade, home, games list) call ensureAllGamesRegistered().
let _allGamesPromise: Promise<void> | null = null

/**
 * Ensure all games (including heavy/non-practice ones) are registered.
 * Call this on pages that need the full game registry (e.g. arcade listing).
 * Safe to call multiple times — only loads once.
 */
export function ensureAllGamesRegistered(): Promise<void> {
  if (!_allGamesPromise) {
    _allGamesPromise = Promise.all([
      import('@/arcade-games/complement-race/index'),
      import('@/arcade-games/card-sorting'),
      import('@/arcade-games/yjs-demo'),
      import('@/arcade-games/rithmomachia'),
    ]).then((modules) => {
      const games: GameDefinition<any, any, any>[] = [
        modules[0].complementRaceGame,
        modules[1].cardSortingGame,
        modules[2].yjsDemoGame,
        modules[3].rithmomachiaGame,
      ]
      for (const game of games) {
        if (!registry.has(game.manifest.name)) {
          registerGame(game)
        }
      }
    })
  }
  return _allGamesPromise
}