Add Plate Swap Puzzle with full functionality and attribution
This commit is contained in:
parent
99482e4314
commit
d9c0c88333
5 changed files with 555 additions and 0 deletions
|
|
@ -32,6 +32,7 @@ import KnightsPuzzlePage from "./pages/KnightsPuzzlePage";
|
||||||
import NimGamePage from "./pages/NimGamePage";
|
import NimGamePage from "./pages/NimGamePage";
|
||||||
import AssistedNimGamePage from "./pages/AssistedNimGamePage";
|
import AssistedNimGamePage from "./pages/AssistedNimGamePage";
|
||||||
import GoldCoinGamePage from "./pages/GoldCoinGamePage";
|
import GoldCoinGamePage from "./pages/GoldCoinGamePage";
|
||||||
|
import PlateSwapPuzzlePage from "./pages/PlateSwapPuzzlePage";
|
||||||
import Foundations from "./pages/theme-pages/discrete-math/Foundations";
|
import Foundations from "./pages/theme-pages/discrete-math/Foundations";
|
||||||
import Proofs from "./pages/theme-pages/discrete-math/Proofs";
|
import Proofs from "./pages/theme-pages/discrete-math/Proofs";
|
||||||
import Counting from "./pages/theme-pages/discrete-math/Counting";
|
import Counting from "./pages/theme-pages/discrete-math/Counting";
|
||||||
|
|
@ -90,6 +91,7 @@ const App = () => (
|
||||||
<Route path="/games/nim" element={<NimGamePage />} />
|
<Route path="/games/nim" element={<NimGamePage />} />
|
||||||
<Route path="/games/assisted-nim" element={<AssistedNimGamePage />} />
|
<Route path="/games/assisted-nim" element={<AssistedNimGamePage />} />
|
||||||
<Route path="/games/gold-coin" element={<GoldCoinGamePage />} />
|
<Route path="/games/gold-coin" element={<GoldCoinGamePage />} />
|
||||||
|
<Route path="/puzzles/plate-swap" element={<PlateSwapPuzzlePage />} />
|
||||||
|
|
||||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,15 @@ const allInteractives: Interactive[] = [
|
||||||
theme: 'Games',
|
theme: 'Games',
|
||||||
dateAdded: '2024-03-15'
|
dateAdded: '2024-03-15'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'plate-swap',
|
||||||
|
title: 'The Plate Swap Puzzle',
|
||||||
|
description: 'Arrange plates around a circular table so each person has their own plate. A challenging permutation puzzle!',
|
||||||
|
tags: ['logic', 'permutation', 'strategy', 'puzzle', 'cycles'],
|
||||||
|
path: '/puzzles/plate-swap',
|
||||||
|
theme: 'Puzzles',
|
||||||
|
dateAdded: '2024-12-19'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'knights-puzzle',
|
id: 'knights-puzzle',
|
||||||
title: 'Knights Exchange Puzzle',
|
title: 'Knights Exchange Puzzle',
|
||||||
|
|
|
||||||
522
src/components/PlateSwapPuzzle.tsx
Normal file
522
src/components/PlateSwapPuzzle.tsx
Normal file
|
|
@ -0,0 +1,522 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
import { RefreshCw, RotateCcw, BookOpen, Trophy, CheckCircle, ArrowLeft, ExternalLink } from 'lucide-react';
|
||||||
|
import SocialShare from './SocialShare';
|
||||||
|
import { useSearchParams, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface Person {
|
||||||
|
id: number;
|
||||||
|
plate: number;
|
||||||
|
isSelected: boolean;
|
||||||
|
isPaired: boolean;
|
||||||
|
pairId: number | null;
|
||||||
|
pairColor: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GameState {
|
||||||
|
people: Person[];
|
||||||
|
moves: number;
|
||||||
|
isComplete: boolean;
|
||||||
|
gameStarted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlateSwapPuzzleProps {
|
||||||
|
showSocialShare?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlateSwapPuzzle: React.FC<PlateSwapPuzzleProps> = ({ showSocialShare = false }) => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const isFromPuzzles = searchParams.get('from') === 'puzzles';
|
||||||
|
|
||||||
|
const [gameState, setGameState] = useState<GameState>(() => {
|
||||||
|
const people: Person[] = [];
|
||||||
|
for (let i = 1; i <= 16; i++) {
|
||||||
|
people.push({
|
||||||
|
id: i,
|
||||||
|
plate: i === 16 ? 1 : i + 1, // 1 has 2's plate, 2 has 3's plate, ..., 16 has 1's plate
|
||||||
|
isSelected: false,
|
||||||
|
isPaired: false,
|
||||||
|
pairId: null,
|
||||||
|
pairColor: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
people,
|
||||||
|
moves: 0,
|
||||||
|
isComplete: false,
|
||||||
|
gameStarted: true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const [showRules, setShowRules] = useState(false);
|
||||||
|
const [showVictory, setShowVictory] = useState(false);
|
||||||
|
|
||||||
|
const resetGame = () => {
|
||||||
|
const people: Person[] = [];
|
||||||
|
for (let i = 1; i <= 16; i++) {
|
||||||
|
people.push({
|
||||||
|
id: i,
|
||||||
|
plate: i === 16 ? 1 : i + 1, // 1 has 2's plate, 2 has 3's plate, ..., 16 has 1's plate
|
||||||
|
isSelected: false,
|
||||||
|
isPaired: false,
|
||||||
|
pairId: null,
|
||||||
|
pairColor: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setGameState({
|
||||||
|
people,
|
||||||
|
moves: 0,
|
||||||
|
isComplete: false,
|
||||||
|
gameStarted: true
|
||||||
|
});
|
||||||
|
setShowVictory(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pairColors = [
|
||||||
|
'#ef4444', // red
|
||||||
|
'#3b82f6', // blue
|
||||||
|
'#f59e0b', // amber
|
||||||
|
'#8b5cf6', // violet
|
||||||
|
'#ec4899', // pink
|
||||||
|
'#06b6d4', // cyan
|
||||||
|
'#84cc16', // lime
|
||||||
|
'#f97316' // orange
|
||||||
|
];
|
||||||
|
|
||||||
|
const handlePersonClick = (personId: number) => {
|
||||||
|
if (!gameState.gameStarted || gameState.isComplete) return;
|
||||||
|
|
||||||
|
const person = gameState.people.find(p => p.id === personId);
|
||||||
|
if (!person || person.plate === person.id) return; // Can't select people who already have their plate
|
||||||
|
|
||||||
|
setGameState(prev => {
|
||||||
|
const updatedPeople = [...prev.people];
|
||||||
|
const clickedPerson = updatedPeople.find(p => p.id === personId)!;
|
||||||
|
|
||||||
|
// If this person is already paired, unselect them
|
||||||
|
if (clickedPerson.isPaired) {
|
||||||
|
const pairId = clickedPerson.pairId!;
|
||||||
|
const pairedPerson = updatedPeople.find(p => p.id === pairId)!;
|
||||||
|
|
||||||
|
clickedPerson.isSelected = false;
|
||||||
|
clickedPerson.isPaired = false;
|
||||||
|
clickedPerson.pairId = null;
|
||||||
|
clickedPerson.pairColor = null;
|
||||||
|
pairedPerson.isSelected = false;
|
||||||
|
pairedPerson.isPaired = false;
|
||||||
|
pairedPerson.pairId = null;
|
||||||
|
pairedPerson.pairColor = null;
|
||||||
|
|
||||||
|
return { ...prev, people: updatedPeople };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this person is selected, unselect them
|
||||||
|
if (clickedPerson.isSelected) {
|
||||||
|
clickedPerson.isSelected = false;
|
||||||
|
return { ...prev, people: updatedPeople };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's already a selected person
|
||||||
|
const selectedPerson = updatedPeople.find(p => p.isSelected && !p.isPaired);
|
||||||
|
|
||||||
|
if (selectedPerson) {
|
||||||
|
// Find the next available color
|
||||||
|
const usedColors = updatedPeople.filter(p => p.pairColor !== null).map(p => p.pairColor);
|
||||||
|
const availableColor = pairColors.find(color => !usedColors.includes(color)) || pairColors[0];
|
||||||
|
|
||||||
|
// Pair them up
|
||||||
|
selectedPerson.isSelected = false;
|
||||||
|
selectedPerson.isPaired = true;
|
||||||
|
selectedPerson.pairId = personId;
|
||||||
|
selectedPerson.pairColor = availableColor;
|
||||||
|
clickedPerson.isSelected = false;
|
||||||
|
clickedPerson.isPaired = true;
|
||||||
|
clickedPerson.pairId = selectedPerson.id;
|
||||||
|
clickedPerson.pairColor = availableColor;
|
||||||
|
} else {
|
||||||
|
// Select this person
|
||||||
|
clickedPerson.isSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...prev, people: updatedPeople };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSwap = () => {
|
||||||
|
if (!gameState.gameStarted || gameState.isComplete) return;
|
||||||
|
|
||||||
|
const pairedPeople = gameState.people.filter(p => p.isPaired);
|
||||||
|
if (pairedPeople.length === 0) return;
|
||||||
|
|
||||||
|
setGameState(prev => {
|
||||||
|
const updatedPeople = [...prev.people];
|
||||||
|
|
||||||
|
// Process each paired person and swap with their pair
|
||||||
|
const processedPairs = new Set<number>();
|
||||||
|
|
||||||
|
pairedPeople.forEach(person => {
|
||||||
|
if (processedPairs.has(person.id)) return; // Skip if already processed
|
||||||
|
|
||||||
|
const person1 = updatedPeople.find(p => p.id === person.id)!;
|
||||||
|
const person2 = updatedPeople.find(p => p.id === person.pairId!)!;
|
||||||
|
|
||||||
|
// Swap plates
|
||||||
|
const tempPlate = person1.plate;
|
||||||
|
person1.plate = person2.plate;
|
||||||
|
person2.plate = tempPlate;
|
||||||
|
|
||||||
|
// Reset pairing state
|
||||||
|
person1.isSelected = false;
|
||||||
|
person1.isPaired = false;
|
||||||
|
person1.pairId = null;
|
||||||
|
person1.pairColor = null;
|
||||||
|
person2.isSelected = false;
|
||||||
|
person2.isPaired = false;
|
||||||
|
person2.pairId = null;
|
||||||
|
person2.pairColor = null;
|
||||||
|
|
||||||
|
// Mark both as processed
|
||||||
|
processedPairs.add(person1.id);
|
||||||
|
processedPairs.add(person2.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if puzzle is complete
|
||||||
|
const isComplete = updatedPeople.every(p => p.plate === p.id);
|
||||||
|
|
||||||
|
if (isComplete) {
|
||||||
|
setShowVictory(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
people: updatedPeople,
|
||||||
|
moves: prev.moves + 1,
|
||||||
|
isComplete
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const canSwap = () => {
|
||||||
|
const pairedPeople = gameState.people.filter(p => p.isPaired);
|
||||||
|
const peopleWithoutCorrectPlate = gameState.people.filter(p => p.plate !== p.id);
|
||||||
|
|
||||||
|
// Check if all people without correct plates are paired
|
||||||
|
const allIncorrectPeoplePaired = peopleWithoutCorrectPlate.every(p => p.isPaired);
|
||||||
|
|
||||||
|
return pairedPeople.length >= 2 && pairedPeople.length % 2 === 0 && allIncorrectPeoplePaired;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPerson = (person: Person) => {
|
||||||
|
const isCorrect = person.plate === person.id;
|
||||||
|
const isSelected = person.isSelected;
|
||||||
|
const isPaired = person.isPaired;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={person.id}
|
||||||
|
className={`
|
||||||
|
relative w-20 h-20 rounded-full border-4 flex items-center justify-center cursor-pointer
|
||||||
|
transition-all duration-200 hover:scale-105
|
||||||
|
${isCorrect ? 'border-green-500 bg-green-50' : 'border-gray-300 bg-white'}
|
||||||
|
${isSelected ? 'border-dashed border-blue-500 bg-blue-50 shadow-lg' : ''}
|
||||||
|
${isPaired && person.pairColor ? `border-[${person.pairColor}] bg-[${person.pairColor}]/10 shadow-lg` : ''}
|
||||||
|
${person.plate === person.id ? 'cursor-default hover:scale-100' : ''}
|
||||||
|
`}
|
||||||
|
style={{
|
||||||
|
borderColor: isPaired && person.pairColor ? person.pairColor : undefined,
|
||||||
|
backgroundColor: isPaired && person.pairColor ? `${person.pairColor}10` : undefined
|
||||||
|
}}
|
||||||
|
onClick={() => handlePersonClick(person.id)}
|
||||||
|
>
|
||||||
|
{/* Plate number */}
|
||||||
|
<div className="text-base font-bold text-gray-700">
|
||||||
|
{person.plate}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTable = () => {
|
||||||
|
const people = gameState.people;
|
||||||
|
if (people.length === 0) return null;
|
||||||
|
|
||||||
|
// Calculate positions for a circular table
|
||||||
|
const radius = 240;
|
||||||
|
const centerX = 300;
|
||||||
|
const centerY = 300;
|
||||||
|
const outerRadius = 310; // Outer radius for seat numbers
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-[600px] h-[600px] mx-auto">
|
||||||
|
<div className="absolute inset-0 border-4 border-gray-300 rounded-full bg-gray-50"></div>
|
||||||
|
|
||||||
|
{/* Seat numbers on outer boundary */}
|
||||||
|
{people.map((person, index) => {
|
||||||
|
const angle = (index * 360) / 16 - 90; // Start from top
|
||||||
|
const x = centerX + outerRadius * Math.cos((angle * Math.PI) / 180);
|
||||||
|
const y = centerY + outerRadius * Math.sin((angle * Math.PI) / 180);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`seat-${person.id}`}
|
||||||
|
className="absolute w-8 h-8 bg-gray-800 text-white text-sm rounded-full flex items-center justify-center font-bold transform -translate-x-4 -translate-y-4"
|
||||||
|
style={{ left: x, top: y }}
|
||||||
|
>
|
||||||
|
{person.id}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Plates */}
|
||||||
|
{people.map((person, index) => {
|
||||||
|
const angle = (index * 360) / 16 - 90; // Start from top
|
||||||
|
const x = centerX + radius * Math.cos((angle * Math.PI) / 180);
|
||||||
|
const y = centerY + radius * Math.sin((angle * Math.PI) / 180);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={person.id}
|
||||||
|
className="absolute transform -translate-x-10 -translate-y-10"
|
||||||
|
style={{ left: x, top: y }}
|
||||||
|
>
|
||||||
|
{renderPerson(person)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl mx-auto p-6 space-y-6">
|
||||||
|
{/* Back to Puzzles button - only show when coming from puzzles */}
|
||||||
|
{isFromPuzzles && (
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/themes/puzzles')}
|
||||||
|
className="text-primary hover:text-primary/80 font-medium flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4" />
|
||||||
|
Go back to Puzzles
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Card className="shadow-lg">
|
||||||
|
<CardHeader className="text-center pb-6">
|
||||||
|
<CardTitle className="flex items-center justify-center gap-3 text-3xl">
|
||||||
|
<RefreshCw className="w-8 h-8 text-blue-500" />
|
||||||
|
The Plate Swap Puzzle
|
||||||
|
<RefreshCw className="w-8 h-8 text-blue-500" />
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="text-lg max-w-2xl mx-auto">
|
||||||
|
Arrange the plates so each person has their own plate. <br /> Pair people up and swap their plates!
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-8">
|
||||||
|
{/* Game Controls */}
|
||||||
|
<div className="flex justify-center items-center gap-4">
|
||||||
|
<Button onClick={() => setShowRules(true)} variant="outline" size="lg" className="px-8">
|
||||||
|
<BookOpen className="w-4 h-4 mr-2" />
|
||||||
|
Rules
|
||||||
|
</Button>
|
||||||
|
<Button onClick={resetGame} variant="outline" size="lg" className="px-8">
|
||||||
|
<RotateCcw className="w-4 h-4 mr-2" />
|
||||||
|
Reset Puzzle
|
||||||
|
</Button>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-semibold text-lg">Moves:</span>
|
||||||
|
<Badge className="text-lg px-4 py-2 bg-blue-600 text-white">
|
||||||
|
{gameState.moves}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Game Board */}
|
||||||
|
<div className="space-y-8">
|
||||||
|
{renderTable()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Instructions */}
|
||||||
|
<div className="text-center mt-8">
|
||||||
|
<div className="inline-block bg-gray-50 px-6 py-3 rounded-lg border">
|
||||||
|
<p className="text-sm text-gray-700 font-medium">
|
||||||
|
{gameState.isComplete ? (
|
||||||
|
"Congratulations! All plates are in their correct positions!"
|
||||||
|
) : (
|
||||||
|
"Click on people to pair them up. All people without correct plates must be paired before swapping."
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Swap Button */}
|
||||||
|
{!gameState.isComplete && (
|
||||||
|
<div className="flex justify-center mt-4">
|
||||||
|
<Button
|
||||||
|
onClick={handleSwap}
|
||||||
|
disabled={!canSwap()}
|
||||||
|
size="lg"
|
||||||
|
className={`px-8 ${canSwap() ? 'bg-green-600 hover:bg-green-700' : 'bg-gray-400 cursor-not-allowed'}`}
|
||||||
|
>
|
||||||
|
SWAP
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Legend */}
|
||||||
|
<div className="space-y-3 mt-8">
|
||||||
|
<h3 className="font-semibold text-lg text-center">Legend</h3>
|
||||||
|
<div className="flex flex-wrap justify-center gap-6 text-sm">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-5 h-5 border-2 border-gray-300 bg-white rounded-full"></div>
|
||||||
|
<span>Person with wrong plate</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-5 h-5 border-2 border-green-500 bg-green-50 rounded-full"></div>
|
||||||
|
<span>Person with correct plate</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-5 h-5 border-2 border-dashed border-blue-500 bg-blue-50 rounded-full"></div>
|
||||||
|
<span>Selected for pairing</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-5 h-5 border-2 border-gray-300 bg-gray-50 rounded-full"></div>
|
||||||
|
<span>Paired up (color-coded)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* Rules Dialog */}
|
||||||
|
<Dialog open={showRules} onOpenChange={setShowRules}>
|
||||||
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<BookOpen className="w-5 h-5" />
|
||||||
|
The Plate Swap Puzzle Rules
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-6 text-sm">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-2 text-lg">Objective</h4>
|
||||||
|
<p className="text-gray-700">Arrange the plates so that each person has their own plate (Person 1 has Plate 1, Person 2 has Plate 2, etc.).</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-2 text-lg">Initial Setup</h4>
|
||||||
|
<p className="text-gray-700">Each person starts with the next person's plate: Person 1 has Plate 2, Person 2 has Plate 3, ..., Person 16 has Plate 1.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-2 text-lg">How to Play</h4>
|
||||||
|
<ol className="list-decimal list-inside space-y-2 ml-4 text-gray-700">
|
||||||
|
<li><strong>Pair people up:</strong> Click on two people to select them for pairing.</li>
|
||||||
|
<li><strong>Swap plates:</strong> Click the SWAP button to exchange the plates of all paired people.</li>
|
||||||
|
<li><strong>Repeat:</strong> Continue pairing and swapping until everyone has their correct plate.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-2 text-lg">Rules</h4>
|
||||||
|
<ul className="list-disc list-inside space-y-2 ml-4 text-gray-700">
|
||||||
|
<li>People who already have their correct plate cannot be selected.</li>
|
||||||
|
<li>You must pair an even number of people to swap.</li>
|
||||||
|
<li>The SWAP button is only enabled when everyone is properly paired.</li>
|
||||||
|
<li>Try to solve the puzzle in as few moves as possible!</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-2 text-lg">Strategy Tips</h4>
|
||||||
|
<ul className="list-disc list-inside space-y-2 ml-4 text-gray-700">
|
||||||
|
<li>Look for cycles in the plate arrangement</li>
|
||||||
|
<li>Try to fix multiple people at once with strategic swaps</li>
|
||||||
|
<li>Plan your moves ahead to minimize the total number of swaps</li>
|
||||||
|
<li>Remember that each swap affects all paired people simultaneously</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Victory Dialog */}
|
||||||
|
<Dialog open={showVictory} onOpenChange={setShowVictory}>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center justify-center gap-2">
|
||||||
|
<Trophy className="w-6 h-6 text-yellow-500" />
|
||||||
|
Puzzle Complete!
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="text-center space-y-6">
|
||||||
|
<div className="flex items-center justify-center gap-3">
|
||||||
|
<CheckCircle className="w-10 h-10 text-green-500" />
|
||||||
|
<span className="text-2xl font-bold text-green-600">
|
||||||
|
Congratulations!
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 text-lg">
|
||||||
|
You solved the puzzle in {gameState.moves} moves!
|
||||||
|
</p>
|
||||||
|
{gameState.moves > 2 && (
|
||||||
|
<p className="text-blue-600 font-medium">
|
||||||
|
Can you solve it in fewer moves?
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<Button onClick={resetGame} className="w-full" size="lg">
|
||||||
|
Play Again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Attribution */}
|
||||||
|
<div className="pt-4 border-t border-outline">
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
I learned about this puzzle from{" "}
|
||||||
|
<a
|
||||||
|
href="https://sites.google.com/view/kumarakash/home"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-accent hover:underline inline-flex items-center gap-1"
|
||||||
|
>
|
||||||
|
Akash Kumar
|
||||||
|
<ExternalLink className="w-3 h-3" />
|
||||||
|
</a>
|
||||||
|
{" "}over some very fun discussions! He in turn learned of this puzzle from{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.tcs.tifr.res.in/~jaikumar/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-accent hover:underline inline-flex items-center gap-1"
|
||||||
|
>
|
||||||
|
Jaikumar Radhakrishnan
|
||||||
|
<ExternalLink className="w-3 h-3" />
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Social Share Section */}
|
||||||
|
{showSocialShare && (
|
||||||
|
<SocialShare
|
||||||
|
title="The Plate Swap Puzzle"
|
||||||
|
description="A challenging puzzle where you need to arrange plates around a circular table!"
|
||||||
|
url={window.location.href}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlateSwapPuzzle;
|
||||||
12
src/pages/PlateSwapPuzzlePage.tsx
Normal file
12
src/pages/PlateSwapPuzzlePage.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PlateSwapPuzzle from '@/components/PlateSwapPuzzle';
|
||||||
|
|
||||||
|
const PlateSwapPuzzlePage = () => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<PlateSwapPuzzle showSocialShare={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlateSwapPuzzlePage;
|
||||||
|
|
@ -6,6 +6,16 @@ const Puzzles = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const puzzles = [
|
const puzzles = [
|
||||||
|
{
|
||||||
|
id: "plate-swap",
|
||||||
|
title: "The Plate Swap Puzzle",
|
||||||
|
description: "Arrange plates around a circular table so each person has their own plate. A challenging permutation puzzle!",
|
||||||
|
path: "/puzzles/plate-swap",
|
||||||
|
tags: ["Logic", "Permutation", "Strategy"],
|
||||||
|
difficulty: "Advanced" as const,
|
||||||
|
duration: "10-30 min",
|
||||||
|
participants: "1 player"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "knights-puzzle",
|
id: "knights-puzzle",
|
||||||
title: "Knights Exchange Puzzle",
|
title: "Knights Exchange Puzzle",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue