diff --git a/src/components/GamesGallery.tsx b/src/components/GamesGallery.tsx index d148fb4..e85f5d5 100644 --- a/src/components/GamesGallery.tsx +++ b/src/components/GamesGallery.tsx @@ -10,6 +10,7 @@ import NimGame from '@/components/NimGame'; import AssistedNimGame from '@/components/AssistedNimGame'; import GoldCoinGame from '@/components/GoldCoinGame'; import GuessingGame from '@/components/GuessingGame'; +import PebblePlacementGame from '@/components/PebblePlacementGame'; interface Game { id: string; @@ -20,6 +21,13 @@ interface Game { } const games: Game[] = [ + { + id: 'pebble-placement-game', + title: 'Pebble Placement Strategy Game', + description: 'Test your strategic thinking! Place pebbles following specific rules to reach the furthest position possible.', + tags: ['strategy', 'logic', 'game', 'placement', 'optimization', 'puzzle'], + component: PebblePlacementGame + }, { id: 'guessing-game', title: 'Number Guessing Game', diff --git a/src/components/InteractiveGallery.tsx b/src/components/InteractiveGallery.tsx index 1086712..0856788 100644 --- a/src/components/InteractiveGallery.tsx +++ b/src/components/InteractiveGallery.tsx @@ -7,6 +7,7 @@ import Layout from '@/components/Layout'; import BinarySearchTrick from '@/components/BinarySearchTrick'; import TernarySearchTrick from '@/components/TernarySearchTrick'; import GuessingGame from '@/components/GuessingGame'; +import PebblePlacementGame from '@/components/PebblePlacementGame'; interface Interactive { id: string; @@ -38,6 +39,13 @@ const interactives: Interactive[] = [ tags: ['ternary', 'magic', 'numbers', 'trick', 'data-structures', 'search'], component: TernarySearchTrick }, + { + id: 'pebble-placement-game', + title: 'Pebble Placement Strategy Game', + description: 'Test your strategic thinking! Place pebbles following specific rules to reach the furthest position possible.', + tags: ['strategy', 'logic', 'game', 'placement', 'optimization', 'puzzle'], + component: PebblePlacementGame + }, ]; const InteractiveGallery = () => { const [searchTerm, setSearchTerm] = useState(''); diff --git a/src/components/PebblePlacementGame.tsx b/src/components/PebblePlacementGame.tsx new file mode 100644 index 0000000..9369c40 --- /dev/null +++ b/src/components/PebblePlacementGame.tsx @@ -0,0 +1,239 @@ +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Slider } from '@/components/ui/slider'; +import { Badge } from '@/components/ui/badge'; +import { RotateCcw, Trophy } from 'lucide-react'; +import SocialShare from '@/components/SocialShare'; + +interface PebblePlacementGameProps { + showSocialShare?: boolean; +} + +const PebblePlacementGame: React.FC = ({ showSocialShare = false }) => { + const [numPebbles, setNumPebbles] = useState(3); + const [pebblesInHand, setPebblesInHand] = useState(3); + const [placedPebbles, setPlacedPebbles] = useState>(new Set()); + const [furthestReached, setFurthestReached] = useState(0); + const [gameStarted, setGameStarted] = useState(false); + + // Reset game when number of pebbles changes + useEffect(() => { + resetGame(); + }, [numPebbles]); + + // Update furthest reached whenever pebbles are placed + useEffect(() => { + if (placedPebbles.size > 0) { + const maxPosition = Math.max(...Array.from(placedPebbles)); + if (maxPosition > furthestReached) { + setFurthestReached(maxPosition); + } + } + }, [placedPebbles, furthestReached]); + + const resetGame = () => { + setPebblesInHand(numPebbles); + setPlacedPebbles(new Set()); + setFurthestReached(0); + setGameStarted(false); + }; + + const startGame = () => { + setGameStarted(true); + }; + + const canPlacePebble = (position: number): boolean => { + // Can always place on position 1 + if (position === 1) return true; + // For other positions, predecessor must have a pebble + return placedPebbles.has(position - 1); + }; + + const canRemovePebble = (position: number): boolean => { + // Can only remove if pebble is placed and predecessor has pebble (or position 1) + if (!placedPebbles.has(position)) return false; + if (position === 1) return true; + return placedPebbles.has(position - 1); + }; + + const handlePositionClick = (position: number) => { + if (!gameStarted) return; + + const newPlacedPebbles = new Set(placedPebbles); + + if (placedPebbles.has(position)) { + // Try to remove pebble + if (canRemovePebble(position)) { + // Check if removing this pebble would make any later pebbles invalid + const pebblesAfter = Array.from(placedPebbles).filter(p => p > position); + let canRemove = true; + + for (const laterPebble of pebblesAfter) { + if (laterPebble === position + 1) { + // Direct successor would become invalid + canRemove = false; + break; + } + } + + if (canRemove) { + newPlacedPebbles.delete(position); + setPlacedPebbles(newPlacedPebbles); + setPebblesInHand(pebblesInHand + 1); + } + } + } else { + // Try to place pebble + if (pebblesInHand > 0 && canPlacePebble(position)) { + newPlacedPebbles.add(position); + setPlacedPebbles(newPlacedPebbles); + setPebblesInHand(pebblesInHand - 1); + } + } + }; + + const getPositionState = (position: number): 'placed' | 'available' | 'blocked' => { + if (placedPebbles.has(position)) return 'placed'; + if (canPlacePebble(position) && pebblesInHand > 0) return 'available'; + return 'blocked'; + }; + + const getPositionStyle = (position: number): string => { + const state = getPositionState(position); + const baseClasses = "w-8 h-8 border-2 rounded-full flex items-center justify-center text-sm font-medium cursor-pointer transition-all duration-200"; + + switch (state) { + case 'placed': + return `${baseClasses} bg-primary text-primary-foreground border-primary hover:bg-primary/90`; + case 'available': + return `${baseClasses} bg-secondary text-secondary-foreground border-secondary hover:bg-secondary/80 hover:scale-110`; + case 'blocked': + return `${baseClasses} bg-muted text-muted-foreground border-muted cursor-not-allowed opacity-50`; + } + }; + + return ( +
+ + + Pebble Placement Strategy Game + + Place your pebbles strategically to reach the furthest position possible! + + + + {/* Game Setup */} +
+
+ + setNumPebbles(value[0])} + min={2} + max={5} + step={1} + className="w-full max-w-xs" + disabled={gameStarted} + /> +
+ + {!gameStarted ? ( + + ) : ( +
+ + + Pebbles in hand: {pebblesInHand} + + {furthestReached > 0 && ( + + + Furthest: {furthestReached} + + )} +
+ )} +
+ + {/* Rules */} +
+

Rules:

+
    +
  • • You can always place a pebble on position 1
  • +
  • • You can place a pebble on position i only if position (i-1) has a pebble
  • +
  • • You can remove a pebble from position i only if position (i-1) has a pebble
  • +
  • • Goal: Place a pebble on the largest possible position
  • +
+
+ + {/* Game Board */} + {gameStarted && ( +
+

Positions 1-35

+ + {/* Rows of 7 positions each */} + {[0, 1, 2, 3, 4].map((row) => ( +
+ + {row * 7 + 1}-{Math.min((row + 1) * 7, 35)}: + +
+ {Array.from({ length: 7 }, (_, i) => { + const position = row * 7 + i + 1; + if (position > 35) return null; + + return ( +
handlePositionClick(position)} + title={ + placedPebbles.has(position) + ? `Pebble placed on ${position}. Click to remove.` + : canPlacePebble(position) && pebblesInHand > 0 + ? `Click to place pebble on ${position}` + : `Cannot place pebble on ${position}` + } + > + {position} +
+ ); + })} +
+
+ ))} +
+ )} + + {/* Game Status */} + {gameStarted && pebblesInHand === 0 && ( +
+

All pebbles placed!

+

+ Your furthest position: {furthestReached} +

+

+ Can you rearrange your pebbles to reach even further? +

+
+ )} +
+
+ + {showSocialShare && ( + + )} +
+ ); +}; + +export default PebblePlacementGame; \ No newline at end of file