From c8d3475104cb13fa8f7f9f5f80174a2620eb5f9b Mon Sep 17 00:00:00 2001 From: Neeldhara Misra Date: Tue, 22 Jul 2025 10:47:49 +0530 Subject: [PATCH] Add Zeckendorf Representation interactive under Discrete Math > Foundations --- src/App.tsx | 2 + src/components/InteractiveIndex.tsx | 9 + src/components/ZeckendorfGame.tsx | 238 ++++++++++++++++++ src/pages/ZeckendorfGamePage.tsx | 13 + .../theme-pages/discrete-math/Foundations.tsx | 8 + 5 files changed, 270 insertions(+) create mode 100644 src/components/ZeckendorfGame.tsx create mode 100644 src/pages/ZeckendorfGamePage.tsx diff --git a/src/App.tsx b/src/App.tsx index 8cb6b39..3209589 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -34,6 +34,7 @@ import AssistedNimGamePage from "./pages/AssistedNimGamePage"; import GoldCoinGamePage from "./pages/GoldCoinGamePage"; import PlateSwapPuzzlePage from "./pages/PlateSwapPuzzlePage"; import ChessboardRepaintPuzzlePage from "./pages/ChessboardRepaintPuzzlePage"; +import ZeckendorfGamePage from "./pages/ZeckendorfGamePage"; import Foundations from "./pages/theme-pages/discrete-math/Foundations"; import Proofs from "./pages/theme-pages/discrete-math/Proofs"; import Counting from "./pages/theme-pages/discrete-math/Counting"; @@ -94,6 +95,7 @@ const App = () => ( } /> } /> } /> + } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> diff --git a/src/components/InteractiveIndex.tsx b/src/components/InteractiveIndex.tsx index fe43a7f..301410f 100644 --- a/src/components/InteractiveIndex.tsx +++ b/src/components/InteractiveIndex.tsx @@ -46,6 +46,15 @@ const allInteractives: Interactive[] = [ theme: 'Discrete Math', dateAdded: '2024-01-17' }, + { + id: 'zeckendorf-game', + title: 'Zeckendorf Representation', + description: 'Learn how numbers are represented using Fibonacci numbers with the unique Zeckendorf representation (no consecutive Fibonacci numbers).', + tags: ['fibonacci', 'numbers', 'representation', 'zeckendorf', 'combinatorics'], + path: '/discrete-math/foundations/zeckendorf', + theme: 'Discrete Math', + dateAdded: '2024-12-22' + }, { id: 'binary-search-trick', title: 'Binary Search Magic Trick', diff --git a/src/components/ZeckendorfGame.tsx b/src/components/ZeckendorfGame.tsx new file mode 100644 index 0000000..9fcf67e --- /dev/null +++ b/src/components/ZeckendorfGame.tsx @@ -0,0 +1,238 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import SocialShare from '@/components/SocialShare'; + +// First 8 Fibonacci numbers: 1, 1, 2, 3, 5, 8, 13, 21 +const FIBONACCI_NUMBERS = [21, 13, 8, 5, 3, 2, 1, 1]; + +interface ZeckendorfGameProps { + showSocialShare?: boolean; +} + +const ZeckendorfGame: React.FC = ({ showSocialShare = true }) => { + const [targetNumber, setTargetNumber] = useState(0); + const [flippedCards, setFlippedCards] = useState(new Array(8).fill(false)); + const [currentSum, setCurrentSum] = useState(0); + const [timer, setTimer] = useState(0); + const [isGameActive, setIsGameActive] = useState(false); + const [isGameWon, setIsGameWon] = useState(false); + const [hasGameStarted, setHasGameStarted] = useState(false); + const [highScore, setHighScore] = useState(null); + + // Load high score from localStorage on component mount + useEffect(() => { + const savedHighScore = localStorage.getItem('zeckendorf-game-high-score'); + if (savedHighScore) { + setHighScore(parseInt(savedHighScore)); + } + }, []); + + // Generate a valid target number (sum of some Fibonacci numbers) + const generateValidTarget = useCallback(() => { + // Generate a random combination of Fibonacci numbers + const numTerms = Math.floor(Math.random() * 4) + 2; // 2-5 terms + let target = 0; + let usedIndices = new Set(); + + for (let i = 0; i < numTerms; i++) { + let index; + do { + index = Math.floor(Math.random() * 8); + } while (usedIndices.has(index)); + + usedIndices.add(index); + target += FIBONACCI_NUMBERS[index]; + } + + return target; + }, []); + + // Start new game + const startNewGame = useCallback(() => { + const newTarget = generateValidTarget(); + setTargetNumber(newTarget); + setFlippedCards(new Array(8).fill(false)); + setCurrentSum(0); + setTimer(0); + setIsGameActive(true); + setIsGameWon(false); + setHasGameStarted(true); + }, [generateValidTarget]); + + // Start game for the first time + const startGame = () => { + startNewGame(); + }; + + // Timer effect + useEffect(() => { + let interval: NodeJS.Timeout; + if (isGameActive && !isGameWon) { + interval = setInterval(() => { + setTimer(prev => prev + 0.1); + }, 100); + } + return () => clearInterval(interval); + }, [isGameActive, isGameWon]); + + // Check for win condition + useEffect(() => { + if (currentSum === targetNumber && isGameActive) { + setIsGameActive(false); + setIsGameWon(true); + + // Update high score + const currentTime = Math.round(timer * 10) / 10; + if (highScore === null || currentTime < highScore) { + setHighScore(currentTime); + localStorage.setItem('zeckendorf-game-high-score', currentTime.toString()); + } + } + }, [currentSum, targetNumber, isGameActive, timer, highScore]); + + // Calculate current sum + useEffect(() => { + const sum = flippedCards.reduce((acc, isFlipped, index) => { + return isFlipped ? acc + FIBONACCI_NUMBERS[index] : acc; + }, 0); + setCurrentSum(sum); + }, [flippedCards]); + + // Check if current selection violates Zeckendorf rule (no consecutive Fibonacci numbers) + const hasConsecutiveFibonacci = () => { + for (let i = 0; i < flippedCards.length - 1; i++) { + if (flippedCards[i] && flippedCards[i + 1]) { + return true; + } + } + return false; + }; + + const toggleCard = (index: number) => { + if (!isGameActive || isGameWon) return; + + setFlippedCards(prev => { + const newFlipped = [...prev]; + newFlipped[index] = !newFlipped[index]; + return newFlipped; + }); + }; + + const isInvalidSelection = hasConsecutiveFibonacci(); + + return ( +
+
+

Zeckendorf Representation

+

Click the cards to represent the target number as a sum of Fibonacci numbers (no consecutive numbers allowed)!

+ {highScore !== null && ( + + Best Time: {highScore}s + + )} +
+ + {/* Cards representing Fibonacci numbers */} +
+ {FIBONACCI_NUMBERS.map((fib, index) => ( +
+ toggleCard(index)} + > + + {flippedCards[index] ? ( + {fib} + ) : ( +
+
+
+ )} +
+
+ {fib} +
+ ))} +
+ + {/* Warning for invalid selection */} + {isInvalidSelection && ( +
+ + ⚠️ Invalid: Cannot use consecutive Fibonacci numbers! + +
+ )} + + {/* Sum display boxes */} +
+ + +

