diff --git a/src/components/BalancedTernaryGame.tsx b/src/components/BalancedTernaryGame.tsx new file mode 100644 index 0000000..feeb10d --- /dev/null +++ b/src/components/BalancedTernaryGame.tsx @@ -0,0 +1,273 @@ +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 { Plus, Minus } from 'lucide-react'; +import SocialShare from '@/components/SocialShare'; + +const POWERS_OF_THREE = [2187, 729, 243, 81, 27, 9, 3, 1]; // 3^7 to 3^0 + +interface BalancedTernaryGameProps { + showSocialShare?: boolean; +} + +const BalancedTernaryGame: React.FC = ({ showSocialShare = true }) => { + const [targetNumber, setTargetNumber] = useState(0); + const [cardValues, setCardValues] = useState(new Array(8).fill(0)); // -1, 0, or 1 for each position + 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('balanced-ternary-game-high-score'); + if (savedHighScore) { + setHighScore(parseInt(savedHighScore)); + } + }, []); + + // Generate a random number that can be represented in balanced ternary + const generateRandomTarget = () => { + // Generate random balanced ternary digits (-1, 0, or 1) for each position + const balancedTernaryDigits = Array.from({ length: 8 }, () => Math.floor(Math.random() * 3) - 1); + // Convert to decimal + const result = balancedTernaryDigits.reduce((sum, digit, index) => sum + digit * POWERS_OF_THREE[index], 0); + // Make sure we don't get 0 as target + return result === 0 ? generateRandomTarget() : result; + }; + + // Start new game + const startNewGame = useCallback(() => { + const newTarget = generateRandomTarget(); + setTargetNumber(newTarget); + setCardValues(new Array(8).fill(0)); + setCurrentSum(0); + setTimer(0); + setIsGameActive(true); + setIsGameWon(false); + setHasGameStarted(true); + }, []); + + // 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('balanced-ternary-game-high-score', currentTime.toString()); + } + } + }, [currentSum, targetNumber, isGameActive, timer, highScore]); + + // Calculate current sum + useEffect(() => { + const sum = cardValues.reduce((acc, value, index) => { + return acc + value * POWERS_OF_THREE[index]; + }, 0); + setCurrentSum(sum); + }, [cardValues]); + + const incrementCard = (index: number) => { + if (!isGameActive || isGameWon) return; + + setCardValues(prev => { + const newValues = [...prev]; + newValues[index] = Math.min(1, newValues[index] + 1); + return newValues; + }); + }; + + const decrementCard = (index: number) => { + if (!isGameActive || isGameWon) return; + + setCardValues(prev => { + const newValues = [...prev]; + newValues[index] = Math.max(-1, newValues[index] - 1); + return newValues; + }); + }; + + // Convert number to balanced ternary string for display + const toBalancedTernary = (num: number) => { + if (num === 0) return '0'; + + let result = ''; + let n = Math.abs(num); + const isNegative = num < 0; + + while (n > 0) { + const remainder = n % 3; + n = Math.floor(n / 3); + + if (remainder === 0) { + result = '0' + result; + } else if (remainder === 1) { + result = '1' + result; + } else { // remainder === 2 + result = 'T' + result; // T represents -1 + n += 1; // carry over + } + } + + return isNegative ? result.split('').map(d => d === '1' ? 'T' : d === 'T' ? '1' : d).join('') : result; + }; + + const getCardColor = (value: number) => { + if (value === -1) return 'bg-gradient-to-br from-red-100 to-red-200 border-red-300'; + if (value === 0) return 'bg-gradient-to-br from-gray-100 to-gray-200 border-gray-300'; + if (value === 1) return 'bg-gradient-to-br from-green-100 to-green-200 border-green-300'; + return ''; + }; + + const getTextColor = (value: number) => { + if (value === -1) return 'text-red-800'; + if (value === 0) return 'text-gray-500'; + if (value === 1) return 'text-green-800'; + return ''; + }; + + const getValueDisplay = (value: number) => { + if (value === -1) return 'T'; + if (value === 0) return '0'; + if (value === 1) return '1'; + return ''; + }; + + return ( +
+
+

Balanced Ternary Number Representation

+

Use + and - buttons to add or subtract powers of three. Digits can be T (-1), 0, or 1.

+ {highScore !== null && ( + + Best Time: {highScore}s + + )} +
+ + {/* Cards representing powers of three */} +
+ {POWERS_OF_THREE.map((power, index) => ( +
+ {/* Add button */} + + + + + + {getValueDisplay(cardValues[index])} + + + {cardValues[index] !== 0 && `${cardValues[index] === 1 ? '+' : '-'}${power}`} + + + + + {/* Subtract button */} + + + 3^{7-index} +
+ ))} +
+ + {/* Sum display boxes */} +
+ + +

Current Sum

+
Math.abs(targetNumber) ? "text-red-600" : "text-foreground" + }`}> + {currentSum} +
+
+
+ + +

Target Sum

+
+ {!hasGameStarted ? "🎯" : isGameWon ? toBalancedTernary(targetNumber) : 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! 🏆 + + )} +
+ )} + + +
+ + {showSocialShare && ( + + )} +
+ ); +}; + +export default BalancedTernaryGame; \ 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 e161bc1..2c90e66 100644 --- a/src/pages/theme-pages/discrete-math/Foundations.tsx +++ b/src/pages/theme-pages/discrete-math/Foundations.tsx @@ -5,6 +5,7 @@ import Layout from "@/components/Layout"; import InteractiveCard from "@/components/InteractiveCard"; import BinaryNumberGame from "@/components/BinaryNumberGame"; import TernaryNumberGame from "@/components/TernaryNumberGame"; +import BalancedTernaryGame from "@/components/BalancedTernaryGame"; interface Interactive { id: string; title: string; @@ -27,6 +28,13 @@ const interactives: Interactive[] = [{ tags: ["ternary", "numbers", "conversion", "representation", "base-3"], component: TernaryNumberGame, greenScreenPath: "/ternary-number-game" +}, { + id: "balanced-ternary-game", + title: "Balanced Ternary Number Representation", + description: "Explore balanced ternary using digits T (-1), 0, and 1. Add or subtract powers of three to match the target number.", + tags: ["balanced-ternary", "numbers", "conversion", "representation", "base-3", "signed-digits"], + component: BalancedTernaryGame, + greenScreenPath: "/balanced-ternary-game" }]; const Foundations = () => { const [searchTerm, setSearchTerm] = useState("");