feat: implement revolutionary drag-and-drop champion arena interface

🏟️ MAJOR FEATURE: Transform games page with innovative Champion Arena
- Create ChampionArena component with drag-and-drop avatar selection
- Combine "choose your champion" + player count in one intuitive interface
- Visual arena where users drag avatars to set game mode (solo/battle/tournament)
- Replace modal selection with always-visible arena experience
- Add GameModeContext for centralized player configuration management
- Enhanced animations: arena entry, champion ready, floating effects
- Real-time mode indicators and champion status tracking
- Click to remove champions from arena with smooth animations

 INNOVATION: Drag avatars into arena = instant game mode selection
- 1 champion = Solo Mode
- 2 champions = Battle Mode
- 3+ champions = Tournament Mode

🎮 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-09-27 14:16:30 -05:00
parent 03f5056902
commit dbf61c4b2d
4 changed files with 714 additions and 243 deletions

View File

@@ -5,28 +5,18 @@ import Link from 'next/link'
import { css } from '../../../styled-system/css'
import { grid } from '../../../styled-system/patterns'
import { useUserProfile } from '../../contexts/UserProfileContext'
import { useGameMode } from '../../contexts/GameModeContext'
import { ChampionArena } from '../../components/ChampionArena'
export default function GamesPage() {
const { profile } = useUserProfile()
const [showCharacterSelection, setShowCharacterSelection] = useState<string | null>(null)
const [selectedGameMode, setSelectedGameMode] = useState<'single' | 'two-player'>('single')
const { gameMode, getActivePlayer } = useGameMode()
const handleGameClick = (gameType: string) => {
setShowCharacterSelection(gameType)
}
const handleCharacterSelectionClose = () => {
setShowCharacterSelection(null)
}
const handleStartGame = (character: 1 | 2) => {
console.log(`Starting ${showCharacterSelection} with character ${character}`)
setShowCharacterSelection(null)
// Navigate directly to games - let them handle their own mode selection
if (showCharacterSelection === 'memory-lightning') {
// Navigate directly to games using the centralized game mode
if (gameType === 'memory-lightning') {
window.location.href = '/games/memory-quiz'
} else if (showCharacterSelection === 'battle-arena') {
} else if (gameType === 'battle-arena') {
window.location.href = '/games/matching'
}
}
@@ -183,6 +173,13 @@ export default function GamesPage() {
</div>
</div>
{/* Champion Arena - Drag & Drop Interface */}
<div className={css({
mb: '16'
})}>
<ChampionArena />
</div>
{/* Character Showcase Header */}
<div className={css({
mb: '16'
@@ -203,7 +200,7 @@ export default function GamesPage() {
color: 'gray.600',
fontSize: 'lg'
})}>
Choose your character and dominate the leaderboards!
Track your progress and achievements!
</p>
</div>
@@ -1877,230 +1874,6 @@ export default function GamesPage() {
</Link>
</div>
{/* Character Selection Modal */}
{showCharacterSelection && (
<div className={css({
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0, 0, 0, 0.6)',
backdropFilter: 'blur(8px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 50,
animation: 'fadeIn 0.3s ease-out'
})}>
<div className={css({
background: 'white',
rounded: '3xl',
p: '8',
maxW: '2xl',
w: '95%',
maxH: '90vh',
overflowY: 'auto',
position: 'relative',
boxShadow: '0 25px 50px rgba(0, 0, 0, 0.2)',
animation: 'slideInUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)'
})}>
{/* Close Button */}
<button
onClick={handleCharacterSelectionClose}
className={css({
position: 'absolute',
top: '4',
right: '4',
w: '10',
h: '10',
rounded: 'full',
background: 'gray.100',
border: 'none',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 'xl',
color: 'gray.500',
transition: 'all 0.2s ease',
_hover: {
background: 'gray.200',
color: 'gray.700'
}
})}
>
</button>
{/* Modal Header */}
<div className={css({
textAlign: 'center',
mb: '8'
})}>
<h2 className={css({
fontSize: '3xl',
fontWeight: 'bold',
color: 'gray.900',
mb: '2'
})}>
🎮 Choose Your Champion
</h2>
<p className={css({
color: 'gray.600',
fontSize: 'lg'
})}>
Select a character for {showCharacterSelection === 'memory-lightning' ? 'Memory Lightning ' : 'Memory Pairs 🧠'}
</p>
</div>
{/* Character Selection */}
<div className={css({
mb: '8'
})}>
<h3 className={css({
fontSize: 'xl',
fontWeight: 'semibold',
color: 'gray.800',
mb: '4',
textAlign: 'center'
})}>
🌟 Choose Your Champion
</h3>
<div className={css({
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '6'
})}>
{/* Player 1 Character */}
<div
onClick={() => handleStartGame(1)}
className={css({
background: 'linear-gradient(135deg, #dbeafe, #bfdbfe)',
border: '3px solid',
borderColor: 'blue.300',
rounded: '2xl',
p: '6',
textAlign: 'center',
cursor: 'pointer',
transition: 'all 0.3s ease',
_hover: {
transform: 'translateY(-4px) scale(1.02)',
boxShadow: '0 20px 40px rgba(59, 130, 246, 0.2)',
borderColor: 'blue.400'
}
})}
>
<div className={css({
fontSize: '5xl',
mb: '3',
animation: 'characterBounce 0.6s ease-in-out infinite alternate'
})}>
{profile.player1Emoji}
</div>
<h4 className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'blue.800',
mb: '2'
})}>
{profile.player1Name}
</h4>
<div className={css({
fontSize: 'sm',
color: 'blue.700',
mb: '3'
})}>
Level {Math.floor(profile.gamesPlayed / 5) + 1} • {profile.totalWins} wins
</div>
<div className={css({
background: 'white',
rounded: 'lg',
p: '3',
fontSize: 'sm',
color: 'blue.800',
fontWeight: 'semibold'
})}>
🚀 Ready to dominate!
</div>
</div>
{/* Player 2 Character */}
<div
onClick={() => handleStartGame(2)}
className={css({
background: 'linear-gradient(135deg, #e9d5ff, #ddd6fe)',
border: '3px solid',
borderColor: 'purple.300',
rounded: '2xl',
p: '6',
textAlign: 'center',
cursor: 'pointer',
transition: 'all 0.3s ease',
_hover: {
transform: 'translateY(-4px) scale(1.02)',
boxShadow: '0 20px 40px rgba(139, 92, 246, 0.2)',
borderColor: 'purple.400'
}
})}
>
<div className={css({
fontSize: '5xl',
mb: '3',
animation: 'characterBounce 0.6s ease-in-out infinite alternate 0.3s'
})}>
{profile.player2Emoji}
</div>
<h4 className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'purple.800',
mb: '2'
})}>
{profile.player2Name}
</h4>
<div className={css({
fontSize: 'sm',
color: 'purple.700',
mb: '3'
})}>
Level {Math.floor(profile.gamesPlayed / 5) + 1} • {Math.floor(profile.totalWins / 2)} wins
</div>
<div className={css({
background: 'white',
rounded: 'lg',
p: '3',
fontSize: 'sm',
color: 'purple.800',
fontWeight: 'semibold'
})}>
⚡ Bring it on!
</div>
</div>
</div>
</div>
{/* Quick Info */}
<div className={css({
background: 'gray.50',
rounded: 'xl',
p: '4',
textAlign: 'center'
})}>
<div className={css({
fontSize: 'sm',
color: 'gray.700',
fontWeight: 'medium'
})}>
💡 Tip: Each character tracks their own progress and achievements!
</div>
</div>
</div>
</div>
)}
</div>
</div>
)

