interactives/src/components/SubtractionGame.tsx
gpt-engineer-app[bot] 1ff24d145c 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.
2025-08-22 06:24:19 +00:00

207 lines
No EOL
6.9 KiB
TypeScript

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<number[]>([]);
const [currentPlayer, setCurrentPlayer] = useState<'Lata' | 'Raj'>('Lata');
const [winner, setWinner] = useState<string | null>(null);
const [highlightedIndices, setHighlightedIndices] = useState<number[]>([]);
// 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 (
<div className={`min-h-screen transition-colors duration-300 ${getBackgroundColor()}`}>
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold text-center mb-8">The Subtraction Game</h1>
{/* Game Controls */}
<div className="bg-card rounded-lg p-6 mb-8 shadow-lg">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* N Slider */}
<div className="space-y-3">
<label className="text-sm font-medium">
Number of circles (n): {n[0]}
</label>
<Slider
value={n}
onValueChange={setN}
min={3}
max={20}
step={1}
disabled={gameStarted}
className="w-full"
/>
</div>
{/* K Slider */}
<div className="space-y-3">
<label className="text-sm font-medium">
Max selection (k): {k[0]}
</label>
<Slider
value={k}
onValueChange={setK}
min={1}
max={n[0]}
step={1}
disabled={gameStarted}
className="w-full"
/>
</div>
</div>
<div className="text-center">
<Button
onClick={gameStarted ? resetGame : startGame}
size="lg"
className="px-8"
>
{gameStarted ? 'Reset' : 'Play'}
</Button>
</div>
</div>
{/* Game Status */}
{gameStarted && !winner && (
<div className="text-center mb-6">
<h2 className="text-2xl font-semibold">
{currentPlayer}'s Turn
</h2>
<p className="text-muted-foreground mt-2">
Click on any highlighted circle to make your move
</p>
</div>
)}
{/* Winner Display */}
{winner && (
<div className="text-center mb-6">
<h2 className="text-3xl font-bold text-primary">
🎉 {winner} Wins! 🎉
</h2>
<p className="text-muted-foreground mt-2">
{winner} made the last move and won the game!
</p>
</div>
)}
{/* Game Board */}
{circles.length > 0 && (
<div className="bg-card rounded-lg p-8 shadow-lg">
<div className="flex flex-wrap justify-center gap-3">
{circles.map((_, index) => (
<div
key={index}
className={getCircleClass(index)}
onClick={() => handleCircleClick(index)}
title={highlightedIndices.includes(index) ? 'Click to select' : 'Not selectable'}
/>
))}
</div>
{circles.length > 0 && (
<div className="text-center mt-6">
<p className="text-sm text-muted-foreground">
{highlightedIndices.length} circle(s) highlighted Last {Math.min(k[0], circles.length)} positions
</p>
</div>
)}
</div>
)}
{/* Game Rules */}
<div className="bg-card rounded-lg p-6 mt-8 shadow-lg">
<h3 className="text-xl font-semibold mb-4">How to Play</h3>
<ul className="space-y-2 text-muted-foreground">
<li> Set the number of circles (n) and maximum selection range (k)</li>
<li> Players take turns clicking on highlighted circles</li>
<li> When you click a circle, all circles to its right disappear</li>
<li> The last k circles (or fewer if less than k remain) are always highlighted</li>
<li> The player who makes the last move wins!</li>
<li> Background color indicates whose turn it is</li>
</ul>
</div>
</div>
</div>
</div>
);
};
export default SubtractionGame;