All files / web/src/arcade-games/matching variant-server.ts

70.65% Statements 65/92
100% Branches 1/1
14.28% Functions 1/7
70.65% Lines 65/92

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 931x 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  
/**
 * Abacus Matching Game — Server-Safe Variant Definition
 *
 * Contains only the data/logic fields needed by the validator.
 * No React component imports — safe to use in server.js / socket-server.
 *
 * The full variant (with React components) is in variant.ts,
 * which extends this with CardFront, SetupContent, etc.
 */
 
import type { MatchingPairsVariant } from '@/lib/arcade/matching-pairs-framework'
import { generateGameCards, getGridConfiguration } from './utils/cardGeneration'
import { validateMatch } from './utils/matchValidation'
import { AbacusCardSchema } from './types'
import type { AbacusCard, AbacusConfig } from './types'
 
/**
 * Server-safe subset of the variant — everything the validator needs.
 * React component fields (CardFront, SetupContent) are omitted.
 */
export const abacusVariantServer: Pick<
  MatchingPairsVariant<AbacusCard, AbacusConfig>,
  | 'gameName'
  | 'defaultConfig'
  | 'cardSchema'
  | 'generateCards'
  | 'validateMatch'
  | 'validateConfigField'
  | 'getTotalPairs'
  | 'getOriginalConfig'
  | 'hasConfigChangedFrom'
  | 'practiceBreakDefaults'
  | 'getGridConfig'
  | 'canFlipCard'
> = {
  gameName: 'matching',
 
  defaultConfig: {
    gameType: 'abacus-numeral',
    difficulty: 6,
    turnTimer: 30,
  },
 
  cardSchema: AbacusCardSchema,
 
  generateCards: (config) => generateGameCards(config.gameType, config.difficulty),
 
  validateMatch: (card1, card2) => validateMatch(card1, card2),
 
  validateConfigField: (field, value) => {
    switch (field) {
      case 'gameType':
        if (!['abacus-numeral', 'complement-pairs'].includes(value)) {
          return 'Invalid game type'
        }
        return null
      case 'difficulty':
        if (![6, 8, 12, 15].includes(value)) {
          return 'Invalid difficulty'
        }
        return null
      case 'turnTimer':
        if (typeof value !== 'number' || value < 5 || value > 300) {
          return 'Turn timer must be between 5 and 300'
        }
        return null
      default:
        return null
    }
  },
 
  getTotalPairs: (config) => config.difficulty,
 
  getOriginalConfig: (config) => ({
    gameType: config.gameType,
    difficulty: config.difficulty,
    turnTimer: config.turnTimer,
  }),
 
  hasConfigChangedFrom: (current, original) =>
    current.gameType !== original.gameType ||
    current.difficulty !== original.difficulty ||
    current.turnTimer !== original.turnTimer,
 
  getGridConfig: (config) => getGridConfiguration(config.difficulty),
 
  practiceBreakDefaults: {
    gameType: 'abacus-numeral',
    difficulty: 6,
    turnTimer: 30,
  },
}