All files / web/src/arcade-games/constant-explorer Validator.ts

91.33% Statements 116/127
15.38% Branches 2/13
40% Functions 2/5
91.33% Lines 116/127

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 1281x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x     1x 1x             1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
import {
  CONSTANT_IDS,
  EXPLORATION_DISPLAY,
} from '@/components/toys/number-line/talkToNumber/explorationRegistry'
import type { GameResultsReport } from '@/lib/arcade/game-sdk/types'
import { finalizeSongContext, songDetail, songMoment } from '@/lib/arcade/song-context'
import type { GameValidator, ValidationResult } from '@/lib/arcade/validation/types'
import type { ConstantExplorerMove, ConstantExplorerState } from './types'
 
function buildConstantExplorerSongContext(
  symbol: string,
  name: string,
  value: number | undefined
): NonNullable<GameResultsReport['songContext']> {
  return finalizeSongContext({
    summary: `Explored the constant ${symbol} (${name})`,
    details: [
      songDetail('Signature constant', `${symbol} ${name}`),
      value !== undefined ? songDetail('Approximate value', value.toFixed(6)) : undefined,
    ],
    dramaticMoments: [
      songMoment('Opening beat', `the discovery detour opened on ${symbol}`),
      songMoment('Signature items', `${symbol} and ${name}`),
    ],
    strategyNotes: ['Discovery break focused on noticing a famous number pattern'],
    outcome: `explored ${symbol}`,
  })
}
 
/**
 * Validator for constant-explorer.
 *
 * Constant explorations are passive (no game moves). The validator exists
 * to satisfy the game registry interface and to build results reports
 * for scoreboard persistence (needed for "balance" selection mode).
 */
class ConstantExplorerValidator
  implements GameValidator<ConstantExplorerState, ConstantExplorerMove>
{
  validateMove(): ValidationResult {
    return { valid: false, error: 'Constant explorer has no moves' }
  }
 
  isGameComplete(state: ConstantExplorerState): boolean {
    return state.phase === 'complete'
  }
 
  getInitialState(config: unknown): ConstantExplorerState {
    const c = config as { constantId?: string } | undefined
    return {
      constantId: c?.constantId ?? null,
      phase: 'idle',
    }
  }
 
  getResultsReport(state: ConstantExplorerState, _config: unknown): GameResultsReport {
    const constantId = state.constantId ?? 'unknown'
    const display = CONSTANT_IDS.has(constantId) ? EXPLORATION_DISPLAY[constantId] : null
    const symbol = display?.symbol ?? constantId
    const name = display?.name ?? constantId
    const value = display?.value
 
    const now = Date.now()
    const durationMs = state.startedAt ? now - state.startedAt : 0
 
    return {
      gameName: 'constant-explorer',
      gameDisplayName: 'Math Discovery',
      gameIcon: '\uD83D\uDD2D',
 
      durationMs,
      completedNormally: true,
      startedAt: state.startedAt ?? now,
      endedAt: now,
 
      gameMode: 'single-player',
      playerCount: 1,
      playerResults: [
        {
          playerId: state.playerId ?? 'unknown',
          playerName: state.playerName ?? 'Explorer',
          playerEmoji: '\uD83D\uDD2D',
          userId: state.playerId ?? 'unknown',
          score: 1, // Binary: explored or not
          rank: 1,
        },
      ],
 
      itemsCompleted: 1,
      itemsTotal: 1,
      completionPercent: 100,
 
      leaderboardEntry: {
        normalizedScore: 100,
        category: 'discovery',
        // Encode constantId in difficulty so we can query per-constant play counts
        difficulty: constantId,
      },
 
      customStats: [
        {
          label: 'Constant',
          value: `${symbol} ${name}`,
          icon: '\uD83D\uDD2D',
          highlight: true,
        },
        ...(value !== undefined
          ? [
              {
                label: 'Value',
                value: `${value.toFixed(6)}...`,
                icon: '\uD83D\uDCCA',
              },
            ]
          : []),
      ],
 
      headline: `Explored ${symbol}!`,
      subheadline: name,
      resultTheme: 'success',
      celebrationType: 'stars',
      songContext: buildConstantExplorerSongContext(symbol, name, value),
    }
  }
}
 
export const constantExplorerValidator = new ConstantExplorerValidator()