diff --git a/src/components/PresentsPuzzle.tsx b/src/components/PresentsPuzzle.tsx index e8f183a..5844ed5 100644 --- a/src/components/PresentsPuzzle.tsx +++ b/src/components/PresentsPuzzle.tsx @@ -2,6 +2,7 @@ import { useState, useRef } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; +import { Slider } from '@/components/ui/slider'; import SocialShare from './SocialShare'; import { Gift, Play, RotateCw, Zap, Pause, ChevronRight, ChevronLeft, ChevronsRight, ChevronsLeft } from 'lucide-react'; interface PresentsPuzzleProps { @@ -17,6 +18,8 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzleProps) => { + const [numBoxes, setNumBoxes] = useState(100); + const [numPresents, setNumPresents] = useState(26); const [presentBoxes, setPresentBoxes] = useState>(new Set()); const [aliceOpened, setAliceOpened] = useState>(new Set()); const [bobOpened, setBobOpened] = useState>(new Set()); @@ -38,27 +41,27 @@ const PresentsPuzzle = ({ // Generate random present positions const generatePresents = (): Set => { const presents = new Set(); - while (presents.size < 26) { - presents.add(Math.floor(Math.random() * 100) + 1); + while (presents.size < numPresents) { + presents.add(Math.floor(Math.random() * numBoxes) + 1); } return presents; }; - // Get Alice's opening order: 1, 2, 3, ..., 100 + // Get Alice's opening order: 1, 2, 3, ..., numBoxes const getAliceOrder = (): number[] => { return Array.from({ - length: 100 + length: numBoxes }, (_, i) => i + 1); }; - // Get Bob's opening order: 1, 3, 5, ..., 99, 2, 4, 6, ..., 100 + // Get Bob's opening order: 1, 3, 5, ..., odds, 2, 4, 6, ..., evens const getBobOrder = (): number[] => { const odds = Array.from({ - length: 50 - }, (_, i) => i * 2 + 1); + length: Math.ceil(numBoxes / 2) + }, (_, i) => i * 2 + 1).filter(n => n <= numBoxes); const evens = Array.from({ - length: 50 - }, (_, i) => (i + 1) * 2); + length: Math.floor(numBoxes / 2) + }, (_, i) => (i + 1) * 2).filter(n => n <= numBoxes); return [...odds, ...evens]; }; @@ -68,18 +71,18 @@ const PresentsPuzzle = ({ const bobOrder = getBobOrder(); let aliceCount = 0; let bobCount = 0; - for (let i = 0; i < 100; i++) { + for (let i = 0; i < numBoxes; i++) { const aliceFoundPresent = presents.has(aliceOrder[i]); const bobFoundPresent = presents.has(bobOrder[i]); if (aliceFoundPresent) aliceCount++; if (bobFoundPresent) bobCount++; - // Check if both reached 26 on the same turn (tie) - if (aliceCount === 26 && bobCount === 26) return 'tie'; + // Check if both reached target on the same turn (tie) + if (aliceCount === numPresents && bobCount === numPresents) return 'tie'; // Check individual winners - if (aliceCount === 26) return 'alice'; - if (bobCount === 26) return 'bob'; + if (aliceCount === numPresents) return 'alice'; + if (bobCount === numPresents) return 'bob'; } return 'tie'; // Default, though this should never happen }; @@ -117,11 +120,11 @@ const PresentsPuzzle = ({ if (presents.has(aliceBox)) aliceCount++; if (presents.has(bobBox)) bobCount++; - if (aliceCount === 26 && bobCount === 26) { + if (aliceCount === numPresents && bobCount === numPresents) { currentWinner = 'tie'; - } else if (aliceCount === 26) { + } else if (aliceCount === numPresents) { currentWinner = 'alice'; - } else if (bobCount === 26) { + } else if (bobCount === numPresents) { currentWinner = 'bob'; } } @@ -160,7 +163,7 @@ const PresentsPuzzle = ({ let bobCount = 0; let currentWinner: 'alice' | 'bob' | 'tie' | null = null; - for (let i = 0; i < 100 && !currentWinner; i++) { + for (let i = 0; i < numBoxes && !currentWinner; i++) { await new Promise(resolve => setTimeout(resolve, 50)); // Check pause after delay, before opening boxes @@ -192,11 +195,11 @@ const PresentsPuzzle = ({ setBobFound(bobCount); setCurrentStep(i); - if (aliceCount === 26 && bobCount === 26) { + if (aliceCount === numPresents && bobCount === numPresents) { currentWinner = 'tie'; - } else if (aliceCount === 26) { + } else if (aliceCount === numPresents) { currentWinner = 'alice'; - } else if (bobCount === 26) { + } else if (bobCount === numPresents) { currentWinner = 'bob'; } } @@ -211,7 +214,7 @@ const PresentsPuzzle = ({ let bobCount = bobFound; let currentWinner: 'alice' | 'bob' | 'tie' | null = winner; - for (let i = currentStep + 1; i < 100 && !currentWinner; i++) { + for (let i = currentStep + 1; i < numBoxes && !currentWinner; i++) { await new Promise(resolve => setTimeout(resolve, 50)); // Check pause after delay, before opening boxes @@ -243,11 +246,11 @@ const PresentsPuzzle = ({ setBobFound(bobCount); setCurrentStep(i); - if (aliceCount === 26 && bobCount === 26) { + if (aliceCount === numPresents && bobCount === numPresents) { currentWinner = 'tie'; - } else if (aliceCount === 26) { + } else if (aliceCount === numPresents) { currentWinner = 'alice'; - } else if (bobCount === 26) { + } else if (bobCount === numPresents) { currentWinner = 'bob'; } } @@ -262,7 +265,7 @@ const PresentsPuzzle = ({ }; const stepNext = () => { - if (currentStep >= 99 || winner) return; + if (currentStep >= numBoxes - 1 || winner) return; updateStep(currentStep + 1); }; @@ -274,21 +277,21 @@ const PresentsPuzzle = ({ const stepToEnd = () => { // Don't do anything if already at end or winner already determined - if (currentStep >= 99 || winner !== null) return; + if (currentStep >= numBoxes - 1 || winner !== null) return; const { presents, aliceOrder: aOrder, bobOrder: bOrder } = initializeSimulation(); let aliceCount = 0; let bobCount = 0; - let winningStep = 99; // default to last step if no winner found + let winningStep = numBoxes - 1; // default to last step if no winner found // Find the step where someone first wins - for (let i = 0; i < 100; i++) { + for (let i = 0; i < numBoxes; i++) { if (presents.has(aOrder[i])) aliceCount++; if (presents.has(bOrder[i])) bobCount++; // Check if someone won on this step - if (aliceCount === 26 || bobCount === 26) { + if (aliceCount === numPresents || bobCount === numPresents) { winningStep = i; break; } @@ -347,19 +350,20 @@ const PresentsPuzzle = ({ // Render a grid of boxes const renderGrid = (title: string, openedBoxes: Set, found: number, color: string) => { + const cols = Math.min(10, numBoxes); return

- {title} ({openedBoxes.size}/100) + {title} ({openedBoxes.size}/{numBoxes})

- {found}/26 + {found}/{numPresents}
-
+
{Array.from({ - length: 100 + length: numBoxes }, (_, i) => i + 1).map(boxNum => { const hasPresent = presentBoxes.has(boxNum); const isOpened = openedBoxes.has(boxNum); @@ -393,12 +397,42 @@ const PresentsPuzzle = ({ {/* Puzzle Statement */} - +

- Charlie puts 26 presents in 100 boxes, labeled 1 to 100. + Charlie puts{' '} + + { + const val = Math.max(1, Math.min(numBoxes, parseInt(e.target.value) || 1)); + setNumPresents(val); + resetSimulation(); + }} + className="w-16 px-2 py-0.5 text-center border rounded bg-background" + disabled={isRunning || currentStep > -1} + /> + presents + + {' '}in{' '} + + { + const val = Math.max(numPresents, parseInt(e.target.value) || numPresents); + setNumBoxes(val); + resetSimulation(); + }} + className="w-16 px-2 py-0.5 text-center border rounded bg-background" + disabled={isRunning || currentStep > -1} + /> + boxes + + , labeled 1 to {numBoxes}. Each second, Alice and Bob look in one box. Alice opens them in order (1, 2, 3, …), while Bob opens the odds first, then the evens (1, 3, 5, …, 2, 4, 6, …). - Who is more likely to see all 26 presents first? + Who is more likely to see all {numPresents} presents first?

@@ -414,7 +448,7 @@ const PresentsPuzzle = ({
- {winner === 'tie' ? "🤝 It's a tie! Both found all 26 presents at the same time!" : `🎉 ${winner === 'alice' ? 'Alice' : 'Bob'} found all 26 presents first!`} + {winner === 'tie' ? `🤝 It's a tie! Both found all ${numPresents} presents at the same time!` : `🎉 ${winner === 'alice' ? 'Alice' : 'Bob'} found all ${numPresents} presents first!`}
@@ -459,7 +493,7 @@ const PresentsPuzzle = ({