fix: update matching game for UUID player system
- GamePhase: Convert Map to array, map numeric IDs to players - GameCard: Use active players array for emoji lookup - PlayerStatusBar: Fix Map.filter, fix duplicate className, fix type comparison - ResultsPhase: Convert to array-based player lookup, add numericId mapping - MemoryPairsContext: Create compatibility layer for numeric player IDs - EmojiPicker: Update import path for PLAYER_EMOJIS - EmojiPicker test: Update import path Maintains backward compatibility with internal numeric player tracking while using UUID-based players from GameModeContext
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useMemo } from 'react'
|
||||
import { PLAYER_EMOJIS } from '../../../../contexts/UserProfileContext'
|
||||
import { PLAYER_EMOJIS } from '../../../../constants/playerEmojis'
|
||||
import { css } from '../../../../../styled-system/css'
|
||||
import emojiData from 'emojibase-data/en/data.json'
|
||||
|
||||
@@ -24,7 +24,7 @@ interface EmojiPickerProps {
|
||||
currentEmoji: string
|
||||
onEmojiSelect: (emoji: string) => void
|
||||
onClose: () => void
|
||||
playerNumber: 1 | 2 | 3 | 4
|
||||
playerNumber: number
|
||||
}
|
||||
|
||||
// Emoji group categories from emojibase (matching Unicode CLDR group IDs)
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
|
||||
import { AbacusReact } from '@soroban/abacus-react'
|
||||
import { useAbacusConfig } from '@soroban/abacus-react'
|
||||
import { useUserProfile } from '../../../../contexts/UserProfileContext'
|
||||
import { useGameMode } from '../../../../contexts/GameModeContext'
|
||||
import type { GameCardProps } from '../context/types'
|
||||
import { css } from '../../../../../styled-system/css'
|
||||
|
||||
export function GameCard({ card, isFlipped, isMatched, onClick, disabled = false }: GameCardProps) {
|
||||
const appConfig = useAbacusConfig()
|
||||
const { profile } = useUserProfile()
|
||||
const { players: playerMap, activePlayers: activePlayerIds } = useGameMode()
|
||||
|
||||
// Get active players array for mapping numeric IDs to actual players
|
||||
const activePlayers = Array.from(activePlayerIds)
|
||||
.map(id => playerMap.get(id))
|
||||
.filter((p): p is NonNullable<typeof p> => p !== undefined)
|
||||
|
||||
const cardBackStyles = css({
|
||||
position: 'absolute',
|
||||
@@ -71,11 +76,9 @@ export function GameCard({ card, isFlipped, isMatched, onClick, disabled = false
|
||||
|
||||
const getCardBackIcon = () => {
|
||||
if (isMatched) {
|
||||
// Show player emoji for matched cards in two-player mode
|
||||
if (card.matchedBy === 1) {
|
||||
return profile.player1Emoji
|
||||
} else if (card.matchedBy === 2) {
|
||||
return profile.player2Emoji
|
||||
// Show player emoji for matched cards in multiplayer mode
|
||||
if (card.matchedBy && card.matchedBy <= activePlayers.length) {
|
||||
return activePlayers[card.matchedBy - 1]?.emoji || '✓'
|
||||
}
|
||||
return '✓' // Default checkmark for single player
|
||||
}
|
||||
@@ -227,7 +230,9 @@ export function GameCard({ card, isFlipped, isMatched, onClick, disabled = false
|
||||
animation: 'emojiBlast 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55) 0.4s both',
|
||||
filter: 'drop-shadow(0 0 8px rgba(255,255,255,0.8))'
|
||||
})}>
|
||||
{card.matchedBy === 1 ? profile.player1Emoji : profile.player2Emoji}
|
||||
{card.matchedBy && card.matchedBy <= activePlayers.length
|
||||
? activePlayers[card.matchedBy - 1]?.emoji || '✓'
|
||||
: '✓'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,11 +9,17 @@ import { pluralizeWord } from '../../../../utils/pluralization'
|
||||
|
||||
export function GamePhase() {
|
||||
const { state, resetGame, activePlayers } = useMemoryPairs()
|
||||
const { players } = useGameMode()
|
||||
const { players: playerMap, activePlayers: activePlayerIds } = useGameMode()
|
||||
|
||||
// Get the current player from the arena champions
|
||||
const currentPlayerData = players.find(p => p.id === state.currentPlayer)
|
||||
const activePlayerData = players.filter(p => activePlayers.includes(p.id))
|
||||
// Convert Map to array and create mapping from numeric index to player
|
||||
const playersArray = Array.from(playerMap.values())
|
||||
const activePlayersArray = Array.from(activePlayerIds)
|
||||
.map(id => playerMap.get(id))
|
||||
.filter((p): p is NonNullable<typeof p> => p !== undefined)
|
||||
|
||||
// Map numeric player ID (1, 2, 3...) to actual player data
|
||||
const currentPlayerData = activePlayersArray[state.currentPlayer - 1]
|
||||
const activePlayerData = activePlayersArray
|
||||
|
||||
return (
|
||||
<div className={css({
|
||||
|
||||
@@ -11,24 +11,23 @@ interface PlayerStatusBarProps {
|
||||
}
|
||||
|
||||
export function PlayerStatusBar({ className }: PlayerStatusBarProps) {
|
||||
const { players } = useGameMode()
|
||||
const { profile } = useUserProfile()
|
||||
const { players: playerMap, activePlayers: activePlayerIds } = useGameMode()
|
||||
const { state } = useMemoryPairs()
|
||||
|
||||
// Get active players with their profile data
|
||||
const activePlayers = players
|
||||
.filter(player => player.isActive)
|
||||
.map(player => ({
|
||||
...player,
|
||||
displayName: player.id === 1 ? profile.player1Name :
|
||||
player.id === 2 ? profile.player2Name :
|
||||
player.name,
|
||||
displayEmoji: player.id === 1 ? profile.player1Emoji :
|
||||
player.id === 2 ? profile.player2Emoji :
|
||||
player.emoji,
|
||||
score: state.scores[player.id] || 0,
|
||||
consecutiveMatches: state.consecutiveMatches?.[player.id] || 0
|
||||
}))
|
||||
// Get active players array
|
||||
const activePlayersData = Array.from(activePlayerIds)
|
||||
.map(id => playerMap.get(id))
|
||||
.filter((p): p is NonNullable<typeof p> => p !== undefined)
|
||||
|
||||
// Map active players to display data with scores
|
||||
// State uses numeric player IDs (1, 2, 3...), so we map by index
|
||||
const activePlayers = activePlayersData.map((player, index) => ({
|
||||
...player,
|
||||
displayName: player.name,
|
||||
displayEmoji: player.emoji,
|
||||
score: state.scores[index + 1] || 0,
|
||||
consecutiveMatches: state.consecutiveMatches?.[index + 1] || 0
|
||||
}))
|
||||
|
||||
// Get celebration level based on consecutive matches
|
||||
const getCelebrationLevel = (consecutiveMatches: number) => {
|
||||
@@ -41,7 +40,7 @@ export function PlayerStatusBar({ className }: PlayerStatusBarProps) {
|
||||
if (activePlayers.length <= 1) {
|
||||
// Simple single player indicator
|
||||
return (
|
||||
<div className={css({
|
||||
<div className={`${css({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
@@ -52,8 +51,7 @@ export function PlayerStatusBar({ className }: PlayerStatusBarProps) {
|
||||
borderColor: 'blue.200',
|
||||
mb: { base: '2', md: '3' },
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
})}
|
||||
className={className}>
|
||||
})} ${className || ''}`}>
|
||||
<div className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@@ -85,15 +83,14 @@ export function PlayerStatusBar({ className }: PlayerStatusBarProps) {
|
||||
|
||||
// For multiplayer, show competitive status bar
|
||||
return (
|
||||
<div className={css({
|
||||
<div className={`${css({
|
||||
background: 'linear-gradient(135deg, #f8fafc, #e2e8f0)',
|
||||
rounded: 'xl',
|
||||
p: { base: '2', md: '3' },
|
||||
border: '2px solid',
|
||||
borderColor: 'gray.200',
|
||||
mb: { base: '3', md: '4' }
|
||||
})}
|
||||
className={className}>
|
||||
})} ${className || ''}`}>
|
||||
<div className={css({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: activePlayers.length <= 2
|
||||
@@ -105,7 +102,7 @@ export function PlayerStatusBar({ className }: PlayerStatusBarProps) {
|
||||
alignItems: 'center'
|
||||
})}>
|
||||
{activePlayers.map((player, index) => {
|
||||
const isCurrentPlayer = player.id === state.currentPlayer
|
||||
const isCurrentPlayer = (index + 1) === state.currentPlayer
|
||||
const isLeading = player.score === Math.max(...activePlayers.map(p => p.score)) && player.score > 0
|
||||
const celebrationLevel = getCelebrationLevel(player.consecutiveMatches)
|
||||
|
||||
|
||||
@@ -9,21 +9,18 @@ import { css } from '../../../../../styled-system/css'
|
||||
|
||||
export function ResultsPhase() {
|
||||
const router = useRouter()
|
||||
const { state, resetGame, activePlayers } = useMemoryPairs()
|
||||
const { players } = useGameMode()
|
||||
const { profile } = useUserProfile()
|
||||
const { state, resetGame, activePlayers, gameMode } = useMemoryPairs()
|
||||
const { players: playerMap, activePlayers: activePlayerIds } = useGameMode()
|
||||
|
||||
// Get active player data with profile information
|
||||
const activePlayerData = players
|
||||
.filter(p => activePlayers.includes(p.id))
|
||||
.map(player => ({
|
||||
// Get active player data array
|
||||
const activePlayerData = Array.from(activePlayerIds)
|
||||
.map(id => playerMap.get(id))
|
||||
.filter((p): p is NonNullable<typeof p> => p !== undefined)
|
||||
.map((player, index) => ({
|
||||
...player,
|
||||
displayName: player.id === 1 ? profile.player1Name :
|
||||
player.id === 2 ? profile.player2Name :
|
||||
player.name,
|
||||
displayEmoji: player.id === 1 ? profile.player1Emoji :
|
||||
player.id === 2 ? profile.player2Emoji :
|
||||
player.emoji
|
||||
displayName: player.name,
|
||||
displayEmoji: player.emoji,
|
||||
numericId: index + 1 // For compatibility with state.scores
|
||||
}))
|
||||
|
||||
const gameTime = state.gameEndTime && state.gameStartTime
|
||||
@@ -31,7 +28,7 @@ export function ResultsPhase() {
|
||||
: 0
|
||||
|
||||
const analysis = getPerformanceAnalysis(state)
|
||||
const multiplayerResult = state.gameMode === 'multiplayer' ? getMultiplayerWinner(state, activePlayers) : null
|
||||
const multiplayerResult = gameMode === 'multiplayer' ? getMultiplayerWinner(state, activePlayers) : null
|
||||
|
||||
return (
|
||||
<div className={css({
|
||||
@@ -52,7 +49,7 @@ export function ResultsPhase() {
|
||||
🎉 Game Complete! 🎉
|
||||
</h2>
|
||||
|
||||
{state.gameMode === 'single' ? (
|
||||
{gameMode === 'single' ? (
|
||||
<p className={css({
|
||||
fontSize: '24px',
|
||||
color: 'gray.700',
|
||||
@@ -76,7 +73,7 @@ export function ResultsPhase() {
|
||||
color: 'blue.600',
|
||||
fontWeight: 'bold'
|
||||
})}>
|
||||
🏆 {activePlayerData.find(p => p.id === multiplayerResult.winners[0])?.displayName || `Player ${multiplayerResult.winners[0]}`} Wins!
|
||||
🏆 {activePlayerData.find(p => p.numericId === multiplayerResult.winners[0])?.displayName || `Player ${multiplayerResult.winners[0]}`} Wins!
|
||||
</p>
|
||||
) : (
|
||||
<p className={css({
|
||||
@@ -180,7 +177,7 @@ export function ResultsPhase() {
|
||||
</div>
|
||||
|
||||
{/* Multiplayer Scores */}
|
||||
{state.gameMode === 'multiplayer' && multiplayerResult && (
|
||||
{gameMode === 'multiplayer' && multiplayerResult && (
|
||||
<div className={css({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@@ -189,8 +186,8 @@ export function ResultsPhase() {
|
||||
flexWrap: 'wrap'
|
||||
})}>
|
||||
{activePlayerData.map((player) => {
|
||||
const score = multiplayerResult.scores[player.id] || 0
|
||||
const isWinner = multiplayerResult.winners.includes(player.id)
|
||||
const score = multiplayerResult.scores[player.numericId] || 0
|
||||
const isWinner = multiplayerResult.winners.includes(player.numericId)
|
||||
|
||||
return (
|
||||
<div key={player.id} className={css({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { describe, test, expect, beforeEach, vi } from 'vitest'
|
||||
import { EmojiPicker } from '../EmojiPicker'
|
||||
import { PLAYER_EMOJIS } from '../../../../../contexts/UserProfileContext'
|
||||
import { PLAYER_EMOJIS } from '../../../../../constants/playerEmojis'
|
||||
|
||||
// Mock the emoji keywords function for testing
|
||||
vi.mock('emojibase-data/en/data.json', () => ({
|
||||
|
||||
@@ -251,10 +251,10 @@ const MemoryPairsContext = createContext<MemoryPairsContextValue | null>(null)
|
||||
// Provider component
|
||||
export function MemoryPairsProvider({ children }: { children: ReactNode }) {
|
||||
const [state, dispatch] = useReducer(memoryPairsReducer, initialState)
|
||||
const { activePlayerCount, players } = useGameMode()
|
||||
const { activePlayerCount, activePlayers: activePlayerIds } = useGameMode()
|
||||
|
||||
// Get active players from GameMode context
|
||||
const activePlayers = players.filter(player => player.isActive).map(player => player.id)
|
||||
// Get active player IDs as numbers (convert from string IDs for now to maintain compatibility)
|
||||
const activePlayers = Array.from(activePlayerIds).map((id, index) => index + 1)
|
||||
|
||||
// Derive game mode from active player count
|
||||
const gameMode = activePlayerCount > 1 ? 'multiplayer' : 'single'
|
||||
|
||||
Reference in New Issue
Block a user