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

15.12% Statements 18/119
100% Branches 0/0
0% Functions 0/3
15.12% Lines 18/119

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 1201x 1x 1x 1x 1x 1x 1x                                                                             1x 1x 1x 1x 1x 1x                                                                       1x 1x 1x 1x 1x                                                          
import type { MusicCard, MusicMatchValidationResult } from '../types'
 
/**
 * Validate staff-to-name match:
 * One card must be staff-note, the other must be note-name, and they must share the same MIDI note.
 */
export function validateStaffToNameMatch(
  card1: MusicCard,
  card2: MusicCard
): MusicMatchValidationResult {
  // Cards must be different types
  if (card1.type === card2.type) {
    return {
      isValid: false,
      reason: 'Must match a staff note with a note name',
      type: 'invalid',
    }
  }

  // One must be staff-note, one must be note-name
  const hasStaff = card1.type === 'staff-note' || card2.type === 'staff-note'
  const hasName = card1.type === 'note-name' || card2.type === 'note-name'

  if (!hasStaff || !hasName) {
    return {
      isValid: false,
      reason: 'Must match a staff note with a note name',
      type: 'invalid',
    }
  }

  // MIDI notes must match
  if (card1.midiNote !== card2.midiNote) {
    return {
      isValid: false,
      reason: 'Notes do not match',
      type: 'invalid',
    }
  }

  return {
    isValid: true,
    type: 'staff-to-name',
  }
}
 
/**
 * Validate treble-to-bass match:
 * Both cards must be staff-note, different clefs, same MIDI note.
 */
export function validateTrebleToBassMatch(
  card1: MusicCard,
  card2: MusicCard
): MusicMatchValidationResult {
  // Both must be staff-note
  if (card1.type !== 'staff-note' || card2.type !== 'staff-note') {
    return {
      isValid: false,
      reason: 'Both cards must be staff notes',
      type: 'invalid',
    }
  }

  // Must be different clefs
  if (card1.clef === card2.clef) {
    return {
      isValid: false,
      reason: 'Cards must be from different clefs',
      type: 'invalid',
    }
  }

  // MIDI notes must match
  if (card1.midiNote !== card2.midiNote) {
    return {
      isValid: false,
      reason: 'Notes do not match',
      type: 'invalid',
    }
  }

  return {
    isValid: true,
    type: 'treble-to-bass',
  }
}
 
/**
 * Main validation function — determines which validation to use based on card types.
 */
export function validateMatch(card1: MusicCard, card2: MusicCard): MusicMatchValidationResult {
  // Cannot match the same card with itself
  if (card1.id === card2.id) {
    return {
      isValid: false,
      reason: 'Cannot match card with itself',
      type: 'invalid',
    }
  }

  // Cannot match already matched cards
  if (card1.matched || card2.matched) {
    return {
      isValid: false,
      reason: 'Cannot match already matched cards',
      type: 'invalid',
    }
  }

  // Determine match type from card types
  const hasNoteName = card1.type === 'note-name' || card2.type === 'note-name'

  if (hasNoteName) {
    return validateStaffToNameMatch(card1, card2)
  }

  // Both are staff-note: treble-to-bass mode
  return validateTrebleToBassMatch(card1, card2)
}