From 1ff24d145ca734facb137be1ae5e38eb851932a7 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 06:24:19 +0000 Subject: [PATCH] Add Subtraction Game interactive Implement "The Subtraction Game" interactive with sliders for n and k, a play/reset button, turn-based gameplay between two players, and visual feedback including confetti for the winner. --- package-lock.json | 11 ++ package.json | 1 + src/App.tsx | 2 + src/components/InteractiveIndex.tsx | 9 ++ src/components/SubtractionGame.tsx | 207 ++++++++++++++++++++++++++++ src/pages/SubtractionGamePage.tsx | 7 + 6 files changed, 237 insertions(+) create mode 100644 src/components/SubtractionGame.tsx create mode 100644 src/pages/SubtractionGamePage.tsx diff --git a/package-lock.json b/package-lock.json index 7e686ba..79ad20f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "@radix-ui/react-tooltip": "^1.1.4", "@tanstack/react-query": "^5.56.2", "@types/qrcode": "^1.5.5", + "canvas-confetti": "^1.9.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", @@ -3480,6 +3481,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-confetti": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.3.tgz", + "integrity": "sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==", + "license": "ISC", + "funding": { + "type": "donate", + "url": "https://www.paypal.me/kirilvatev" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", diff --git a/package.json b/package.json index 50578b1..47d0209 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@radix-ui/react-tooltip": "^1.1.4", "@tanstack/react-query": "^5.56.2", "@types/qrcode": "^1.5.5", + "canvas-confetti": "^1.9.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", diff --git a/src/App.tsx b/src/App.tsx index e056966..d51111c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -52,6 +52,7 @@ import NQueensPuzzlePage from './pages/NQueensPuzzlePage'; import RulesOfInferencePlaygroundPage from './pages/RulesOfInferencePlaygroundPage'; import PebblePlacementGamePage from './pages/PebblePlacementGamePage'; import BulgarianSolitairePage from './pages/BulgarianSolitairePage'; +import SubtractionGamePage from './pages/SubtractionGamePage'; const queryClient = new QueryClient(); @@ -107,6 +108,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/InteractiveIndex.tsx b/src/components/InteractiveIndex.tsx index 70ae9b6..2c8dbee 100644 --- a/src/components/InteractiveIndex.tsx +++ b/src/components/InteractiveIndex.tsx @@ -127,6 +127,15 @@ const allInteractives: Interactive[] = [ theme: 'Puzzles', dateAdded: '2024-12-22' }, + { + id: 'subtraction-game', + title: 'The Subtraction Game', + description: 'A strategic two-player game where players take turns removing circles, with the last player to move winning.', + tags: ['strategy', 'two-player', 'game-theory', 'mathematical-games', 'nim-variant'], + path: '/subtraction-game', + theme: 'Games', + dateAdded: '2024-12-22' + }, { id: 'knights-puzzle', title: 'Knights Exchange Puzzle', diff --git a/src/components/SubtractionGame.tsx b/src/components/SubtractionGame.tsx new file mode 100644 index 0000000..822b4cd --- /dev/null +++ b/src/components/SubtractionGame.tsx @@ -0,0 +1,207 @@ +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Slider } from '@/components/ui/slider'; +import confetti from 'canvas-confetti'; + +const SubtractionGame = () => { + const [n, setN] = useState([10]); + const [k, setK] = useState([3]); + const [gameStarted, setGameStarted] = useState(false); + const [circles, setCircles] = useState([]); + const [currentPlayer, setCurrentPlayer] = useState<'Lata' | 'Raj'>('Lata'); + const [winner, setWinner] = useState(null); + const [highlightedIndices, setHighlightedIndices] = useState([]); + + // Update k slider max when n changes + useEffect(() => { + if (k[0] > n[0]) { + setK([n[0]]); + } + }, [n, k]); + + // Calculate highlighted indices + useEffect(() => { + if (circles.length > 0) { + const lastKIndices = []; + const start = Math.max(0, circles.length - k[0]); + for (let i = start; i < circles.length; i++) { + lastKIndices.push(i); + } + setHighlightedIndices(lastKIndices); + } + }, [circles, k]); + + const startGame = () => { + const initialCircles = Array.from({ length: n[0] }, (_, i) => i); + setCircles(initialCircles); + setGameStarted(true); + setCurrentPlayer('Lata'); + setWinner(null); + }; + + const resetGame = () => { + setGameStarted(false); + setCircles([]); + setWinner(null); + setCurrentPlayer('Lata'); + setHighlightedIndices([]); + }; + + const handleCircleClick = (index: number) => { + if (!gameStarted || winner || !highlightedIndices.includes(index)) { + return; + } + + // Remove all circles to the right of clicked circle + const newCircles = circles.slice(0, index); + setCircles(newCircles); + + // Check if game is over + if (newCircles.length === 0) { + setWinner(currentPlayer); + setGameStarted(false); + // Trigger confetti + confetti({ + particleCount: 100, + spread: 70, + origin: { y: 0.6 } + }); + return; + } + + // Switch player + setCurrentPlayer(currentPlayer === 'Lata' ? 'Raj' : 'Lata'); + }; + + const getBackgroundColor = () => { + if (!gameStarted || winner) return 'bg-background'; + return currentPlayer === 'Lata' ? 'bg-red-100 dark:bg-red-950' : 'bg-purple-100 dark:bg-purple-950'; + }; + + const getCircleClass = (index: number) => { + const baseClass = 'w-8 h-8 rounded-full border-2 border-black cursor-pointer transition-colors'; + const isHighlighted = highlightedIndices.includes(index); + const fillClass = isHighlighted ? 'bg-yellow-200 hover:bg-yellow-300' : 'bg-gray-300 hover:bg-gray-400'; + return `${baseClass} ${fillClass}`; + }; + + return ( +
+
+
+

The Subtraction Game

+ + {/* Game Controls */} +
+
+ {/* N Slider */} +
+ + +
+ + {/* K Slider */} +
+ + +
+
+ +
+ +
+
+ + {/* Game Status */} + {gameStarted && !winner && ( +
+

+ {currentPlayer}'s Turn +

+

+ Click on any highlighted circle to make your move +

+
+ )} + + {/* Winner Display */} + {winner && ( +
+

+ 🎉 {winner} Wins! 🎉 +

+

+ {winner} made the last move and won the game! +

+
+ )} + + {/* Game Board */} + {circles.length > 0 && ( +
+
+ {circles.map((_, index) => ( +
handleCircleClick(index)} + title={highlightedIndices.includes(index) ? 'Click to select' : 'Not selectable'} + /> + ))} +
+ + {circles.length > 0 && ( +
+

+ {highlightedIndices.length} circle(s) highlighted • Last {Math.min(k[0], circles.length)} positions +

+
+ )} +
+ )} + + {/* Game Rules */} +
+

How to Play

+
    +
  • • Set the number of circles (n) and maximum selection range (k)
  • +
  • • Players take turns clicking on highlighted circles
  • +
  • • When you click a circle, all circles to its right disappear
  • +
  • • The last k circles (or fewer if less than k remain) are always highlighted
  • +
  • • The player who makes the last move wins!
  • +
  • • Background color indicates whose turn it is
  • +
+
+
+
+
+ ); +}; + +export default SubtractionGame; \ No newline at end of file diff --git a/src/pages/SubtractionGamePage.tsx b/src/pages/SubtractionGamePage.tsx new file mode 100644 index 0000000..302ca9c --- /dev/null +++ b/src/pages/SubtractionGamePage.tsx @@ -0,0 +1,7 @@ +import SubtractionGame from '@/components/SubtractionGame'; + +const SubtractionGamePage = () => { + return ; +}; + +export default SubtractionGamePage; \ No newline at end of file