Add pebble game interactive
Add a new interactive game where users place pebbles on an integer line from 1 to 35. The rules are: - A pebble can only be placed on position `i` if position `i-1` has a pebble. - Position 1 can always have a pebble placed on it. - Users start with `n` pebbles (chosen via a slider from 2 to 5). - The goal is to find the largest integer position for which a pebble can be placed.
This commit is contained in:
parent
927db0fcd1
commit
8917e1a66f
3 changed files with 255 additions and 0 deletions
|
|
@ -10,6 +10,7 @@ import NimGame from '@/components/NimGame';
|
||||||
import AssistedNimGame from '@/components/AssistedNimGame';
|
import AssistedNimGame from '@/components/AssistedNimGame';
|
||||||
import GoldCoinGame from '@/components/GoldCoinGame';
|
import GoldCoinGame from '@/components/GoldCoinGame';
|
||||||
import GuessingGame from '@/components/GuessingGame';
|
import GuessingGame from '@/components/GuessingGame';
|
||||||
|
import PebblePlacementGame from '@/components/PebblePlacementGame';
|
||||||
|
|
||||||
interface Game {
|
interface Game {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -20,6 +21,13 @@ interface Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
const games: 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',
|
id: 'guessing-game',
|
||||||
title: 'Number Guessing Game',
|
title: 'Number Guessing Game',
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import Layout from '@/components/Layout';
|
||||||
import BinarySearchTrick from '@/components/BinarySearchTrick';
|
import BinarySearchTrick from '@/components/BinarySearchTrick';
|
||||||
import TernarySearchTrick from '@/components/TernarySearchTrick';
|
import TernarySearchTrick from '@/components/TernarySearchTrick';
|
||||||
import GuessingGame from '@/components/GuessingGame';
|
import GuessingGame from '@/components/GuessingGame';
|
||||||
|
import PebblePlacementGame from '@/components/PebblePlacementGame';
|
||||||
|
|
||||||
interface Interactive {
|
interface Interactive {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -38,6 +39,13 @@ const interactives: Interactive[] = [
|
||||||
tags: ['ternary', 'magic', 'numbers', 'trick', 'data-structures', 'search'],
|
tags: ['ternary', 'magic', 'numbers', 'trick', 'data-structures', 'search'],
|
||||||
component: TernarySearchTrick
|
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 InteractiveGallery = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
|
|
||||||
239
src/components/PebblePlacementGame.tsx
Normal file
239
src/components/PebblePlacementGame.tsx
Normal file
|
|
@ -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<PebblePlacementGameProps> = ({ showSocialShare = false }) => {
|
||||||
|
const [numPebbles, setNumPebbles] = useState<number>(3);
|
||||||
|
const [pebblesInHand, setPebblesInHand] = useState<number>(3);
|
||||||
|
const [placedPebbles, setPlacedPebbles] = useState<Set<number>>(new Set());
|
||||||
|
const [furthestReached, setFurthestReached] = useState<number>(0);
|
||||||
|
const [gameStarted, setGameStarted] = useState<boolean>(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 (
|
||||||
|
<div className="max-w-6xl mx-auto p-6 space-y-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="text-center">
|
||||||
|
<CardTitle className="text-2xl font-bold">Pebble Placement Strategy Game</CardTitle>
|
||||||
|
<CardDescription className="text-lg">
|
||||||
|
Place your pebbles strategically to reach the furthest position possible!
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
{/* Game Setup */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">Number of Pebbles: {numPebbles}</label>
|
||||||
|
<Slider
|
||||||
|
value={[numPebbles]}
|
||||||
|
onValueChange={(value) => setNumPebbles(value[0])}
|
||||||
|
min={2}
|
||||||
|
max={5}
|
||||||
|
step={1}
|
||||||
|
className="w-full max-w-xs"
|
||||||
|
disabled={gameStarted}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!gameStarted ? (
|
||||||
|
<Button onClick={startGame} size="lg">
|
||||||
|
Start Game
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Button onClick={resetGame} variant="outline" size="sm">
|
||||||
|
<RotateCcw className="w-4 h-4 mr-2" />
|
||||||
|
Reset Game
|
||||||
|
</Button>
|
||||||
|
<Badge variant="secondary">
|
||||||
|
Pebbles in hand: {pebblesInHand}
|
||||||
|
</Badge>
|
||||||
|
{furthestReached > 0 && (
|
||||||
|
<Badge variant="default" className="bg-green-600">
|
||||||
|
<Trophy className="w-3 h-3 mr-1" />
|
||||||
|
Furthest: {furthestReached}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rules */}
|
||||||
|
<div className="bg-muted p-4 rounded-lg space-y-2">
|
||||||
|
<h3 className="font-semibold text-sm">Rules:</h3>
|
||||||
|
<ul className="text-sm space-y-1 text-muted-foreground">
|
||||||
|
<li>• You can always place a pebble on position 1</li>
|
||||||
|
<li>• You can place a pebble on position i only if position (i-1) has a pebble</li>
|
||||||
|
<li>• You can remove a pebble from position i only if position (i-1) has a pebble</li>
|
||||||
|
<li>• Goal: Place a pebble on the largest possible position</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Game Board */}
|
||||||
|
{gameStarted && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-semibold">Positions 1-35</h3>
|
||||||
|
|
||||||
|
{/* Rows of 7 positions each */}
|
||||||
|
{[0, 1, 2, 3, 4].map((row) => (
|
||||||
|
<div key={row} className="flex items-center gap-2">
|
||||||
|
<span className="text-sm text-muted-foreground w-8">
|
||||||
|
{row * 7 + 1}-{Math.min((row + 1) * 7, 35)}:
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{Array.from({ length: 7 }, (_, i) => {
|
||||||
|
const position = row * 7 + i + 1;
|
||||||
|
if (position > 35) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={position}
|
||||||
|
className={getPositionStyle(position)}
|
||||||
|
onClick={() => 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}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Game Status */}
|
||||||
|
{gameStarted && pebblesInHand === 0 && (
|
||||||
|
<div className="text-center p-4 bg-accent rounded-lg">
|
||||||
|
<h3 className="text-lg font-semibold">All pebbles placed!</h3>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Your furthest position: <span className="font-semibold text-foreground">{furthestReached}</span>
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-muted-foreground mt-2">
|
||||||
|
Can you rearrange your pebbles to reach even further?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{showSocialShare && (
|
||||||
|
<SocialShare
|
||||||
|
title="Pebble Placement Strategy Game"
|
||||||
|
description="Test your strategic thinking with this pebble placement puzzle!"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PebblePlacementGame;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue