All files / web/src/arcade-games/music-matching index.ts

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

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                                                                                                                                                                                                                                                     
/**
 * Music Note Matching Game Definition
 *
 * A matching-pairs game where players match staff notation with note names
 * or match the same pitch across treble and bass clefs.
 */

import { getGameTheme } from '@/lib/arcade/game-themes'
import type { GameManifest } from '@/lib/arcade/manifest-schema'
import { defineMatchingPairsGame } from '@/lib/arcade/matching-pairs-framework'
import { musicVariant } from './variant'
import type { MusicConfig } from './types'

const manifest: GameManifest = {
  name: 'music-matching',
  displayName: 'Music Note Match',
  shortName: 'Note Match',
  icon: '🎵',
  description: 'Match notes on the staff with their names',
  longDescription:
    'Learn to read music by matching staff notation with note names. ' +
    'Choose between treble clef, bass clef, or both. ' +
    'Challenge yourself with accidentals and ledger lines at higher difficulties!',
  maxPlayers: 4,
  difficulty: 'Beginner',
  chips: ['🎵 Music', '📖 Sight-Reading', '🧠 Memory'],
  ...getGameTheme('purple'),
  available: true,
  practiceBreakReady: true,
  practiceBreakConfig: {
    suggestedConfig: {
      gameType: 'staff-to-name',
      clef: 'treble',
      difficulty: 6,
      turnTimer: 30,
    },
    lockedFields: ['turnTimer'],
    minDurationMinutes: 2,
    maxDurationMinutes: 8,
    difficultyPresets: {
      easy: { difficulty: 6, gameType: 'staff-to-name', clef: 'treble' },
      medium: { difficulty: 8, gameType: 'staff-to-name', clef: 'both' },
      hard: { difficulty: 12, gameType: 'treble-to-bass', clef: 'both' },
    },
    fieldConfig: {
      gameType: {
        label: 'Match Type',
        type: 'select',
        options: [
          { value: 'staff-to-name', label: 'Staff ↔ Name' },
          { value: 'treble-to-bass', label: 'Treble ↔ Bass' },
        ],
      },
      clef: {
        label: 'Clef',
        type: 'select',
        options: [
          { value: 'treble', label: 'Treble' },
          { value: 'bass', label: 'Bass' },
          { value: 'both', label: 'Both' },
        ],
      },
      difficulty: {
        label: 'Pairs',
        type: 'select',
        options: [
          { value: 6, label: '6' },
          { value: 8, label: '8' },
          { value: 12, label: '12' },
          { value: 15, label: '15' },
        ],
      },
    },
  },
  resultsConfig: {
    supportsResults: true,
    resultsDisplayDurationMs: 5000,
    scoreboardCategory: 'memory',
  },
  coPlay: { mode: 'join-at-start' },
}

function validateMusicConfig(config: unknown): config is MusicConfig {
  if (typeof config !== 'object' || config === null) {
    return false
  }

  const c = config as Record<string, unknown>

  if (!('gameType' in c) || !['staff-to-name', 'treble-to-bass'].includes(c.gameType as string)) {
    return false
  }

  if (!('clef' in c) || !['treble', 'bass', 'both'].includes(c.clef as string)) {
    return false
  }

  if (!('difficulty' in c) || ![6, 8, 12, 15].includes(c.difficulty as number)) {
    return false
  }

  if (
    !('turnTimer' in c) ||
    typeof c.turnTimer !== 'number' ||
    c.turnTimer < 5 ||
    c.turnTimer > 300
  ) {
    return false
  }

  return true
}

const { game, useMatchingPairs, validator } = defineMatchingPairsGame({
  manifest,
  variant: musicVariant,
  validateConfig: validateMusicConfig,
})

export const musicMatchingGame = game
export { useMatchingPairs }
export const musicMatchingValidator = validator