diff --git a/src/App.tsx b/src/App.tsx index a4240c1..fadaae4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -47,6 +47,7 @@ import NotFound from "./pages/NotFound"; import GridTilingPuzzlePage from './pages/GridTilingPuzzlePage'; import SunnyLinesPuzzlePage from './pages/SunnyLinesPuzzlePage'; import SikiniaParliamentPuzzlePage from './pages/SikiniaParliamentPuzzlePage'; +import NQueensPuzzlePage from './pages/NQueensPuzzlePage'; const queryClient = new QueryClient(); @@ -102,6 +103,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> diff --git a/src/components/InteractiveIndex.tsx b/src/components/InteractiveIndex.tsx index 8b7fc5d..7886fbd 100644 --- a/src/components/InteractiveIndex.tsx +++ b/src/components/InteractiveIndex.tsx @@ -180,6 +180,15 @@ const allInteractives: Interactive[] = [ path: '/puzzles/sikinia-parliament', theme: 'Puzzles', dateAdded: '2024-12-22' + }, + { + id: 'n-queens', + title: 'N-Queens Puzzle', + description: 'Place as many queens as possible on an n×n chessboard so that no two queens attack each other.', + tags: ['logic', 'chess', 'placement', 'classic'], + path: '/puzzles/n-queens', + theme: 'Puzzles', + dateAdded: '2024-12-22' } ]; diff --git a/src/components/NQueensPuzzle.tsx b/src/components/NQueensPuzzle.tsx new file mode 100644 index 0000000..6026c32 --- /dev/null +++ b/src/components/NQueensPuzzle.tsx @@ -0,0 +1,307 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { Button } from '@/components/ui/button'; +import { Slider } from '@/components/ui/slider'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import { Play, Pause, RotateCcw, Crown, Timer, Trophy } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; +import SocialShare from './SocialShare'; + +interface NQueensPuzzleProps { + showSocialShare?: boolean; +} + +const NQueensPuzzle: React.FC = ({ showSocialShare = false }) => { + const navigate = useNavigate(); + const [boardSize, setBoardSize] = useState([8]); + const [queens, setQueens] = useState>(new Set()); + const [timerStarted, setTimerStarted] = useState(false); + const [timeElapsed, setTimeElapsed] = useState(0); + const [isRunning, setIsRunning] = useState(false); + + // Timer effect + useEffect(() => { + let interval: NodeJS.Timeout; + if (isRunning) { + interval = setInterval(() => { + setTimeElapsed(prev => prev + 1); + }, 1000); + } + return () => clearInterval(interval); + }, [isRunning]); + + const formatTime = (seconds: number) => { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; + }; + + const startTimer = () => { + setTimerStarted(true); + setIsRunning(true); + }; + + const pauseTimer = () => { + setIsRunning(false); + }; + + const resetPuzzle = () => { + setQueens(new Set()); + setTimerStarted(false); + setIsRunning(false); + setTimeElapsed(0); + }; + + const isQueenAt = (row: number, col: number) => { + return queens.has(`${row}-${col}`); + }; + + const isSquareAttacked = useCallback((row: number, col: number) => { + for (const queenPos of queens) { + const [queenRow, queenCol] = queenPos.split('-').map(Number); + + // Same row or column + if (queenRow === row || queenCol === col) { + return true; + } + + // Diagonal + if (Math.abs(queenRow - row) === Math.abs(queenCol - col)) { + return true; + } + } + return false; + }, [queens]); + + const handleSquareClick = (row: number, col: number) => { + if (!timerStarted) return; + + const posKey = `${row}-${col}`; + + if (queens.has(posKey)) { + // Remove queen + const newQueens = new Set(queens); + newQueens.delete(posKey); + setQueens(newQueens); + } else { + // Add queen + const newQueens = new Set(queens); + newQueens.add(posKey); + setQueens(newQueens); + } + }; + + const getSquareStyle = (row: number, col: number) => { + const isLight = (row + col) % 2 === 0; + const hasQueen = isQueenAt(row, col); + const isAttacked = !hasQueen && isSquareAttacked(row, col); + + let baseClasses = "aspect-square border border-border flex items-center justify-center text-2xl font-bold transition-all cursor-pointer relative"; + + if (isLight) { + baseClasses += " bg-amber-100 dark:bg-amber-200"; + } else { + baseClasses += " bg-amber-600 dark:bg-amber-700"; + } + + if (hasQueen) { + baseClasses += " bg-primary/20 ring-2 ring-primary"; + } else if (isAttacked) { + baseClasses += " bg-destructive/10"; + } + + if (!timerStarted) { + baseClasses += " cursor-not-allowed opacity-50"; + } else { + baseClasses += " hover:bg-accent/20"; + } + + return baseClasses; + }; + + const renderSquare = (row: number, col: number) => { + const hasQueen = isQueenAt(row, col); + const isAttacked = !hasQueen && isSquareAttacked(row, col); + + return ( +
handleSquareClick(row, col)} + > + {hasQueen && } + {isAttacked && ( +
+
+
+ )} +
+ ); + }; + + const currentBoardSize = boardSize[0]; + const maxPossibleQueens = currentBoardSize; + const currentQueenCount = queens.size; + + return ( +
+
+ {/* Header */} +
+ +

N-Queens Puzzle

+

+ Place as many queens as possible on the chessboard so that no two queens attack each other. + Queens attack horizontally, vertically, and diagonally. +

+
+ +
+ {/* Controls */} +
+ + + + + Game Controls + + + +
+ + +
+ + + +
+
+ Time: + + {formatTime(timeElapsed)} + +
+ +
+ {!timerStarted ? ( + + ) : ( + + )} + + +
+
+ + + +
+
+ Queens Placed: + + {currentQueenCount}/{maxPossibleQueens} + +
+ + {currentQueenCount === maxPossibleQueens && ( +
+ + Perfect solution found! +
+ )} +
+
+
+ + + + Instructions + + +

• Start the timer to begin placing queens

+

• Click empty squares to place queens

+

• Click placed queens to remove them

+

• Red dots show attacked squares

+

• Try to place {currentBoardSize} queens without conflicts!

+
+
+
+ + {/* Chessboard */} +
+
+
+ {Array.from({ length: currentBoardSize }, (_, row) => + Array.from({ length: currentBoardSize }, (_, col) => + renderSquare(row, col) + ) + )} +
+ + {!timerStarted && ( +
+

+ Start the timer to begin placing queens +

+
+ )} +
+
+
+ + {showSocialShare && ( + + )} +
+
+ ); +}; + +export default NQueensPuzzle; \ No newline at end of file diff --git a/src/pages/NQueensPuzzlePage.tsx b/src/pages/NQueensPuzzlePage.tsx new file mode 100644 index 0000000..5096145 --- /dev/null +++ b/src/pages/NQueensPuzzlePage.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import NQueensPuzzle from '@/components/NQueensPuzzle'; + +const NQueensPuzzlePage = () => { + return ( +
+ +
+ ); +}; + +export default NQueensPuzzlePage; \ No newline at end of file diff --git a/src/pages/theme-pages/Puzzles.tsx b/src/pages/theme-pages/Puzzles.tsx index 98d6df7..3cf3896 100644 --- a/src/pages/theme-pages/Puzzles.tsx +++ b/src/pages/theme-pages/Puzzles.tsx @@ -45,6 +45,16 @@ const Puzzles = () => { difficulty: "Intermediate" as const, duration: "5-15 min", participants: "1 player" + }, + { + id: "n-queens", + title: "N-Queens Puzzle", + description: "Place as many queens as possible on an n×n chessboard so that no two queens attack each other.", + path: "/puzzles/n-queens", + tags: ["Logic", "Chess", "Placement", "Classic"], + difficulty: "Intermediate" as const, + duration: "5-15 min", + participants: "1 player" } ];