Visual edit in Lovable
Edited UI in Lovable
This commit is contained in:
parent
4e4c667fa9
commit
1c6b1641e4
1 changed files with 56 additions and 122 deletions
|
|
@ -4,19 +4,19 @@ import { Button } from '@/components/ui/button';
|
|||
import { Badge } from '@/components/ui/badge';
|
||||
import SocialShare from './SocialShare';
|
||||
import { Gift, Play, RotateCw, Zap } from 'lucide-react';
|
||||
|
||||
interface PresentsPuzzleProps {
|
||||
showSocialShare?: boolean;
|
||||
shareUrl?: string;
|
||||
}
|
||||
|
||||
interface SimulationResult {
|
||||
alice: number;
|
||||
bob: number;
|
||||
ties: number;
|
||||
}
|
||||
|
||||
const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzleProps) => {
|
||||
const PresentsPuzzle = ({
|
||||
showSocialShare = false,
|
||||
shareUrl
|
||||
}: PresentsPuzzleProps) => {
|
||||
const [presentBoxes, setPresentBoxes] = useState<Set<number>>(new Set());
|
||||
const [aliceOpened, setAliceOpened] = useState<Set<number>>(new Set());
|
||||
const [bobOpened, setBobOpened] = useState<Set<number>>(new Set());
|
||||
|
|
@ -24,7 +24,11 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
const [bobFound, setBobFound] = useState(0);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [winner, setWinner] = useState<'alice' | 'bob' | 'tie' | null>(null);
|
||||
const [simulationResults, setSimulationResults] = useState<SimulationResult>({ alice: 0, bob: 0, ties: 0 });
|
||||
const [simulationResults, setSimulationResults] = useState<SimulationResult>({
|
||||
alice: 0,
|
||||
bob: 0,
|
||||
ties: 0
|
||||
});
|
||||
const [hasSimulated, setHasSimulated] = useState(false);
|
||||
|
||||
// Generate random present positions
|
||||
|
|
@ -38,13 +42,19 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
|
||||
// Get Alice's opening order: 1, 2, 3, ..., 100
|
||||
const getAliceOrder = (): number[] => {
|
||||
return Array.from({ length: 100 }, (_, i) => i + 1);
|
||||
return Array.from({
|
||||
length: 100
|
||||
}, (_, i) => i + 1);
|
||||
};
|
||||
|
||||
// Get Bob's opening order: 1, 3, 5, ..., 99, 2, 4, 6, ..., 100
|
||||
const getBobOrder = (): number[] => {
|
||||
const odds = Array.from({ length: 50 }, (_, i) => i * 2 + 1);
|
||||
const evens = Array.from({ length: 50 }, (_, i) => (i + 1) * 2);
|
||||
const odds = Array.from({
|
||||
length: 50
|
||||
}, (_, i) => i * 2 + 1);
|
||||
const evens = Array.from({
|
||||
length: 50
|
||||
}, (_, i) => (i + 1) * 2);
|
||||
return [...odds, ...evens];
|
||||
};
|
||||
|
||||
|
|
@ -52,14 +62,11 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
const simulateRound = (presents: Set<number>): 'alice' | 'bob' | 'tie' => {
|
||||
const aliceOrder = getAliceOrder();
|
||||
const bobOrder = getBobOrder();
|
||||
|
||||
let aliceCount = 0;
|
||||
let bobCount = 0;
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const aliceFoundPresent = presents.has(aliceOrder[i]);
|
||||
const bobFoundPresent = presents.has(bobOrder[i]);
|
||||
|
||||
if (aliceFoundPresent) aliceCount++;
|
||||
if (bobFoundPresent) bobCount++;
|
||||
|
||||
|
|
@ -70,7 +77,6 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
if (aliceCount === 26) return 'alice';
|
||||
if (bobCount === 26) return 'bob';
|
||||
}
|
||||
|
||||
return 'tie'; // Default, though this should never happen
|
||||
};
|
||||
|
||||
|
|
@ -82,34 +88,26 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
setBobOpened(new Set());
|
||||
setAliceFound(0);
|
||||
setBobFound(0);
|
||||
|
||||
const presents = generatePresents();
|
||||
setPresentBoxes(presents);
|
||||
|
||||
const aliceOrder = getAliceOrder();
|
||||
const bobOrder = getBobOrder();
|
||||
|
||||
let aliceCount = 0;
|
||||
let bobCount = 0;
|
||||
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];
|
||||
const bobBox = bobOrder[i];
|
||||
|
||||
setAliceOpened(prev => new Set(prev).add(aliceBox));
|
||||
setBobOpened(prev => new Set(prev).add(bobBox));
|
||||
|
||||
const aliceFoundPresent = presents.has(aliceBox);
|
||||
const bobFoundPresent = presents.has(bobBox);
|
||||
|
||||
if (aliceFoundPresent) {
|
||||
aliceCount++;
|
||||
setAliceFound(aliceCount);
|
||||
}
|
||||
|
||||
if (bobFoundPresent) {
|
||||
bobCount++;
|
||||
setBobFound(bobCount);
|
||||
|
|
@ -124,7 +122,6 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
currentWinner = 'bob';
|
||||
}
|
||||
}
|
||||
|
||||
setWinner(currentWinner);
|
||||
setIsRunning(false);
|
||||
};
|
||||
|
|
@ -142,8 +139,11 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
|
||||
// Run 100 simulations
|
||||
const runMultipleSimulations = () => {
|
||||
const results: SimulationResult = { alice: 0, bob: 0, ties: 0 };
|
||||
|
||||
const results: SimulationResult = {
|
||||
alice: 0,
|
||||
bob: 0,
|
||||
ties: 0
|
||||
};
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const presents = generatePresents();
|
||||
const winner = simulateRound(presents);
|
||||
|
|
@ -155,20 +155,13 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
results.ties++;
|
||||
}
|
||||
}
|
||||
|
||||
setSimulationResults(results);
|
||||
setHasSimulated(true);
|
||||
};
|
||||
|
||||
// Render a grid of boxes
|
||||
const renderGrid = (
|
||||
title: string,
|
||||
openedBoxes: Set<number>,
|
||||
found: number,
|
||||
color: string
|
||||
) => {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
const renderGrid = (title: string, openedBoxes: Set<number>, found: number, color: string) => {
|
||||
return <div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-lg">{title}</h3>
|
||||
<Badge variant="secondary" className="text-sm">
|
||||
|
|
@ -177,41 +170,28 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
</Badge>
|
||||
</div>
|
||||
<div className="grid grid-cols-10 gap-1 p-2 bg-muted/30 rounded-lg">
|
||||
{Array.from({ length: 100 }, (_, i) => i + 1).map((boxNum) => {
|
||||
const hasPresent = presentBoxes.has(boxNum);
|
||||
const isOpened = openedBoxes.has(boxNum);
|
||||
const showPresent = isOpened && hasPresent;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={boxNum}
|
||||
className={`
|
||||
{Array.from({
|
||||
length: 100
|
||||
}, (_, i) => i + 1).map(boxNum => {
|
||||
const hasPresent = presentBoxes.has(boxNum);
|
||||
const isOpened = openedBoxes.has(boxNum);
|
||||
const showPresent = isOpened && hasPresent;
|
||||
return <div key={boxNum} className={`
|
||||
aspect-square rounded border flex items-center justify-center text-xs font-medium
|
||||
transition-all duration-200
|
||||
${isOpened
|
||||
? showPresent
|
||||
? 'bg-[#808000] text-white border-[#606000] shadow-sm'
|
||||
: 'bg-muted border-border text-muted-foreground'
|
||||
: 'bg-gray-400 border-gray-500 text-gray-700'
|
||||
}
|
||||
`}
|
||||
title={`Box ${boxNum}${hasPresent ? ' (Present!)' : ''}`}
|
||||
>
|
||||
${isOpened ? showPresent ? 'bg-[#808000] text-white border-[#606000] shadow-sm' : 'bg-muted border-border text-muted-foreground' : 'bg-gray-400 border-gray-500 text-gray-700'}
|
||||
`} title={`Box ${boxNum}${hasPresent ? ' (Present!)' : ''}`}>
|
||||
{showPresent ? <Gift className="w-3 h-3" /> : boxNum}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{title === 'Alice' && 'Opens in order: 1, 2, 3, 4, 5, ...'}
|
||||
{title === 'Bob' && 'Opens odds first, then evens: 1, 3, 5, ..., 2, 4, 6, ...'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
return <div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
|
|
@ -242,37 +222,23 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
</div>
|
||||
|
||||
{/* Winner Display */}
|
||||
{winner && (
|
||||
<Card className="bg-primary/10 border-primary/20">
|
||||
{winner && <Card className="bg-primary/10 border-primary/20">
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-primary">
|
||||
{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 26 presents at the same time!" : `🎉 ${winner === 'alice' ? 'Alice' : 'Bob'} found all 26 presents first!`}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</Card>}
|
||||
|
||||
{/* Control Buttons */}
|
||||
<div className="flex gap-3 justify-center flex-wrap">
|
||||
<Button
|
||||
onClick={startSimulation}
|
||||
disabled={isRunning}
|
||||
className="gap-2"
|
||||
>
|
||||
<Button onClick={startSimulation} disabled={isRunning} className="gap-2">
|
||||
<Play className="w-4 h-4" />
|
||||
Start
|
||||
</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" />
|
||||
Reset
|
||||
</Button>
|
||||
|
|
@ -281,20 +247,14 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
{/* Simulate 100 */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
onClick={runMultipleSimulations}
|
||||
variant="secondary"
|
||||
className="gap-2"
|
||||
size="lg"
|
||||
>
|
||||
<Button onClick={runMultipleSimulations} variant="secondary" className="gap-2" size="lg">
|
||||
<Zap className="w-4 h-4" />
|
||||
Simulate 100 Rounds
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Results */}
|
||||
{hasSimulated && (
|
||||
<Card className="bg-accent/10 border-accent/20">
|
||||
{hasSimulated && <Card className="bg-accent/10 border-accent/20">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Results from 100 Simulations</CardTitle>
|
||||
</CardHeader>
|
||||
|
|
@ -306,7 +266,7 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
</div>
|
||||
<div className="text-sm font-medium">Alice Wins</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{((simulationResults.alice / 100) * 100).toFixed(1)}%
|
||||
{(simulationResults.alice / 100 * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center space-y-2">
|
||||
|
|
@ -315,7 +275,7 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
</div>
|
||||
<div className="text-sm font-medium">Bob Wins</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{((simulationResults.bob / 100) * 100).toFixed(1)}%
|
||||
{(simulationResults.bob / 100 * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center space-y-2">
|
||||
|
|
@ -324,26 +284,18 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
</div>
|
||||
<div className="text-sm font-medium">Ties</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{((simulationResults.ties / 100) * 100).toFixed(1)}%
|
||||
{(simulationResults.ties / 100 * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 pt-4 border-t border-border">
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
{simulationResults.alice > simulationResults.bob && simulationResults.alice > simulationResults.ties
|
||||
? '💡 Alice tends to win more often!'
|
||||
: simulationResults.bob > simulationResults.alice && simulationResults.bob > simulationResults.ties
|
||||
? '💡 Bob tends to win more often!'
|
||||
: simulationResults.ties > simulationResults.alice && simulationResults.ties > simulationResults.bob
|
||||
? '💡 Ties are most common!'
|
||||
: '💡 The results are quite balanced!'
|
||||
}
|
||||
{simulationResults.alice > simulationResults.bob && simulationResults.alice > simulationResults.ties ? '💡 Alice tends to win more often!' : simulationResults.bob > simulationResults.alice && simulationResults.bob > simulationResults.ties ? '💡 Bob tends to win more often!' : simulationResults.ties > simulationResults.alice && simulationResults.ties > simulationResults.bob ? '💡 Ties are most common!' : '💡 The results are quite balanced!'}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</Card>}
|
||||
</div>
|
||||
|
||||
{/* Insight */}
|
||||
|
|
@ -361,24 +313,14 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
{/* Acknowledgement */}
|
||||
<Card className="bg-muted/50">
|
||||
<CardContent className="pt-6">
|
||||
<h4 className="font-semibold mb-2 text-sm">🙏 Acknowledgement</h4>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
This simulation was inspired by{' '}
|
||||
<a
|
||||
href="https://x.com/littmath/status/1990807150101475653"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
<a href="https://x.com/littmath/status/1990807150101475653" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">
|
||||
this tweet
|
||||
</a>
|
||||
{' '}by Daniel Litt, which in turn is a variation of{' '}
|
||||
<a
|
||||
href="https://gilkalai.wordpress.com/2024/09/03/test-your-intuition-56-fifteen-boxes-puzzle/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
<a href="https://gilkalai.wordpress.com/2024/09/03/test-your-intuition-56-fifteen-boxes-puzzle/" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">
|
||||
this puzzle
|
||||
</a>
|
||||
{' '}by Gil Kalai.
|
||||
|
|
@ -388,15 +330,7 @@ const PresentsPuzzle = ({ showSocialShare = false, shareUrl }: PresentsPuzzlePro
|
|||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{showSocialShare && (
|
||||
<SocialShare
|
||||
url={shareUrl || window.location.href}
|
||||
title="The Presents Puzzle"
|
||||
description="A probability puzzle about search strategies and expected value"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{showSocialShare && <SocialShare url={shareUrl || window.location.href} title="The Presents Puzzle" description="A probability puzzle about search strategies and expected value" />}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default PresentsPuzzle;
|
||||
Loading…
Add table
Add a link
Reference in a new issue