import React, { useState, useRef } from 'react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import SocialShare from '@/components/SocialShare'; const SKY_BLUE = '#e0f2fe'; const MAROON = '#800000'; function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } const MIN_HEAPS = 2; const MAX_HEAPS = 10; const MIN_STONES = 1; const MAX_STONES = 20; type Mode = 'random' | 'user'; type Heap = number; type Player = 0 | 1; const heapColors = [ '#fbbf24', '#60a5fa', '#34d399', '#f472b6', '#f87171', '#a78bfa', '#facc15', '#38bdf8', '#4ade80', '#f472b6', '#f87171', '#a78bfa', '#fbbf24', '#60a5fa', '#34d399', '#f472b6', '#f87171', '#a78bfa', '#facc15', '#38bdf8', ]; const NimGame: React.FC = () => { const [mode, setMode] = useState(null); const [showSetup, setShowSetup] = useState(false); const [userHeapCount, setUserHeapCount] = useState(3); const [userHeapSizes, setUserHeapSizes] = useState([3, 3, 3]); const [heaps, setHeaps] = useState([]); const [currentPlayer, setCurrentPlayer] = useState(0); const [winner, setWinner] = useState(null); // Store the initial heap sizes to keep the left edge fixed const [initialHeapSizes, setInitialHeapSizes] = useState([]); // Store the initial max heap size for all heaps in a ref const initialMaxHeapSizeRef = useRef(0); // Store the pixel width for the entire heaps group container (label + stones) const [heapsGroupWidth, setHeapsGroupWidth] = useState('0px'); // Store the initial heaps for reset const initialHeapsRef = useRef([]); // When a new game starts, record the initial heap sizes and max size and compute the group width React.useEffect(() => { if (mode !== null && heaps.length > 0) { const maxSize = Math.max(...heaps); initialMaxHeapSizeRef.current = maxSize; // Each stone is 1.25rem wide (w-5 = 1.25rem), gap is 0.25rem (gap-1), label is ~2ch (about 1.5rem) // Total width = label + stones + gaps between stones const stoneWidth = 1.25; // rem const gapWidth = 0.25; // rem const labelWidth = 1.5; // rem (for 'min-w-[2ch]' + gap) const totalWidthRem = maxSize > 0 ? (labelWidth + maxSize * stoneWidth + (maxSize - 1) * gapWidth) : labelWidth; setHeapsGroupWidth(`${totalWidthRem}rem`); } // eslint-disable-next-line }, [mode, heaps.length]); // When a new game starts, record the initial heaps React.useEffect(() => { if (mode !== null && heaps.length > 0) { initialHeapsRef.current = [...heaps]; } // eslint-disable-next-line }, [mode, heaps.length]); // Start a new game const startGame = (selectedMode: Mode) => { setMode(selectedMode); setWinner(null); setCurrentPlayer(0); if (selectedMode === 'random') { const k = getRandomInt(MIN_HEAPS, MAX_HEAPS); const randomHeaps = Array.from({ length: k }, () => getRandomInt(MIN_STONES, MAX_STONES)); setHeaps(randomHeaps); } else { setShowSetup(true); } }; // Handle user-defined setup const handleUserSetupConfirm = () => { setHeaps([...userHeapSizes]); setShowSetup(false); setWinner(null); setCurrentPlayer(0); }; // Handle stone click: remove all stones from clicked index to the end in that heap const handleStoneClick = (heapIdx: number, stoneIdx: number) => { if (winner !== null) return; if (heaps[heapIdx] === 0) return; const newHeaps = [...heaps]; newHeaps[heapIdx] = stoneIdx; setHeaps(newHeaps); // Check for win if (newHeaps.every(h => h === 0)) { setWinner(currentPlayer); } else { setCurrentPlayer((currentPlayer === 0 ? 1 : 0) as Player); } }; // Handle play again const handlePlayAgain = () => { setMode(null); setHeaps([]); setWinner(null); setCurrentPlayer(0); }; // Reset the game to the initial heaps const handleResetGame = () => { setHeaps([...initialHeapsRef.current]); setWinner(null); setCurrentPlayer(0); }; // Start a new game (go back to mode selection) const handleNewGame = () => { setMode(null); setHeaps([]); setWinner(null); setCurrentPlayer(0); }; // User heap count slider change const handleHeapCountChange = (val: number) => { setUserHeapCount(val); setUserHeapSizes(Array.from({ length: val }, (_, i) => userHeapSizes[i] || 3)); }; // User heap size slider change const handleHeapSizeChange = (idx: number, val: number) => { setUserHeapSizes(sizes => sizes.map((s, i) => (i === idx ? val : s))); }; // Find the max heap size for centering const maxHeapSize = Math.max(0, ...heaps, ...userHeapSizes); // Visual heap rendering: always show all heaps, show 0 for empty, keep left anchoring, reserve space for initial max heap size const renderHeap = (heap: number, idx: number) => { const maxInitial = initialMaxHeapSizeRef.current; return (
{/* Heap size label */} {heap} {/* Stones, left-aligned, reserve space for initial max heap size */}
{heap > 0 ? ( Array.from({ length: heap }).map((_, i) => ( handleStoneClick(idx, i)} title="Click to remove this stone and all to its right" /> )) ) : ( )}
); }; // Center the group of heaps only at the start // We'll use a flex container with justify-center for the initial render, but keep the left edge fixed after return (
Game of Nim Take turns. On your turn, click any stone in any heap to remove that stone and all stones to its right in that heap. The player to take the last stone wins! {/* Game Controls */} {mode === null && (
)} {mode !== null && ( <> {/* Current Turn Indicator and Reset Button */}
{winner === null ? ( <>

Current Turn:

Player {currentPlayer + 1}
) : (
Winner: Player {winner + 1}!
)}
{/* Heaps Display */}
{heaps.map((heap, idx) => renderHeap(heap, idx))}
{/* Play Again Button */} {winner !== null && (
)} )} {/* Instructions */}

Click any stone in any heap to remove that stone and all stones to its right in that heap. The player to take the last stone wins!

{/* User-defined setup modal */} Set up your Nim instance
handleHeapCountChange(Number(e.target.value))} className="w-full" />
{Array.from({ length: userHeapCount }).map((_, idx) => (
handleHeapSizeChange(idx, Number(e.target.value))} className="w-40" /> {userHeapSizes[idx] || 3}
))}
{/* Social Share */}
); }; export default NimGame;