View File

@@ -2,6 +2,7 @@ import type { Metadata } from 'next'
import './globals.css'
import { AbacusDisplayProvider } from '@/contexts/AbacusDisplayContext'
import { UserProfileProvider } from '@/contexts/UserProfileContext'
import { GameModeProvider } from '@/contexts/GameModeContext'
import { AppNavBar } from '@/components/AppNavBar'
export const metadata: Metadata = {
@@ -19,8 +20,10 @@ export default function RootLayout({
<body>
<AbacusDisplayProvider>
<UserProfileProvider>
<AppNavBar />
{children}
<GameModeProvider>
<AppNavBar />
{children}
</GameModeProvider>
</UserProfileProvider>
</AbacusDisplayProvider>
</body>

View File

@@ -0,0 +1,544 @@
'use client'
import { useState, useRef } from 'react'
import { css } from '../../styled-system/css'
import { useUserProfile } from '../contexts/UserProfileContext'
import { useGameMode } from '../contexts/GameModeContext'
interface ChampionArenaProps {
onGameModeChange?: (mode: 'single' | 'battle' | 'tournament') => void
className?: string
}
export function ChampionArena({ onGameModeChange, className }: ChampionArenaProps) {
const { profile } = useUserProfile()
const { gameMode, players, setGameMode, updatePlayer } = useGameMode()
const [draggedPlayer, setDraggedPlayer] = useState<number | null>(null)
const [isDragOver, setIsDragOver] = useState(false)
const arenaRef = useRef<HTMLDivElement>(null)
const availablePlayers = players.filter(player => !player.isActive)
const arenaPlayers = players.filter(player => player.isActive)
const handleDragStart = (playerId: number) => {
setDraggedPlayer(playerId)
}
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault()
setIsDragOver(true)
}
const handleDragLeave = (e: React.DragEvent) => {
if (!arenaRef.current?.contains(e.relatedTarget as Node)) {
setIsDragOver(false)
}
}
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
setIsDragOver(false)
if (draggedPlayer) {
// Activate the dragged player
updatePlayer(draggedPlayer, { isActive: true })
// Determine new game mode based on active player count
const newActiveCount = arenaPlayers.length + 1
let newMode: 'single' | 'battle' | 'tournament' = 'single'
if (newActiveCount === 1) {
newMode = 'single'
} else if (newActiveCount === 2) {
newMode = 'battle'
} else {
newMode = 'tournament'
}
setGameMode(newMode)
onGameModeChange?.(newMode)
setDraggedPlayer(null)
}
}
const handleRemoveFromArena = (playerId: number) => {
updatePlayer(playerId, { isActive: false })
// Update game mode based on remaining players
const newActiveCount = arenaPlayers.length - 1
let newMode: 'single' | 'battle' | 'tournament' = 'single'
if (newActiveCount === 0) {
newMode = 'single'
// Re-activate player 1 by default
updatePlayer(1, { isActive: true })
} else if (newActiveCount === 1) {
newMode = 'single'
} else if (newActiveCount === 2) {
newMode = 'battle'
} else {
newMode = 'tournament'
}
setGameMode(newMode)
onGameModeChange?.(newMode)
}
const getPlayerEmoji = (id: number) => {
if (id === 1) return profile.player1Emoji
if (id === 2) return profile.player2Emoji
const player = players.find(p => p.id === id)
return player?.emoji || '😀'
}
const getPlayerName = (id: number) => {
if (id === 1) return profile.player1Name
if (id === 2) return profile.player2Name
const player = players.find(p => p.id === id)
return player?.name || `Player ${id}`
}
return (
<div className={css({
background: 'white',
rounded: '3xl',
p: '8',
border: '2px solid',
borderColor: 'gray.200',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.1)',
transition: 'all 0.3s ease'
}) + (className ? ` ${className}` : '')}>
{/* Header */}
<div className={css({
textAlign: 'center',
mb: '8'
})}>
<h2 className={css({
fontSize: { base: '2xl', md: '3xl' },
fontWeight: 'bold',
color: 'gray.900',
mb: '2'
})}>
🏟 Champion Arena
</h2>
<p className={css({
color: 'gray.600',
fontSize: 'lg',
mb: '4'
})}>
Drag your champions into the arena to set your game mode!
</p>
{/* Current Mode Indicator */}
<div className={css({
display: 'inline-flex',
alignItems: 'center',
gap: '2',
background: gameMode === 'single'
? 'linear-gradient(135deg, #dbeafe, #bfdbfe)'
: gameMode === 'battle'
? 'linear-gradient(135deg, #e9d5ff, #ddd6fe)'
: 'linear-gradient(135deg, #fef3c7, #fde68a)',
px: '4',
py: '2',
rounded: 'full',
border: '2px solid',
borderColor: gameMode === 'single'
? 'blue.300'
: gameMode === 'battle'
? 'purple.300'
: 'yellow.300'
})}>
<span className={css({ fontSize: 'lg' })}>
{gameMode === 'single' ? '👤' : gameMode === 'battle' ? '⚔️' : '🏆'}
</span>
<span className={css({
fontWeight: 'bold',
color: gameMode === 'single'
? 'blue.800'
: gameMode === 'battle'
? 'purple.800'
: 'yellow.800',
textTransform: 'uppercase',
fontSize: 'sm'
})}>
{gameMode === 'single' ? 'Solo Mode' : gameMode === 'battle' ? 'Battle Mode' : 'Tournament Mode'}
</span>
</div>
</div>
<div className={css({
display: 'grid',
gridTemplateColumns: { base: '1fr', lg: '1fr 1fr' },
gap: '8',
alignItems: 'start'
})}>
{/* Available Champions Roster */}
<div className={css({
order: { base: 2, lg: 1 }
})}>
<h3 className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'gray.800',
mb: '4',
textAlign: 'center'
})}>
🎯 Available Champions
</h3>
<div className={css({
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))',
gap: '4',
p: '6',
background: 'linear-gradient(135deg, #f8fafc, #f1f5f9)',
rounded: '2xl',
border: '2px dashed',
borderColor: 'gray.300',
minH: '32'
})}>
{availablePlayers.map((player) => (
<div
key={player.id}
draggable
onDragStart={() => handleDragStart(player.id)}
className={css({
background: 'white',
rounded: '2xl',
p: '4',
textAlign: 'center',
cursor: 'grab',
border: '2px solid',
borderColor: player.color,
boxShadow: '0 8px 20px rgba(0, 0, 0, 0.1)',
transition: 'all 0.3s ease',
_hover: {
transform: 'translateY(-4px) scale(1.05)',
boxShadow: '0 12px 30px rgba(0, 0, 0, 0.15)',
'& .champion-emoji': {
transform: 'scale(1.2) rotate(10deg)',
animation: 'championBounce 0.6s ease-in-out'
}
},
_active: {
cursor: 'grabbing',
transform: 'scale(0.95)'
}
})}
>
<div
className={css({
fontSize: '3xl',
mb: '2',
transition: 'all 0.3s ease'
}) + ' champion-emoji'}
>
{getPlayerEmoji(player.id)}
</div>
<div className={css({
fontSize: 'sm',
fontWeight: 'bold',
color: 'gray.800'
})}>
{getPlayerName(player.id)}
</div>
<div className={css({
fontSize: 'xs',
color: 'gray.600',
mt: '1'
})}>
Level {Math.floor((profile.gamesPlayed || 0) / 5) + 1}
</div>
</div>
))}
{availablePlayers.length === 0 && (
<div className={css({
gridColumn: '1 / -1',
textAlign: 'center',
color: 'gray.500',
fontSize: 'sm',
fontStyle: 'italic',
py: '8'
})}>
All champions are in the arena! 🎮
</div>
)}
</div>
</div>
{/* Arena Drop Zone */}
<div className={css({
order: { base: 1, lg: 2 }
})}>
<h3 className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'gray.800',
mb: '4',
textAlign: 'center'
})}>
🏟 Battle Arena
</h3>
<div
ref={arenaRef}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={css({
p: '8',
background: isDragOver
? 'linear-gradient(135deg, #dcfce7, #bbf7d0)'
: 'linear-gradient(135deg, #fef3c7, #fde68a)',
rounded: '3xl',
border: '3px dashed',
borderColor: isDragOver ? 'green.400' : 'yellow.400',
minH: '64',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
transition: 'all 0.3s ease',
position: 'relative',
overflow: 'hidden'
})}
>
{/* Arena Background Pattern */}
<div className={css({
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundImage: 'radial-gradient(circle at 25% 25%, rgba(251, 191, 36, 0.1) 0%, transparent 50%), radial-gradient(circle at 75% 75%, rgba(245, 158, 11, 0.1) 0%, transparent 50%)',
pointerEvents: 'none'
})} />
{arenaPlayers.length === 0 ? (
<div className={css({
textAlign: 'center',
zIndex: 1
})}>
<div className={css({
fontSize: '4xl',
mb: '4',
opacity: isDragOver ? 1 : 0.6,
transition: 'all 0.3s ease'
})}>
{isDragOver ? '✨' : '🏟️'}
</div>
<p className={css({
color: 'gray.700',
fontWeight: 'semibold',
fontSize: 'lg'
})}>
{isDragOver ? 'Drop to enter the arena!' : 'Drag champions here'}
</p>
<p className={css({
color: 'gray.600',
fontSize: 'sm',
mt: '2'
})}>
1 champion = Solo 2 = Battle 3+ = Tournament
</p>
</div>
) : (
<div className={css({
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))',
gap: '4',
w: 'full',
zIndex: 1
})}>
{arenaPlayers.map((player, index) => (
<div
key={player.id}
className={css({
background: 'white',
rounded: '2xl',
p: '4',
textAlign: 'center',
border: '3px solid',
borderColor: player.color,
boxShadow: `0 0 20px ${player.color}40`,
position: 'relative',
animation: `arenaEntry 0.6s ease-out ${index * 0.1}s both`,
cursor: 'pointer',
transition: 'all 0.3s ease',
_hover: {
transform: 'translateY(-2px) scale(1.05)',
boxShadow: `0 8px 25px ${player.color}60`
}
})}
onClick={() => handleRemoveFromArena(player.id)}
>
{/* Remove Button */}
<div className={css({
position: 'absolute',
top: '-2',
right: '-2',
w: '6',
h: '6',
background: 'red.500',
rounded: 'full',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 'xs',
color: 'white',
cursor: 'pointer',
opacity: 0,
transition: 'all 0.3s ease',
_hover: {
background: 'red.600',
transform: 'scale(1.1)'
}
})}
style={{
opacity: 1 // Always show remove button
}}
>
</div>
{/* Champion Ready Animation */}
<div className={css({
position: 'absolute',
top: '-4px',
left: '-4px',
right: '-4px',
bottom: '-4px',
borderRadius: '20px',
border: '2px solid',
borderColor: player.color,
animation: 'championReady 2s ease-in-out infinite',
opacity: 0.6
})} />
<div className={css({
fontSize: '3xl',
mb: '2',
animation: 'championFloat 3s ease-in-out infinite'
})}>
{getPlayerEmoji(player.id)}
</div>
<div className={css({
fontSize: 'sm',
fontWeight: 'bold',
color: 'gray.800'
})}>
{getPlayerName(player.id)}
</div>
<div className={css({
fontSize: 'xs',
color: 'green.700',
fontWeight: 'semibold',
mt: '1'
})}>
READY! 🔥
</div>
</div>
))}
</div>
)}
</div>
{/* Arena Info */}
{arenaPlayers.length > 0 && (
<div className={css({
mt: '4',
textAlign: 'center',
background: 'white',
rounded: 'xl',
p: '4',
border: '1px solid',
borderColor: 'gray.200'
})}>
<div className={css({
fontSize: 'sm',
fontWeight: 'semibold',
color: 'gray.700'
})}>
🎮 {arenaPlayers.length} champion{arenaPlayers.length > 1 ? 's' : ''} ready to battle!
</div>
<div className={css({
fontSize: 'xs',
color: 'gray.500',
mt: '1'
})}>
Click a champion to remove from arena
</div>
</div>
)}
</div>
</div>
</div>
)
}
// Enhanced animations for champion arena
const championArenaAnimations = `
@keyframes championBounce {
0% { transform: scale(1) rotate(0deg); }
25% { transform: scale(1.1) rotate(5deg); }
50% { transform: scale(1.2) rotate(0deg); }
75% { transform: scale(1.1) rotate(-5deg); }
100% { transform: scale(1) rotate(0deg); }
}
@keyframes championFloat {
0%, 100% { transform: translateY(0px) rotate(0deg); }
33% { transform: translateY(-4px) rotate(2deg); }
66% { transform: translateY(-2px) rotate(-2deg); }
}
@keyframes championReady {
0%, 100% {
transform: scale(1);
opacity: 0.6;
}
50% {
transform: scale(1.05);
opacity: 1;
}
}
@keyframes arenaEntry {
0% {
opacity: 0;
transform: translateY(20px) scale(0.8) rotate(180deg);
}
60% {
opacity: 1;
transform: translateY(-5px) scale(1.1) rotate(-10deg);
}
80% {
transform: translateY(2px) scale(0.95) rotate(5deg);
}
100% {
opacity: 1;
transform: translateY(0px) scale(1) rotate(0deg);
}
}
@keyframes arenaGlow {
0%, 100% {
box-shadow: 0 0 20px rgba(251, 191, 36, 0.3);
}
50% {
box-shadow: 0 0 30px rgba(251, 191, 36, 0.6);
}
}
`
// Inject champion arena animations
if (typeof document !== 'undefined' && !document.getElementById('champion-arena-animations')) {
const style = document.createElement('style')
style.id = 'champion-arena-animations'
style.textContent = championArenaAnimations
document.head.appendChild(style)
}

