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 handleSquareClick = (row: number, col: number) => { if (isComplete) return; // Special case for bottom-right corner if (row === 7 && col === 7) { if (bottomRightMode === 'row') { repaintRow(7); } else { repaintColumn(7); } return; } // If clicking on the rightmost column, repaint the entire row if (col === 7) { repaintRow(row); return; } // If clicking on the bottom row, repaint the entire column if (row === 7) { repaintColumn(col); return; } // Otherwise, repaint the 2x2 square with this square as top-left repaint2x2Square(row, col); }; const handleBottomRightHover = () => { // Clear any existing timer if (hoverTimer) { clearTimeout(hoverTimer); } // Set a new timer to switch mode after 3 seconds const timer = setTimeout(() => { setBottomRightMode(prev => prev === 'row' ? 'column' : 'row'); }, 3000); setHoverTimer(timer); }; const handleBottomRightLeave = () => { // Clear the timer when leaving the square if (hoverTimer) { clearTimeout(hoverTimer); setHoverTimer(null); } }; const [hoveredSquare, setHoveredSquare] = useState<{row: number, col: number} | null>(null); const [bottomRightMode, setBottomRightMode] = useState<'row' | 'column'>('row'); const [hoverTimer, setHoverTimer] = useState(null); const getAffectedSquares = (row: number, col: number) => { const squares: {row: number, col: number}[] = []; // Special case for bottom-right corner if (row === 7 && col === 7) { if (bottomRightMode === 'row') { for (let c = 0; c < 8; c++) { squares.push({row: 7, col: c}); } } else { for (let r = 0; r < 8; r++) { squares.push({row: r, col: 7}); } } return squares; } // If clicking on the rightmost column, repaint the entire row if (col === 7) { for (let c = 0; c < 8; c++) { squares.push({row, col: c}); } return squares; } // If clicking on the bottom row, repaint the entire column if (row === 7) { for (let r = 0; r < 8; r++) { squares.push({row: r, col}); } return squares; } // Otherwise, repaint the 2x2 square with this square as top-left for (let r = row; r < row + 2 && r < 8; r++) { for (let c = col; c < col + 2 && c < 8; c++) { squares.push({row: r, col: c}); } } return squares; }; const getBoundingBox = (squares: {row: number, col: number}[]) => { if (squares.length === 0) return null; const minRow = Math.min(...squares.map(s => s.row)); const maxRow = Math.max(...squares.map(s => s.row)); const minCol = Math.min(...squares.map(s => s.col)); const maxCol = Math.max(...squares.map(s => s.col)); return { minRow, maxRow, minCol, maxCol }; }; const renderSquare = (row: number, col: number) => { const isBlack = board[row][col]; const isBottomRow = row === 7; const isRightmostCol = col === 7; const isBottomRight = row === 7 && col === 7; return (
handleSquareClick(row, col)} onMouseEnter={() => { setHoveredSquare({row, col}); if (isBottomRight) { handleBottomRightHover(); } }} onMouseLeave={() => { setHoveredSquare(null); if (isBottomRight) { handleBottomRightLeave(); } }} className={` w-12 h-12 border border-gray-300 flex items-center justify-center cursor-pointer transition-all duration-200 hover:scale-105 ${isBlack ? 'bg-gray-800' : 'bg-gray-100'} `} title={isBottomRight ? `Hover for 3s to switch mode (current: ${bottomRightMode})` : undefined} /> ); }; return (
{/* Back to Puzzles button - only show when coming from puzzles */} {isFromPuzzles && (
)} Chessboard Repaint Puzzle Repaint rows, columns, or 2×2 squares to achieve exactly one black square! {/* Game Controls */}
Moves: {moves}
Black Squares: {countBlackSquares(board)}
{/* Chessboard */}
{/* Chessboard grid */}
{board.map((row, rowIndex) => row.map((square, colIndex) => renderSquare(rowIndex, colIndex)) )}
{/* Bounding rectangle overlay */} {hoveredSquare && (() => { const boundingBox = getBoundingBox(getAffectedSquares(hoveredSquare.row, hoveredSquare.col)); if (!boundingBox) return null; const squareSize = 48; // w-12 = 48px const left = boundingBox.minCol * squareSize; const top = boundingBox.minRow * squareSize; const width = (boundingBox.maxCol - boundingBox.minCol + 1) * squareSize; const height = (boundingBox.maxRow - boundingBox.minRow + 1) * squareSize; return (
); })()}
{/* Instructions */}

{isComplete ? ( "🎉 Congratulations! You achieved exactly one black square!" ) : ( "Click any square to repaint the 2×2 region with that square as top-left. Click rightmost column to repaint entire row, or bottom row to repaint entire column. Bottom-right corner switches between row/column mode on 3s hover. Hover to preview affected squares!" )}

{/* Legend */}

Legend

White square
Black square
Hover to preview affected squares
{/* Rules Dialog */} Chessboard Repaint Puzzle Rules

Objective

You start with a standard 8×8 chessboard with alternating black and white squares. Repaint the chessboard to achieve exactly one black square.

How to Play

  1. Repaint a 2×2 square: Click any square to flip all four squares in the 2×2 region with that square as the top-left corner.
  2. Repaint a row: Click any square in the rightmost column to flip all squares in that entire row.
  3. Repaint a column: Click any square in the bottom row to flip all squares in that entire column.
  4. Bottom-right corner: Hover for 3 seconds to switch between row and column mode.
  5. Preview: Hover over any square to see which squares will be affected.

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?

)}
{/* Attribution */}

⚠️ Spoiler: This solution to this puzzle leverages a parity-based invariant.

{/* Social Share Section */} {showSocialShare && ( )}
); }; export default ChessboardRepaintPuzzle;