Add presents controls

Enhance Presents Puzzle with pause, step, and navigation controls:
- Add pause, step next/back, step to start/end buttons
- Integrate into existing puzzle controls and state
- Enable stepping through simulation without auto-rotation/motion

X-Lovable-Edit-ID: edt-d7d358ef-a108-4530-a942-9a6508865305
This commit is contained in:
gpt-engineer-app[bot] 2025-11-19 03:32:42 +00:00
commit 6f35e0c38e

View file

@ -3,7 +3,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import SocialShare from './SocialShare'; import SocialShare from './SocialShare';
import { Gift, Play, RotateCw, Zap } from 'lucide-react'; import { Gift, Play, RotateCw, Zap, Pause, ChevronRight, ChevronLeft, ChevronsRight, ChevronsLeft } from 'lucide-react';
interface PresentsPuzzleProps { interface PresentsPuzzleProps {
showSocialShare?: boolean; showSocialShare?: boolean;
shareUrl?: string; shareUrl?: string;
@ -23,6 +23,10 @@ const PresentsPuzzle = ({
const [aliceFound, setAliceFound] = useState(0); const [aliceFound, setAliceFound] = useState(0);
const [bobFound, setBobFound] = useState(0); const [bobFound, setBobFound] = useState(0);
const [isRunning, setIsRunning] = useState(false); const [isRunning, setIsRunning] = useState(false);
const [isPaused, setIsPaused] = useState(false);
const [currentStep, setCurrentStep] = useState(0);
const [aliceOrder, setAliceOrder] = useState<number[]>([]);
const [bobOrder, setBobOrder] = useState<number[]>([]);
const [winner, setWinner] = useState<'alice' | 'bob' | 'tie' | null>(null); const [winner, setWinner] = useState<'alice' | 'bob' | 'tie' | null>(null);
const [simulationResults, setSimulationResults] = useState<SimulationResult>({ const [simulationResults, setSimulationResults] = useState<SimulationResult>({
alice: 0, alice: 0,
@ -80,28 +84,78 @@ const PresentsPuzzle = ({
return 'tie'; // Default, though this should never happen return 'tie'; // Default, though this should never happen
}; };
// Update display for a specific step
const updateStep = (step: number) => {
const aliceOpenedSet = new Set<number>();
const bobOpenedSet = new Set<number>();
let aliceCount = 0;
let bobCount = 0;
let currentWinner: 'alice' | 'bob' | 'tie' | null = null;
for (let i = 0; i <= step; i++) {
const aliceBox = aliceOrder[i];
const bobBox = bobOrder[i];
aliceOpenedSet.add(aliceBox);
bobOpenedSet.add(bobBox);
if (presentBoxes.has(aliceBox)) aliceCount++;
if (presentBoxes.has(bobBox)) bobCount++;
if (aliceCount === 26 && bobCount === 26) {
currentWinner = 'tie';
} else if (aliceCount === 26) {
currentWinner = 'alice';
} else if (bobCount === 26) {
currentWinner = 'bob';
}
}
setAliceOpened(aliceOpenedSet);
setBobOpened(bobOpenedSet);
setAliceFound(aliceCount);
setBobFound(bobCount);
setWinner(currentWinner);
setCurrentStep(step);
};
// Animate a single simulation // Animate a single simulation
const startSimulation = async () => { const startSimulation = async () => {
if (currentStep === 0) {
// Start new simulation
setIsRunning(true); setIsRunning(true);
setIsPaused(false);
setWinner(null); setWinner(null);
setAliceOpened(new Set()); setAliceOpened(new Set());
setBobOpened(new Set()); setBobOpened(new Set());
setAliceFound(0); setAliceFound(0);
setBobFound(0); setBobFound(0);
setCurrentStep(0);
const presents = generatePresents(); const presents = generatePresents();
setPresentBoxes(presents); setPresentBoxes(presents);
const aliceOrder = getAliceOrder(); const newAliceOrder = getAliceOrder();
const bobOrder = getBobOrder(); const newBobOrder = getBobOrder();
setAliceOrder(newAliceOrder);
setBobOrder(newBobOrder);
// Start animation from beginning
let aliceCount = 0; let aliceCount = 0;
let bobCount = 0; let bobCount = 0;
let currentWinner: 'alice' | 'bob' | 'tie' | null = null; let currentWinner: 'alice' | 'bob' | 'tie' | null = null;
for (let i = 0; i < 100 && !currentWinner; i++) {
await new Promise(resolve => setTimeout(resolve, 50)); // Animation delay
const aliceBox = aliceOrder[i]; for (let i = 0; i < 100 && !currentWinner; i++) {
const bobBox = bobOrder[i]; await new Promise(resolve => setTimeout(resolve, 50));
// Check if paused
while (isPaused) {
await new Promise(resolve => setTimeout(resolve, 100));
}
const aliceBox = newAliceOrder[i];
const bobBox = newBobOrder[i];
setAliceOpened(prev => new Set(prev).add(aliceBox)); setAliceOpened(prev => new Set(prev).add(aliceBox));
setBobOpened(prev => new Set(prev).add(bobBox)); setBobOpened(prev => new Set(prev).add(bobBox));
setCurrentStep(i);
const aliceFoundPresent = presents.has(aliceBox); const aliceFoundPresent = presents.has(aliceBox);
const bobFoundPresent = presents.has(bobBox); const bobFoundPresent = presents.has(bobBox);
if (aliceFoundPresent) { if (aliceFoundPresent) {
@ -113,7 +167,6 @@ const PresentsPuzzle = ({
setBobFound(bobCount); setBobFound(bobCount);
} }
// Check for tie first (both reach 26 on same turn)
if (aliceCount === 26 && bobCount === 26) { if (aliceCount === 26 && bobCount === 26) {
currentWinner = 'tie'; currentWinner = 'tie';
} else if (aliceCount === 26) { } else if (aliceCount === 26) {
@ -124,6 +177,81 @@ const PresentsPuzzle = ({
} }
setWinner(currentWinner); setWinner(currentWinner);
setIsRunning(false); setIsRunning(false);
} else {
// Resume from current step
setIsRunning(true);
setIsPaused(false);
let aliceCount = aliceFound;
let bobCount = bobFound;
let currentWinner: 'alice' | 'bob' | 'tie' | null = winner;
for (let i = currentStep + 1; i < 100 && !currentWinner; i++) {
await new Promise(resolve => setTimeout(resolve, 50));
while (isPaused) {
await new Promise(resolve => setTimeout(resolve, 100));
}
const aliceBox = aliceOrder[i];
const bobBox = bobOrder[i];
setAliceOpened(prev => new Set(prev).add(aliceBox));
setBobOpened(prev => new Set(prev).add(bobBox));
setCurrentStep(i);
const aliceFoundPresent = presentBoxes.has(aliceBox);
const bobFoundPresent = presentBoxes.has(bobBox);
if (aliceFoundPresent) {
aliceCount++;
setAliceFound(aliceCount);
}
if (bobFoundPresent) {
bobCount++;
setBobFound(bobCount);
}
if (aliceCount === 26 && bobCount === 26) {
currentWinner = 'tie';
} else if (aliceCount === 26) {
currentWinner = 'alice';
} else if (bobCount === 26) {
currentWinner = 'bob';
}
}
setWinner(currentWinner);
setIsRunning(false);
}
};
const pauseSimulation = () => {
setIsPaused(true);
setIsRunning(false);
};
const stepNext = () => {
if (aliceOrder.length === 0 || currentStep >= 99 || winner) return;
updateStep(currentStep + 1);
};
const stepBack = () => {
if (aliceOrder.length === 0 || currentStep <= 0) return;
setWinner(null);
updateStep(currentStep - 1);
};
const stepToEnd = () => {
if (aliceOrder.length === 0) return;
updateStep(99);
};
const stepToStart = () => {
if (aliceOrder.length === 0) return;
setAliceOpened(new Set());
setBobOpened(new Set());
setAliceFound(0);
setBobFound(0);
setCurrentStep(0);
setWinner(null);
}; };
// Reset simulation // Reset simulation
@ -135,6 +263,10 @@ const PresentsPuzzle = ({
setBobFound(0); setBobFound(0);
setWinner(null); setWinner(null);
setIsRunning(false); setIsRunning(false);
setIsPaused(false);
setCurrentStep(0);
setAliceOrder([]);
setBobOrder([]);
}; };
// Run 100 simulations // Run 100 simulations
@ -235,10 +367,59 @@ const PresentsPuzzle = ({
</Card>} </Card>}
{/* Control Buttons */} {/* Control Buttons */}
<div className="flex gap-3 justify-center flex-wrap"> <div className="flex gap-2 justify-center flex-wrap">
<Button onClick={startSimulation} disabled={isRunning} className="gap-2"> <Button
onClick={startSimulation}
disabled={isRunning || (aliceOrder.length > 0 && winner !== null)}
className="gap-2"
>
<Play className="w-4 h-4" /> <Play className="w-4 h-4" />
Start {currentStep === 0 ? 'Start' : 'Resume'}
</Button>
<Button
onClick={pauseSimulation}
disabled={!isRunning}
variant="outline"
className="gap-2"
>
<Pause className="w-4 h-4" />
Pause
</Button>
<Button
onClick={stepToStart}
disabled={isRunning || aliceOrder.length === 0 || currentStep === 0}
variant="outline"
className="gap-2"
title="Step to start"
>
<ChevronsLeft className="w-4 h-4" />
</Button>
<Button
onClick={stepBack}
disabled={isRunning || aliceOrder.length === 0 || currentStep === 0}
variant="outline"
className="gap-2"
title="Step back"
>
<ChevronLeft className="w-4 h-4" />
</Button>
<Button
onClick={stepNext}
disabled={isRunning || aliceOrder.length === 0 || currentStep >= 99 || winner !== null}
variant="outline"
className="gap-2"
title="Step next"
>
<ChevronRight className="w-4 h-4" />
</Button>
<Button
onClick={stepToEnd}
disabled={isRunning || aliceOrder.length === 0 || currentStep >= 99}
variant="outline"
className="gap-2"
title="Step to end"
>
<ChevronsRight className="w-4 h-4" />
</Button> </Button>
<Button onClick={resetSimulation} variant="outline" disabled={isRunning} className="gap-2"> <Button onClick={resetSimulation} variant="outline" disabled={isRunning} className="gap-2">
<RotateCw className="w-4 h-4" /> <RotateCw className="w-4 h-4" />