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 |