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 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',
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
|
|
|
|||
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