From 828f2dba667696a55389a83f161277d2203bfd3b Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 03:04:28 +0000 Subject: [PATCH] Changes --- src/App.tsx | 2 + src/components/ErdosDiscrepancyPuzzle.tsx | 902 ++++++++++++++++++++++ src/components/InteractiveIndex.tsx | 9 + src/pages/ErdosDiscrepancyPuzzlePage.tsx | 16 + src/pages/theme-pages/Puzzles.tsx | 10 + 5 files changed, 939 insertions(+) create mode 100644 src/components/ErdosDiscrepancyPuzzle.tsx create mode 100644 src/pages/ErdosDiscrepancyPuzzlePage.tsx diff --git a/src/App.tsx b/src/App.tsx index 55ab15d..9c68f71 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -67,6 +67,7 @@ import BagchalGamePage from './pages/BagchalGamePage'; import AscDescGridPuzzlePage from './pages/AscDescGridPuzzlePage'; import EternalDominationGamePage from './pages/EternalDominationGamePage'; import LadybugClockPuzzlePage from './pages/LadybugClockPuzzlePage'; +import ErdosDiscrepancyPuzzlePage from './pages/ErdosDiscrepancyPuzzlePage'; const queryClient = new QueryClient(); @@ -144,6 +145,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/ErdosDiscrepancyPuzzle.tsx b/src/components/ErdosDiscrepancyPuzzle.tsx new file mode 100644 index 0000000..8046d40 --- /dev/null +++ b/src/components/ErdosDiscrepancyPuzzle.tsx @@ -0,0 +1,902 @@ +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Slider } from '@/components/ui/slider'; +import { + Play, RotateCcw, Info, ChevronDown, ChevronUp, + ArrowLeft, ArrowRight, Skull, AlertTriangle, + BookOpen, Zap, Eye, Grid3X3, TrendingUp, Pause, + Volume2, VolumeX, Target, Trophy +} from 'lucide-react'; +import SocialShare from '@/components/SocialShare'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; + +interface ErdosDiscrepancyPuzzleProps { + showSocialShare?: boolean; +} + +type GamePhase = 'intro' | 'writing' | 'captor-reveal' | 'walking' | 'fallen' | 'survived'; +type Direction = 1 | -1 | null; + +interface DiscrepancyResult { + d: number; + k: number; + sum: number; + positions: number[]; +} + +const ErdosDiscrepancyPuzzle: React.FC = ({ showSocialShare = false }) => { + // Core game state + const [sequence, setSequence] = useState([]); + const [discrepancyBound, setDiscrepancyBound] = useState(1); + const [gamePhase, setGamePhase] = useState('intro'); + const [showRules, setShowRules] = useState(false); + + // Walk visualization state + const [selectedD, setSelectedD] = useState(1); + const [walkPosition, setWalkPosition] = useState(0); + const [walkStep, setWalkStep] = useState(0); + const [isWalking, setIsWalking] = useState(false); + const [walkSpeed, setWalkSpeed] = useState(500); + + // Prisoner's Walk story mode + const [captorChosenD, setCaptorChosenD] = useState(null); + const [prisonerPosition, setPrisonerPosition] = useState(0); + const [prisonerStep, setPrisonerStep] = useState(0); + + // Heatmap state + const [hoveredCell, setHoveredCell] = useState<{d: number, k: number} | null>(null); + const [highlightedSubsequence, setHighlightedSubsequence] = useState([]); + + // Best score tracking + const [bestLength, setBestLength] = useState(null); + + // Load best score from localStorage + useEffect(() => { + const saved = localStorage.getItem(`erdos-best-length-c${discrepancyBound}`); + if (saved) { + setBestLength(parseInt(saved)); + } + }, [discrepancyBound]); + + // Calculate all discrepancies for current sequence + const allDiscrepancies = useMemo((): DiscrepancyResult[] => { + if (sequence.length === 0) return []; + + const results: DiscrepancyResult[] = []; + const n = sequence.length; + + for (let d = 1; d <= n; d++) { + let sum = 0; + const positions: number[] = []; + + for (let k = 1; k * d <= n; k++) { + const pos = k * d - 1; // 0-indexed + if (sequence[pos] !== null) { + sum += sequence[pos]!; + positions.push(k * d); + results.push({ + d, + k, + sum, + positions: [...positions] + }); + } + } + } + + return results; + }, [sequence]); + + // Find maximum discrepancy + const maxDiscrepancy = useMemo(() => { + if (allDiscrepancies.length === 0) return { value: 0, result: null }; + + let maxAbs = 0; + let maxResult: DiscrepancyResult | null = null; + + for (const result of allDiscrepancies) { + if (Math.abs(result.sum) > maxAbs) { + maxAbs = Math.abs(result.sum); + maxResult = result; + } + } + + return { value: maxAbs, result: maxResult }; + }, [allDiscrepancies]); + + // Check if current sequence violates bound + const hasViolation = maxDiscrepancy.value > discrepancyBound; + + // Find worst step size for captor + const findWorstD = useCallback((): number => { + let worstD = 1; + let worstDiscrepancy = 0; + + for (const result of allDiscrepancies) { + if (Math.abs(result.sum) > worstDiscrepancy) { + worstDiscrepancy = Math.abs(result.sum); + worstD = result.d; + } + } + + return worstD; + }, [allDiscrepancies]); + + // Get discrepancy for specific d,k + const getDiscrepancy = (d: number, k: number): number | null => { + const result = allDiscrepancies.find(r => r.d === d && r.k === k); + return result ? result.sum : null; + }; + + // Toggle sequence value + const togglePosition = (index: number) => { + if (gamePhase !== 'writing') return; + + setSequence(prev => { + const newSeq = [...prev]; + if (newSeq[index] === null) { + newSeq[index] = 1; + } else if (newSeq[index] === 1) { + newSeq[index] = -1; + } else { + newSeq[index] = 1; + } + return newSeq; + }); + }; + + // Add new position to sequence + const addPosition = () => { + if (gamePhase !== 'writing') return; + setSequence(prev => [...prev, null]); + }; + + // Start the game + const startGame = () => { + setSequence([null, null, null, null, null]); + setGamePhase('writing'); + setWalkPosition(0); + setWalkStep(0); + setPrisonerPosition(0); + setPrisonerStep(0); + setCaptorChosenD(null); + setHighlightedSubsequence([]); + }; + + // Reset the game + const resetGame = () => { + setSequence([]); + setGamePhase('intro'); + setWalkPosition(0); + setWalkStep(0); + setIsWalking(false); + setPrisonerPosition(0); + setPrisonerStep(0); + setCaptorChosenD(null); + setHighlightedSubsequence([]); + }; + + // Handle captor reveal (Prisoner's Walk mode) + const revealCaptor = () => { + const worstD = findWorstD(); + setCaptorChosenD(worstD); + setGamePhase('captor-reveal'); + setPrisonerPosition(0); + setPrisonerStep(0); + }; + + // Start prisoner walking + const startPrisonerWalk = () => { + if (captorChosenD === null) return; + setGamePhase('walking'); + setPrisonerPosition(0); + setPrisonerStep(0); + }; + + // Prisoner walk animation + useEffect(() => { + if (gamePhase !== 'walking' || captorChosenD === null) return; + + const interval = setInterval(() => { + setPrisonerStep(prev => { + const nextStep = prev + 1; + const posIndex = nextStep * captorChosenD - 1; + + if (posIndex >= sequence.length || sequence[posIndex] === null) { + // Sequence ended without falling + setGamePhase('survived'); + clearInterval(interval); + + // Save best score + const validLength = sequence.filter(s => s !== null).length; + if (!bestLength || validLength > bestLength) { + setBestLength(validLength); + localStorage.setItem(`erdos-best-length-c${discrepancyBound}`, validLength.toString()); + } + + return prev; + } + + const newPosition = prisonerPosition + sequence[posIndex]!; + setPrisonerPosition(newPosition); + + if (Math.abs(newPosition) > discrepancyBound) { + setGamePhase('fallen'); + clearInterval(interval); + return nextStep; + } + + return nextStep; + }); + }, walkSpeed); + + return () => clearInterval(interval); + }, [gamePhase, captorChosenD, sequence, prisonerPosition, discrepancyBound, walkSpeed, bestLength]); + + // Manual walk control + const walkSubsequence = () => { + if (isWalking) { + setIsWalking(false); + return; + } + + setWalkPosition(0); + setWalkStep(0); + setIsWalking(true); + }; + + // Walk animation effect + useEffect(() => { + if (!isWalking) return; + + const interval = setInterval(() => { + setWalkStep(prev => { + const nextStep = prev + 1; + const posIndex = nextStep * selectedD - 1; + + if (posIndex >= sequence.length || sequence[posIndex] === null) { + setIsWalking(false); + return prev; + } + + setWalkPosition(current => current + sequence[posIndex]!); + return nextStep; + }); + }, walkSpeed); + + return () => clearInterval(interval); + }, [isWalking, selectedD, sequence, walkSpeed]); + + // Get color for heatmap cell + const getHeatmapColor = (discrepancy: number | null): string => { + if (discrepancy === null) return 'bg-muted/30'; + + const absVal = Math.abs(discrepancy); + if (absVal > discrepancyBound) { + return 'bg-destructive/80 text-destructive-foreground'; + } + if (absVal === discrepancyBound) { + return 'bg-amber-500/70 text-white'; + } + if (absVal === 0) { + return 'bg-emerald-500/50 text-emerald-900 dark:text-emerald-100'; + } + + const intensity = absVal / discrepancyBound; + if (intensity < 0.5) { + return 'bg-emerald-400/40 text-emerald-900 dark:text-emerald-100'; + } + return 'bg-amber-400/50 text-amber-900 dark:text-amber-100'; + }; + + // Max valid k for given d + const maxK = (d: number) => Math.floor(sequence.length / d); + + // Highlighted positions for selected d + const selectedPositions = useMemo(() => { + const positions: number[] = []; + for (let k = 1; k * selectedD <= sequence.length; k++) { + positions.push(k * selectedD); + } + return positions; + }, [selectedD, sequence.length]); + + // Get fill status of sequence + const filledCount = sequence.filter(s => s !== null).length; + const allFilled = filledCount === sequence.length && sequence.length > 0; + + // Known optimal sequences + const optimalSequences: Record = { + 1: [1, 1, -1, 1, -1, -1, -1, 1, 1, 1, -1], // One of the optimal 11-length sequences for C=1 + }; + + const loadOptimalSequence = () => { + const optimal = optimalSequences[discrepancyBound]; + if (optimal) { + setSequence([...optimal]); + setGamePhase('writing'); + } + }; + + return ( +
+ {/* Header */} +
+
+

+ The Erdős Discrepancy Problem +

+

+ Can you write instructions to survive the tunnel forever? +

+
+
+ +
+
+ + {/* Rules Panel */} + {showRules && ( + + +
+
+

+ The Prisoner's Dilemma +

+

+ You're trapped in a tunnel with a deadly cliff {discrepancyBound} step{discrepancyBound > 1 ? 's' : ''} to your LEFT and + a pit of vipers {discrepancyBound} step{discrepancyBound > 1 ? 's' : ''} to your RIGHT. +

+

+ Write a list of instructions: each is either LEFT or RIGHT. +

+

+ The evil captor then picks a step size d and you follow every d-th instruction. +

+
+
+

+ The Impossible Task +

+

+ C = 1: Maximum possible sequence length is 11 +

+

+ C = 2: Maximum possible sequence length is 1,160 +

+

+ Terence Tao proved in 2015: No infinite sequence can survive! +

+
+
+
+
+ )} + + {/* Game Phase: Intro */} + {gamePhase === 'intro' && ( + + +
+ +

The Prisoner's Walk

+

+ You wake up in a dark tunnel. There's a cliff to your left and vipers to your right. + Your captor demands you write a sequence of LEFT/RIGHT instructions... + but they get to choose which instructions you follow. +

+ +
+ +
+ 1 + setDiscrepancyBound(v[0])} + min={1} + max={3} + step={1} + className="flex-1" + /> + 3 +
+

+ C=1: Max 11 steps | C=2: Max 1,160 steps | C=3: Unknown (very large) +

+
+ +
+ + {discrepancyBound === 1 && ( + + )} +
+ + {bestLength && ( +

+ Your best: {bestLength} steps with C={discrepancyBound} +

+ )} +
+
+
+ )} + + {/* Main Game UI */} + {gamePhase !== 'intro' && ( +
+ {/* Status Bar */} +
+ + Length: {sequence.length} + + + Filled: {filledCount}/{sequence.length} + + + Max Discrepancy: {maxDiscrepancy.value} + {maxDiscrepancy.result && ` (d=${maxDiscrepancy.result.d}, k=${maxDiscrepancy.result.k})`} + + + Bound: C = {discrepancyBound} + + {bestLength && ( + + + Best: {bestLength} + + )} +
+ + {/* Violation Alert */} + {hasViolation && gamePhase === 'writing' && ( + + + Bound Exceeded! + + The subsequence with d={maxDiscrepancy.result?.d}, k={maxDiscrepancy.result?.k} has + discrepancy {maxDiscrepancy.value}, which exceeds your bound of {discrepancyBound}. + + + )} + + + + + + Sequence + + + + Walk + + + + Heatmap + + + + {/* Sequence Builder Tab */} + + + + + Write Your Instructions + {gamePhase === 'writing' && ( + Click to toggle + )} + + + + {/* Sequence Grid */} +
+
+ {sequence.map((val, idx) => { + const pos = idx + 1; + const isHighlighted = highlightedSubsequence.includes(pos) || selectedPositions.includes(pos); + const isViolationPart = maxDiscrepancy.result?.positions.includes(pos) && hasViolation; + + return ( + + ); + })} + + {gamePhase === 'writing' && ( + + )} +
+
+ + {/* Controls */} +
+ {gamePhase === 'writing' && ( + <> + + + + )} +
+
+
+
+ + {/* Walk Visualization Tab */} + + + + Walk Visualization + + + {/* Step size selector */} +
+
+ +
+ {Array.from({ length: Math.min(sequence.length, 8) }, (_, i) => i + 1).map(d => ( + + ))} +
+
+ +
+ + setWalkSpeed(1000 - v[0])} + min={0} + max={900} + step={100} + className="w-24" + /> +
+
+ + {/* Number line visualization */} +
+ {/* Danger zones */} +
+ CLIFF +
+
+ VIPERS +
+ + {/* Safe zone markers */} +
+ {Array.from({ length: discrepancyBound * 2 + 1 }, (_, i) => i - discrepancyBound).map(pos => ( +
+ {pos} +
+ ))} +
+ + {/* Walker position */} +
+
+ 👤 +
+
+
+ + {/* Walk info */} +
+ Position: {walkPosition} + Steps taken: {walkStep} + Following: positions {selectedPositions.slice(0, 5).join(', ')}{selectedPositions.length > 5 ? '...' : ''} +
+ + {/* Walk controls */} +
+ + +
+
+
+
+ + {/* Heatmap Tab */} + + {/* This is actually handled in the walk tab above */} + + + + + + Discrepancy Heatmap + + +

+ Each cell shows the sum of values at positions d, 2d, 3d, ..., kd. + Red cells exceed the bound. +

+ +
+ + + + + {Array.from({ length: Math.min(12, sequence.length) }, (_, i) => i + 1).map(k => ( + + ))} + + + + {Array.from({ length: Math.min(8, sequence.length) }, (_, i) => i + 1).map(d => ( + + + {Array.from({ length: Math.min(12, sequence.length) }, (_, i) => i + 1).map(k => { + const discrepancy = getDiscrepancy(d, k); + const isValid = d * k <= sequence.length; + + return ( + + ); + })} + + ))} + +
d \ k{k}
{d} { + if (isValid) { + setHoveredCell({ d, k }); + const result = allDiscrepancies.find(r => r.d === d && r.k === k); + if (result) { + setHighlightedSubsequence(result.positions); + } + } + }} + onMouseLeave={() => { + setHoveredCell(null); + setHighlightedSubsequence([]); + }} + > + {isValid && discrepancy !== null ? discrepancy : ''} +
+
+ + {hoveredCell && ( +
+ d={hoveredCell.d}, k={hoveredCell.k}: Looking at positions{' '} + {Array.from({ length: hoveredCell.k }, (_, i) => (i + 1) * hoveredCell.d).join(', ')} +
+ )} +
+
+
+
+ + {/* Captor Reveal Phase */} + {gamePhase === 'captor-reveal' && captorChosenD !== null && ( + + + +

The Captor Speaks...

+

+ "Interesting sequence... Let me see... I choose d = {captorChosenD}!" +

+

+ You will follow every {captorChosenD === 1 ? '' : captorChosenD === 2 ? '2nd' : captorChosenD === 3 ? '3rd' : `${captorChosenD}th`} instruction: + positions {Array.from({ length: Math.floor(sequence.length / captorChosenD) }, (_, i) => (i + 1) * captorChosenD).slice(0, 8).join(', ')}... +

+ +
+
+ )} + + {/* Walking Phase */} + {gamePhase === 'walking' && captorChosenD !== null && ( + + +
+

Walking with d = {captorChosenD}...

+

+ Step {prisonerStep} | Position: {prisonerPosition} +

+
+ + {/* Tunnel visualization */} +
+
+ 🏔️ +
+
+ 🐍 +
+ +
+ 🚶 +
+
+
+
+ )} + + {/* Fallen Phase */} + {gamePhase === 'fallen' && ( + + + You Fell! + + After {prisonerStep} steps with d={captorChosenD}, you ended up at position {prisonerPosition}. + The captor found your weakness! Your sequence lasted {sequence.length} instructions. +
+ + +
+
+
+ )} + + {/* Survived Phase (temporary - sequence ended) */} + {gamePhase === 'survived' && ( + + + Sequence Complete! + + Your {sequence.length}-instruction sequence survived d={captorChosenD}! + But remember: no sequence can survive forever. Try making it longer! +
+ + +
+
+
+ )} +
+ )} + + {/* Acknowledgement */} + + +

Credits & History

+

+ This puzzle is based on the Erdős Discrepancy Problem, posed by Paul Erdős in the 1930s + (with a $500 prize!). The "Prisoner's Walk" framing comes from various mathematical expositions, + popularized by James Grime's singingbanana video. +

+

+ 2014: Boris Konev & Alexei Lisitsa proved C=2 has maximum length 1,160 using SAT solvers + (generating a 13GB proof!). 2015: Terence Tao proved that NO infinite sequence + can have bounded discrepancy, settling the problem completely. +

+
+
+ + {/* Social Share */} + {showSocialShare !== false && ( + + )} +
+ ); +}; + +export default ErdosDiscrepancyPuzzle; diff --git a/src/components/InteractiveIndex.tsx b/src/components/InteractiveIndex.tsx index a9ce43c..d2dbe67 100644 --- a/src/components/InteractiveIndex.tsx +++ b/src/components/InteractiveIndex.tsx @@ -288,6 +288,15 @@ const allInteractives: Interactive[] = [ path: '/puzzles/ladybug-clock', theme: 'Puzzles', dateAdded: '2025-01-21' + }, + { + id: 'erdos-discrepancy', + title: 'The Erdős Discrepancy Problem', + description: 'Can you write instructions to survive the tunnel forever? Explore this famous mathematical puzzle through the Prisoner\'s Walk game.', + tags: ['erdos', 'discrepancy', 'sequences', 'game-theory', 'impossibility', 'puzzle', 'number-theory'], + path: '/puzzles/erdos-discrepancy', + theme: 'Puzzles', + dateAdded: '2025-01-22' } ]; diff --git a/src/pages/ErdosDiscrepancyPuzzlePage.tsx b/src/pages/ErdosDiscrepancyPuzzlePage.tsx new file mode 100644 index 0000000..cb323f8 --- /dev/null +++ b/src/pages/ErdosDiscrepancyPuzzlePage.tsx @@ -0,0 +1,16 @@ +import Layout from "@/components/Layout"; +import ErdosDiscrepancyPuzzle from "@/components/ErdosDiscrepancyPuzzle"; + +const ErdosDiscrepancyPuzzlePage = () => { + return ( + +
+
+ +
+
+
+ ); +}; + +export default ErdosDiscrepancyPuzzlePage; diff --git a/src/pages/theme-pages/Puzzles.tsx b/src/pages/theme-pages/Puzzles.tsx index 323270e..5e6b9b1 100644 --- a/src/pages/theme-pages/Puzzles.tsx +++ b/src/pages/theme-pages/Puzzles.tsx @@ -115,6 +115,16 @@ const Puzzles = () => { difficulty: "Intermediate" as const, duration: "5-15 min", participants: "1 player" + }, + { + id: "erdos-discrepancy", + title: "The Erdős Discrepancy Problem", + description: "Can you write instructions to survive the prisoner's tunnel? Explore this famous mathematical puzzle and discover why no sequence can keep you safe forever!", + path: "/puzzles/erdos-discrepancy", + tags: ["Number Theory", "Sequences", "Impossibility", "Game Theory"], + difficulty: "Advanced" as const, + duration: "10-30 min", + participants: "1 player" } ];