diff --git a/src/App.tsx b/src/App.tsx index f6533d3..8cb6b39 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,6 +33,7 @@ import NimGamePage from "./pages/NimGamePage"; import AssistedNimGamePage from "./pages/AssistedNimGamePage"; import GoldCoinGamePage from "./pages/GoldCoinGamePage"; import PlateSwapPuzzlePage from "./pages/PlateSwapPuzzlePage"; +import ChessboardRepaintPuzzlePage from "./pages/ChessboardRepaintPuzzlePage"; import Foundations from "./pages/theme-pages/discrete-math/Foundations"; import Proofs from "./pages/theme-pages/discrete-math/Proofs"; import Counting from "./pages/theme-pages/discrete-math/Counting"; @@ -90,8 +91,9 @@ const App = () => ( } /> } /> } /> - } /> - } /> + } /> + } /> + } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> diff --git a/src/components/ChessboardRepaintPuzzle.tsx b/src/components/ChessboardRepaintPuzzle.tsx new file mode 100644 index 0000000..7598f55 --- /dev/null +++ b/src/components/ChessboardRepaintPuzzle.tsx @@ -0,0 +1,376 @@ +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 ChessboardRepaintPuzzleProps { + showSocialShare?: boolean; +} + +const ChessboardRepaintPuzzle: React.FC = ({ showSocialShare = false }) => { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const isFromPuzzles = searchParams.get('from') === 'puzzles'; + + // Initialize 8x8 chessboard with alternating colors + const initializeBoard = (): boolean[][] => { + const board: boolean[][] = []; + for (let row = 0; row < 8; row++) { + board[row] = []; + for (let col = 0; col < 8; col++) { + // true = black, false = white + board[row][col] = (row + col) % 2 === 0; + } + } + return board; + }; + + const [board, setBoard] = useState(initializeBoard); + const [moves, setMoves] = useState(0); + const [isComplete, setIsComplete] = useState(false); + const [showRules, setShowRules] = useState(false); + const [showVictory, setShowVictory] = useState(false); + + const resetPuzzle = () => { + setBoard(initializeBoard()); + setMoves(0); + setIsComplete(false); + setShowVictory(false); + }; + + const countBlackSquares = (board: boolean[][]): number => { + return board.flat().filter(square => square).length; + }; + + const repaintRow = (rowIndex: number) => { + const newBoard = board.map(row => [...row]); + for (let col = 0; col < 8; col++) { + newBoard[rowIndex][col] = !newBoard[rowIndex][col]; + } + setBoard(newBoard); + setMoves(moves + 1); + + const blackCount = countBlackSquares(newBoard); + if (blackCount === 1) { + setIsComplete(true); + setShowVictory(true); + } + }; + + const repaintColumn = (colIndex: number) => { + const newBoard = board.map(row => [...row]); + for (let row = 0; row < 8; row++) { + newBoard[row][colIndex] = !newBoard[row][colIndex]; + } + setBoard(newBoard); + setMoves(moves + 1); + + const blackCount = countBlackSquares(newBoard); + if (blackCount === 1) { + setIsComplete(true); + setShowVictory(true); + } + }; + + const repaint2x2Square = (startRow: number, startCol: number) => { + const newBoard = board.map(row => [...row]); + for (let row = startRow; row < startRow + 2 && row < 8; row++) { + for (let col = startCol; col < startCol + 2 && col < 8; col++) { + newBoard[row][col] = !newBoard[row][col]; + } + } + setBoard(newBoard); + setMoves(moves + 1); + + const blackCount = countBlackSquares(newBoard); + if (blackCount === 1) { + setIsComplete(true); + setShowVictory(true); + } + }; + + const renderSquare = (row: number, col: number) => { + const isBlack = board[row][col]; + return ( + + ); + }; + + const render2x2Button = (startRow: number, startCol: number) => { + return ( + repaint2x2Square(startRow, startCol)} + variant="outline" + size="sm" + className="w-6 h-6 p-0 text-xs" + disabled={isComplete} + > + 2×2 + + ); + }; + + return ( + + {/* Back to Puzzles button - only show when coming from puzzles */} + {isFromPuzzles && ( + + navigate('/themes/puzzles')} + className="text-primary hover:text-primary/80 font-medium flex items-center gap-2" + > + + Go back to Puzzles + + + )} + + + + + + Chessboard Repaint Puzzle + + + + Repaint rows, columns, or 2×2 squares to achieve exactly one black square! + + + + {/* Game Controls */} + + setShowRules(true)} variant="outline" size="lg" className="px-8"> + + Rules + + + + Reset Puzzle + + + Moves: + + {moves} + + + + Black Squares: + + {countBlackSquares(board)} + + + + + {/* Chessboard */} + + + {/* Row buttons */} + + {Array.from({ length: 8 }, (_, i) => ( + repaintRow(i)} + variant="outline" + size="sm" + className="w-12 h-6 text-xs" + disabled={isComplete} + > + Row {i + 1} + + ))} + + + {/* Column buttons */} + + {Array.from({ length: 8 }, (_, i) => ( + repaintColumn(i)} + variant="outline" + size="sm" + className="w-12 h-6 text-xs" + disabled={isComplete} + > + Col {i + 1} + + ))} + + + {/* 2x2 square buttons */} + + {Array.from({ length: 7 }, (_, row) => + Array.from({ length: 7 }, (_, col) => ( + + {render2x2Button(row, col)} + + )) + )} + + + {/* Chessboard grid */} + + {board.map((row, rowIndex) => + row.map((square, colIndex) => renderSquare(rowIndex, colIndex)) + )} + + + + + {/* Instructions */} + + + + {isComplete ? ( + "🎉 Congratulations! You achieved exactly one black square!" + ) : ( + "Click row/column buttons or 2×2 square buttons to repaint. Goal: exactly one black square!" + )} + + + + + {/* Legend */} + + Legend + + + + White square + + + + Black square + + + 2×2 + Repaint 2×2 square + + + + + + + {/* Rules Dialog */} + + + + + + Chessboard Repaint Puzzle Rules + + + + + Objective + Repaint the chessboard to achieve exactly one black square. + + + + Initial Setup + You start with a standard 8×8 chessboard with alternating black and white squares. + + + + How to Play + + Repaint a row: Click any "Row X" button to flip all squares in that row (black becomes white, white becomes black). + Repaint a column: Click any "Col X" button to flip all squares in that column. + Repaint a 2×2 square: Click any "2×2" button to flip all four squares in that 2×2 region. + Goal: Achieve exactly one black square on the entire board. + + + + + Rules + + You can repaint any row, any column, or any 2×2 square + Each click counts as one move + The puzzle is solved when exactly one square is black + Try to solve it in as few moves as possible! + + + + + Strategy Tips + + Think about parity - how do different operations affect the total number of black squares? + Consider the effect of each operation on the overall pattern + Remember that flipping a row or column affects 8 squares, while flipping a 2×2 affects 4 squares + Look for patterns in how the operations interact with each other + + + + + + + {/* Victory Dialog */} + + + + + + Puzzle Solved! + + + + + + + Congratulations! + + + + You solved the puzzle in {moves} moves! + + {moves > 5 && ( + + Can you solve it in fewer moves? + + )} + + Play Again + + + + + + {/* Attribution */} + + + This puzzle explores the fascinating concept of parity and how different operations affect the overall state of a system. + + + + {/* Social Share Section */} + {showSocialShare && ( + + )} + + ); +}; + +export default ChessboardRepaintPuzzle; \ No newline at end of file diff --git a/src/components/InteractiveIndex.tsx b/src/components/InteractiveIndex.tsx index 5347090..fe43a7f 100644 --- a/src/components/InteractiveIndex.tsx +++ b/src/components/InteractiveIndex.tsx @@ -91,6 +91,15 @@ const allInteractives: Interactive[] = [ theme: 'Puzzles', dateAdded: '2024-12-19' }, + { + id: 'chessboard-repaint', + title: 'Chessboard Repaint Puzzle', + description: 'Repaint rows, columns, or 2×2 squares to achieve exactly one black square. A puzzle about parity and operations!', + tags: ['logic', 'parity', 'strategy', 'puzzle', 'chessboard', 'operations'], + path: '/puzzles/chessboard-repaint', + theme: 'Puzzles', + dateAdded: '2024-12-19' + }, { id: 'knights-puzzle', title: 'Knights Exchange Puzzle', diff --git a/src/pages/ChessboardRepaintPuzzlePage.tsx b/src/pages/ChessboardRepaintPuzzlePage.tsx new file mode 100644 index 0000000..6a338a4 --- /dev/null +++ b/src/pages/ChessboardRepaintPuzzlePage.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import ChessboardRepaintPuzzle from '@/components/ChessboardRepaintPuzzle'; + +const ChessboardRepaintPuzzlePage = () => { + return ( + + + + ); +}; + +export default ChessboardRepaintPuzzlePage; \ No newline at end of file diff --git a/src/pages/theme-pages/Puzzles.tsx b/src/pages/theme-pages/Puzzles.tsx index 2365b79..93513c9 100644 --- a/src/pages/theme-pages/Puzzles.tsx +++ b/src/pages/theme-pages/Puzzles.tsx @@ -16,6 +16,16 @@ const Puzzles = () => { duration: "10-30 min", participants: "1 player" }, + { + id: "chessboard-repaint", + title: "Chessboard Repaint Puzzle", + description: "Repaint rows, columns, or 2×2 squares to achieve exactly one black square. A puzzle about parity and operations!", + path: "/puzzles/chessboard-repaint", + tags: ["Logic", "Parity", "Operations"], + difficulty: "Advanced" as const, + duration: "15-45 min", + participants: "1 player" + }, { id: "knights-puzzle", title: "Knights Exchange Puzzle",
+ {isComplete ? ( + "🎉 Congratulations! You achieved exactly one black square!" + ) : ( + "Click row/column buttons or 2×2 square buttons to repaint. Goal: exactly one black square!" + )} +
Repaint the chessboard to achieve exactly one black square.
You start with a standard 8×8 chessboard with alternating black and white squares.
+ You solved the puzzle in {moves} moves! +
+ Can you solve it in fewer moves? +
+ This puzzle explores the fascinating concept of parity and how different operations affect the overall state of a system. +