View File

@@ -0,0 +1,151 @@
'use client'
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'
export type GameMode = 'single' | 'battle' | 'tournament'
export interface PlayerConfig {
id: number
name: string
emoji: string
color: string
isActive: boolean
}
export interface GameModeContextType {
gameMode: GameMode
players: PlayerConfig[]
activePlayerCount: number
setGameMode: (mode: GameMode) => void
updatePlayer: (id: number, config: Partial<PlayerConfig>) => void
getActivePlayer: (id: number) => PlayerConfig | undefined
resetPlayers: () => void
}
const defaultPlayers: PlayerConfig[] = [
{
id: 1,
name: 'Player 1',
emoji: '😀',
color: '#3b82f6', // Blue
isActive: true
},
{
id: 2,
name: 'Player 2',
emoji: '😎',
color: '#8b5cf6', // Purple
isActive: false
},
{
id: 3,
name: 'Player 3',
emoji: '🤠',
color: '#10b981', // Green
isActive: false
},
{
id: 4,
name: 'Player 4',
emoji: '🚀',
color: '#f59e0b', // Orange
isActive: false
}
]
const STORAGE_KEY = 'soroban-game-mode-config'
const GameModeContext = createContext<GameModeContextType | null>(null)
export function GameModeProvider({ children }: { children: ReactNode }) {
const [gameMode, setGameModeState] = useState<GameMode>('single')
const [players, setPlayers] = useState<PlayerConfig[]>(defaultPlayers)
const [isInitialized, setIsInitialized] = useState(false)
// Load configuration from localStorage on mount
useEffect(() => {
if (typeof window !== 'undefined') {
try {
const stored = localStorage.getItem(STORAGE_KEY)
if (stored) {
const config = JSON.parse(stored)
setGameModeState(config.gameMode || 'single')
setPlayers(config.players || defaultPlayers)
}
setIsInitialized(true)
} catch (error) {
console.warn('Failed to load game mode config from localStorage:', error)
setIsInitialized(true)
}
}
}, [])
// Save configuration to localStorage whenever it changes
useEffect(() => {
if (typeof window !== 'undefined' && isInitialized) {
try {
const config = { gameMode, players }
localStorage.setItem(STORAGE_KEY, JSON.stringify(config))
} catch (error) {
console.warn('Failed to save game mode config to localStorage:', error)
}
}
}, [gameMode, players, isInitialized])
const setGameMode = (mode: GameMode) => {
setGameModeState(mode)
// Auto-configure active players based on mode
setPlayers(prevPlayers => prevPlayers.map(player => ({
...player,
isActive: mode === 'single'
? player.id === 1
: mode === 'battle'
? player.id <= 2
: player.id <= 4 // tournament mode
})))
}
const updatePlayer = (id: number, config: Partial<PlayerConfig>) => {
setPlayers(prevPlayers =>
prevPlayers.map(player =>
player.id === id ? { ...player, ...config } : player
)
)
}
const getActivePlayer = (id: number) => {
return players.find(player => player.id === id && player.isActive)
}
const resetPlayers = () => {
setPlayers(defaultPlayers)
setGameModeState('single')
}
const activePlayerCount = players.filter(player => player.isActive).length
const contextValue: GameModeContextType = {
gameMode,
players,
activePlayerCount,
setGameMode,
updatePlayer,
getActivePlayer,
resetPlayers
}
return (
<GameModeContext.Provider value={contextValue}>
{children}
</GameModeContext.Provider>
)
}
export function useGameMode(): GameModeContextType {
const context = useContext(GameModeContext)
if (!context) {
throw new Error('useGameMode must be used within a GameModeProvider')
}
return context
}