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

75.4% Statements 92/122
100% Branches 0/0
0% Functions 0/1
75.4% Lines 92/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 1231x 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  
/**
 * 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