Current Sum

+
targetNumber ? "text-red-600" : isInvalidSelection ? "text-orange-600" : "text-foreground"}`}> + {currentSum} +
+
+
+ + +

Target Sum

+
+ {!hasGameStarted ? "🎯" : targetNumber} +
+
+
+
+ + {/* Timer and controls */} +
+
+ Time: {Math.round(timer * 10) / 10}s +
+ + {isGameWon && ( +
+
Congratulations! 🎉
+
+ Your score: {Math.round(timer * 10) / 10} seconds +
+ {highScore === Math.round(timer * 10) / 10 && ( + + New High Score! 🏆 + + )} +
+ )} + + +
+ + {/* Information about Zeckendorf representation */} +
+

About Zeckendorf Representation

+

+ Every positive integer can be uniquely represented as a sum of Fibonacci numbers, + where no two consecutive Fibonacci numbers are used. This is called the Zeckendorf representation. +

+
+ + {showSocialShare && ( + + )} +
+ ); +}; + +export default ZeckendorfGame; \ No newline at end of file diff --git a/src/pages/ZeckendorfGamePage.tsx b/src/pages/ZeckendorfGamePage.tsx new file mode 100644 index 0000000..130ecfa --- /dev/null +++ b/src/pages/ZeckendorfGamePage.tsx @@ -0,0 +1,13 @@ +import ZeckendorfGame from '@/components/ZeckendorfGame'; + +const ZeckendorfGamePage = () => { + return ( +
+
+ +
+
+ ); +}; + +export default ZeckendorfGamePage; \ No newline at end of file diff --git a/src/pages/theme-pages/discrete-math/Foundations.tsx b/src/pages/theme-pages/discrete-math/Foundations.tsx index 15f90bf..5f40fe1 100644 --- a/src/pages/theme-pages/discrete-math/Foundations.tsx +++ b/src/pages/theme-pages/discrete-math/Foundations.tsx @@ -6,6 +6,7 @@ import InteractiveCard from "@/components/InteractiveCard"; import BinaryNumberGame from "@/components/BinaryNumberGame"; import TernaryNumberGame from "@/components/TernaryNumberGame"; import BalancedTernaryGame from "@/components/BalancedTernaryGame"; +import ZeckendorfGame from "@/components/ZeckendorfGame"; interface Interactive { id: string; title: string; @@ -35,6 +36,13 @@ const interactives: Interactive[] = [{ tags: ["balanced-ternary", "numbers", "conversion", "representation", "base-3", "signed-digits"], component: BalancedTernaryGame, greenScreenPath: "/balanced-ternary-game" +}, { + id: "zeckendorf-game", + title: "Zeckendorf Representation", + description: "Learn how numbers are represented using Fibonacci numbers with the unique Zeckendorf representation (no consecutive Fibonacci numbers).", + tags: ["fibonacci", "numbers", "representation", "zeckendorf", "combinatorics"], + component: ZeckendorfGame, + greenScreenPath: "/discrete-math/foundations/zeckendorf" }]; const Foundations = () => { const [searchTerm, setSearchTerm] = useState("");