import React, { useState, useEffect, useCallback } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Play, RotateCcw, Trophy, Clock, Info, ChevronDown, ChevronUp, User, UserCircle, Users, UserCheck, UserX, Baby, PersonStanding, Smile, Frown, Meh } from 'lucide-react'; import SocialShare from '@/components/SocialShare'; interface Person { id: number; x: number; y: number; color: 'grey' | 'blue' | 'pink'; } interface Edge { from: number; to: number; isViolation: boolean; } interface SikiniaParliamentPuzzleProps { showSocialShare?: boolean; } const SikiniaParliamentPuzzle: React.FC = ({ showSocialShare = false }) => { const [people, setPeople] = useState([]); const [edges, setEdges] = useState([]); const [gameStarted, setGameStarted] = useState(false); const [timer, setTimer] = useState(0); const [isRunning, setIsRunning] = useState(false); const [bestTime, setBestTime] = useState(null); const [isComplete, setIsComplete] = useState(false); const [showExplanation, setShowExplanation] = useState(false); // Initialize people in circular layout useEffect(() => { const centerX = 200; const centerY = 200; const radius = 150; const newPeople: Person[] = []; for (let i = 0; i < 10; i++) { const angle = (i * 2 * Math.PI) / 10 - Math.PI / 2; // Start from top const x = centerX + radius * Math.cos(angle); const y = centerY + radius * Math.sin(angle); newPeople.push({ id: i, x, y, color: 'grey' }); } setPeople(newPeople); }, []); // Timer effect useEffect(() => { let interval: NodeJS.Timeout; if (isRunning) { interval = setInterval(() => { setTimer(timer => timer + 1); }, 1000); } return () => clearInterval(interval); }, [isRunning]); // Generate random graph with max degree 3 const generateRandomGraph = useCallback(() => { const newEdges: Edge[] = []; const degrees = new Array(10).fill(0); // Try to create edges while respecting max degree constraint for (let attempts = 0; attempts < 100 && newEdges.length < 15; attempts++) { const from = Math.floor(Math.random() * 10); const to = Math.floor(Math.random() * 10); if (from !== to && degrees[from] < 3 && degrees[to] < 3 && !newEdges.some(e => (e.from === from && e.to === to) || (e.from === to && e.to === from))) { newEdges.push({ from, to, isViolation: false }); degrees[from]++; degrees[to]++; } } setEdges(newEdges); }, []); // Check for violations and update edge colors const updateViolations = useCallback(() => { const updatedEdges = edges.map(edge => { const person1 = people.find(p => p.id === edge.from); const person2 = people.find(p => p.id === edge.to); if (!person1 || !person2 || person1.color === 'grey' || person2.color === 'grey') { return { ...edge, isViolation: false }; } if (person1.color === person2.color) { // Check if either person has more than 1 enemy of their color const person1Enemies = edges.filter(e => (e.from === person1.id || e.to === person1.id) && e !== edge ).map(e => e.from === person1.id ? e.to : e.from) .map(id => people.find(p => p.id === id)) .filter(p => p && p.color === person1.color); const person2Enemies = edges.filter(e => (e.from === person2.id || e.to === person2.id) && e !== edge ).map(e => e.from === person2.id ? e.to : e.from) .map(id => people.find(p => p.id === id)) .filter(p => p && p.color === person2.color); return { ...edge, isViolation: person1Enemies.length >= 1 || person2Enemies.length >= 1 }; } return { ...edge, isViolation: false }; }); setEdges(updatedEdges); return updatedEdges; }, [people, edges]); // Check if puzzle is solved const checkCompletion = useCallback(() => { if (!gameStarted) return; const allColored = people.every(p => p.color !== 'grey'); const noViolations = edges.every(e => !e.isViolation); if (allColored && noViolations && !isComplete) { setIsComplete(true); setIsRunning(false); if (!bestTime || timer < bestTime) { setBestTime(timer); localStorage.setItem('sikinia-best-time', timer.toString()); } } }, [people, edges, gameStarted, isComplete, timer, bestTime]); useEffect(() => { const updatedEdges = updateViolations(); // Check completion with the updated edges if (gameStarted && !isComplete) { const allColored = people.every(p => p.color !== 'grey'); const noViolations = updatedEdges.every(e => !e.isViolation); if (allColored && noViolations) { setIsComplete(true); setIsRunning(false); if (!bestTime || timer < bestTime) { setBestTime(timer); localStorage.setItem('sikinia-best-time', timer.toString()); } } } }, [people, updateViolations, gameStarted, isComplete, timer, bestTime]); // Load best time from localStorage useEffect(() => { const saved = localStorage.getItem('sikinia-best-time'); if (saved) { setBestTime(parseInt(saved)); } }, []); const startGame = () => { generateRandomGraph(); setGameStarted(true); setIsRunning(true); setTimer(0); setIsComplete(false); setPeople(people.map(p => ({ ...p, color: 'grey' }))); }; const resetGame = () => { setGameStarted(false); setIsRunning(false); setTimer(0); setIsComplete(false); setEdges([]); setPeople(people.map(p => ({ ...p, color: 'grey' }))); }; const colorPerson = (id: number) => { if (!gameStarted) return; setPeople(people.map(person => { if (person.id === id) { const nextColor = person.color === 'grey' ? 'blue' : person.color === 'blue' ? 'pink' : 'grey'; return { ...person, color: nextColor }; } return person; })); }; const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; }; const getPersonColor = (color: string) => { switch (color) { case 'blue': return '#3b82f6'; case 'pink': return '#ec4899'; default: return '#6b7280'; } }; const getPersonIcon = (id: number) => { const icons = [User, UserCircle, Users, UserCheck, UserX, Baby, PersonStanding, Smile, Frown, Meh]; return icons[id]; }; return (
Parliament of Sikinia Puzzle Graph Theory

Separate parliament members into two houses so each has at most one enemy in their house

{showExplanation && ( Goal: Color all 10 parliament members blue or pink such that each member has at most one enemy of their own color in their house.
How to play: Click grey icons to make them blue, blue to make them pink, pink to make them grey. Red edges indicate violations (someone has more than one enemy of their color).
Mathematical insight: This demonstrates that any graph with maximum degree 3 is 2-colorable in a way that each vertex has at most one neighbor of its color.
)}
{!gameStarted ? ( ) : ( )}
{formatTime(timer)}
{bestTime && (
{formatTime(bestTime)}
)}
{isComplete && ( 🎉 Congratulations! You've successfully separated the parliament in {formatTime(timer)}! {timer === bestTime && " New best time!"} )}
{/* Draw edges */} {edges.map((edge, index) => { const person1 = people.find(p => p.id === edge.from); const person2 = people.find(p => p.id === edge.to); if (!person1 || !person2) return null; return ( ); })} {/* Draw people */} {people.map((person) => ( colorPerson(person.id)} /> {(() => { const IconComponent = getPersonIcon(person.id); return ( ); })()} ))}
Unassigned
House 1
House 2
{/* Social Share */} {showSocialShare && ( )} ); }; export default SikiniaParliamentPuzzle;