From 2e75dfc30e0b2f3822f5cfeaaab7ecfc034f6e72 Mon Sep 17 00:00:00 2001
From: "gpt-engineer-app[bot]"
<159125892+gpt-engineer-app[bot]@users.noreply.github.com>
Date: Wed, 23 Jul 2025 20:10:51 +0000
Subject: [PATCH] Add Sikinian Parliament puzzle
Create a new playable puzzle based on the Sikinian Parliament problem. The puzzle features 10 individuals in a circular layout with randomly assigned enemy relationships (at most three per person). Users can color individuals blue or pink, and edges between individuals of the same color are highlighted in red if they have more than one same-colored enemy. The puzzle includes start, timer, high score, and share features, and is added to the Puzzles page.
---
src/App.tsx | 2 +
src/components/SikiniaParliamentPuzzle.tsx | 356 +++++++++++++++++++++
src/pages/SikiniaParliamentPuzzlePage.tsx | 12 +
src/pages/theme-pages/Puzzles.tsx | 10 +
4 files changed, 380 insertions(+)
create mode 100644 src/components/SikiniaParliamentPuzzle.tsx
create mode 100644 src/pages/SikiniaParliamentPuzzlePage.tsx
diff --git a/src/App.tsx b/src/App.tsx
index 7702395..a4240c1 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -46,6 +46,7 @@ import Graphs from "./pages/theme-pages/discrete-math/Graphs";
import NotFound from "./pages/NotFound";
import GridTilingPuzzlePage from './pages/GridTilingPuzzlePage';
import SunnyLinesPuzzlePage from './pages/SunnyLinesPuzzlePage';
+import SikiniaParliamentPuzzlePage from './pages/SikiniaParliamentPuzzlePage';
const queryClient = new QueryClient();
@@ -98,6 +99,7 @@ const App = () => (
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/src/components/SikiniaParliamentPuzzle.tsx b/src/components/SikiniaParliamentPuzzle.tsx
new file mode 100644
index 0000000..40a2d37
--- /dev/null
+++ b/src/components/SikiniaParliamentPuzzle.tsx
@@ -0,0 +1,356 @@
+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 } 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(() => {
+ setEdges(currentEdges => {
+ return currentEdges.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 = currentEdges.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 = currentEdges.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 };
+ });
+ });
+ }, [people]);
+
+ // 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(() => {
+ updateViolations();
+ checkCompletion();
+ }, [people, updateViolations, checkCompletion]);
+
+ // 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';
+ }
+ };
+
+ 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!"}
+
+
+ )}
+
+