feat(card-sorting): add CardPosition type and position syncing
Add viewport-relative position tracking for card-sorting game: - Add CardPosition interface (x, y as % of viewport, rotation, zIndex) - Add UPDATE_CARD_POSITIONS move type - Add validateUpdateCardPositions validator method - Add cardPositions array to CardSortingState This enables real-time card position syncing across browser windows using percentage-based coordinates that work across different viewport sizes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5d6c800cee
commit
656f5a7838
|
|
@ -3,7 +3,7 @@ import type {
|
|||
ValidationContext,
|
||||
ValidationResult,
|
||||
} from '@/lib/arcade/validation/types'
|
||||
import type { CardSortingConfig, CardSortingMove, CardSortingState } from './types'
|
||||
import type { CardSortingConfig, CardSortingMove, CardSortingState, CardPosition } from './types'
|
||||
import { calculateScore } from './utils/scoringAlgorithm'
|
||||
import { placeCardAtPosition, insertCardAtPosition, removeCardAtPosition } from './utils/validation'
|
||||
|
||||
|
|
@ -32,6 +32,8 @@ export class CardSortingValidator implements GameValidator<CardSortingState, Car
|
|||
return this.validateSetConfig(state, move.data.field, move.data.value)
|
||||
case 'RESUME_GAME':
|
||||
return this.validateResumeGame(state)
|
||||
case 'UPDATE_CARD_POSITIONS':
|
||||
return this.validateUpdateCardPositions(state, move.data.positions)
|
||||
default:
|
||||
return {
|
||||
valid: false,
|
||||
|
|
@ -81,6 +83,7 @@ export class CardSortingValidator implements GameValidator<CardSortingState, Car
|
|||
correctOrder: correctOrder as typeof state.correctOrder,
|
||||
availableCards: selectedCards as typeof state.availableCards,
|
||||
placedCards: new Array(state.cardCount).fill(null),
|
||||
cardPositions: [], // Will be set by first position update
|
||||
numbersRevealed: false,
|
||||
scoreBreakdown: null,
|
||||
},
|
||||
|
|
@ -444,6 +447,48 @@ export class CardSortingValidator implements GameValidator<CardSortingState, Car
|
|||
}
|
||||
}
|
||||
|
||||
private validateUpdateCardPositions(
|
||||
state: CardSortingState,
|
||||
positions: CardPosition[]
|
||||
): ValidationResult {
|
||||
// Must be in playing phase
|
||||
if (state.gamePhase !== 'playing') {
|
||||
return { valid: false, error: 'Can only update positions during playing phase' }
|
||||
}
|
||||
|
||||
// Validate positions array
|
||||
if (!Array.isArray(positions)) {
|
||||
return { valid: false, error: 'positions must be an array' }
|
||||
}
|
||||
|
||||
// Basic validation of position values
|
||||
for (const pos of positions) {
|
||||
if (typeof pos.x !== 'number' || pos.x < 0 || pos.x > 100) {
|
||||
return { valid: false, error: 'x must be between 0 and 100' }
|
||||
}
|
||||
if (typeof pos.y !== 'number' || pos.y < 0 || pos.y > 100) {
|
||||
return { valid: false, error: 'y must be between 0 and 100' }
|
||||
}
|
||||
if (typeof pos.rotation !== 'number') {
|
||||
return { valid: false, error: 'rotation must be a number' }
|
||||
}
|
||||
if (typeof pos.zIndex !== 'number') {
|
||||
return { valid: false, error: 'zIndex must be a number' }
|
||||
}
|
||||
if (typeof pos.cardId !== 'string') {
|
||||
return { valid: false, error: 'cardId must be a string' }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
newState: {
|
||||
...state,
|
||||
cardPositions: positions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
isGameComplete(state: CardSortingState): boolean {
|
||||
return state.gamePhase === 'results'
|
||||
}
|
||||
|
|
@ -467,6 +512,7 @@ export class CardSortingValidator implements GameValidator<CardSortingState, Car
|
|||
correctOrder: [],
|
||||
availableCards: [],
|
||||
placedCards: new Array(config.cardCount).fill(null),
|
||||
cardPositions: [],
|
||||
selectedCardId: null,
|
||||
numbersRevealed: false,
|
||||
scoreBreakdown: null,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@ export interface SortingCard {
|
|||
svgContent: string // Serialized AbacusReact SVG
|
||||
}
|
||||
|
||||
export interface CardPosition {
|
||||
cardId: string
|
||||
x: number // % of viewport width (0-100)
|
||||
y: number // % of viewport height (0-100)
|
||||
rotation: number // degrees (-15 to 15)
|
||||
zIndex: number
|
||||
}
|
||||
|
||||
export interface PlacedCard {
|
||||
card: SortingCard // The card data
|
||||
position: number // Which slot it's in (0-indexed)
|
||||
|
|
@ -74,6 +82,7 @@ export interface CardSortingState extends GameState {
|
|||
correctOrder: SortingCard[] // Sorted by number (answer key)
|
||||
availableCards: SortingCard[] // Cards not yet placed
|
||||
placedCards: (SortingCard | null)[] // Array of N slots (null = empty)
|
||||
cardPositions: CardPosition[] // Viewport-relative positions for all cards
|
||||
|
||||
// UI state (client-only, not in server state)
|
||||
selectedCardId: string | null // Currently selected card
|
||||
|
|
@ -178,6 +187,15 @@ export type CardSortingMove =
|
|||
timestamp: number
|
||||
data: Record<string, never>
|
||||
}
|
||||
| {
|
||||
type: 'UPDATE_CARD_POSITIONS'
|
||||
playerId: string
|
||||
userId: string
|
||||
timestamp: number
|
||||
data: {
|
||||
positions: CardPosition[]
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Component Props
|
||||
|
|
|
|||
Loading…
Reference in New Issue