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 | 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 | /**
* 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
|