Add Chessboard Repaint Puzzle - a challenging parity puzzle

This commit is contained in:
Neeldhara Misra 2025-07-22 04:50:42 +05:30
parent d9c0c88333
commit f8fd9ac4d8
5 changed files with 411 additions and 2 deletions

View file

@ -33,6 +33,7 @@ import NimGamePage from "./pages/NimGamePage";
import AssistedNimGamePage from "./pages/AssistedNimGamePage"; import AssistedNimGamePage from "./pages/AssistedNimGamePage";
import GoldCoinGamePage from "./pages/GoldCoinGamePage"; import GoldCoinGamePage from "./pages/GoldCoinGamePage";
import PlateSwapPuzzlePage from "./pages/PlateSwapPuzzlePage"; import PlateSwapPuzzlePage from "./pages/PlateSwapPuzzlePage";
import ChessboardRepaintPuzzlePage from "./pages/ChessboardRepaintPuzzlePage";
import Foundations from "./pages/theme-pages/discrete-math/Foundations"; import Foundations from "./pages/theme-pages/discrete-math/Foundations";
import Proofs from "./pages/theme-pages/discrete-math/Proofs"; import Proofs from "./pages/theme-pages/discrete-math/Proofs";
import Counting from "./pages/theme-pages/discrete-math/Counting"; import Counting from "./pages/theme-pages/discrete-math/Counting";
@ -92,6 +93,7 @@ const App = () => (
<Route path="/games/assisted-nim" element={<AssistedNimGamePage />} /> <Route path="/games/assisted-nim" element={<AssistedNimGamePage />} />
<Route path="/games/gold-coin" element={<GoldCoinGamePage />} /> <Route path="/games/gold-coin" element={<GoldCoinGamePage />} />
<Route path="/puzzles/plate-swap" element={<PlateSwapPuzzlePage />} /> <Route path="/puzzles/plate-swap" element={<PlateSwapPuzzlePage />} />
<Route path="/puzzles/chessboard-repaint" element={<ChessboardRepaintPuzzlePage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />

View file

@ -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<ChessboardRepaintPuzzleProps> = ({ 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<boolean[][]>(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 (
<div
key={`${row}-${col}`}
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'}
`}
/>
);
};
const render2x2Button = (startRow: number, startCol: number) => {
return (
<Button
key={`2x2-${startRow}-${startCol}`}
onClick={() => repaint2x2Square(startRow, startCol)}
variant="outline"
size="sm"
className="w-6 h-6 p-0 text-xs"
disabled={isComplete}
>
2×2
</Button>
);
};
return (
<div className="max-w-4xl mx-auto p-6 space-y-6">
{/* Back to Puzzles button - only show when coming from puzzles */}
{isFromPuzzles && (
<div className="flex items-center gap-4">
<button
onClick={() => navigate('/themes/puzzles')}
className="text-primary hover:text-primary/80 font-medium flex items-center gap-2"
>
<ArrowLeft className="w-4 h-4" />
Go back to Puzzles
</button>
</div>
)}
<Card className="shadow-lg">
<CardHeader className="text-center pb-6">
<CardTitle className="flex items-center justify-center gap-3 text-3xl">
<RefreshCw className="w-8 h-8 text-blue-500" />
Chessboard Repaint Puzzle
<RefreshCw className="w-8 h-8 text-blue-500" />
</CardTitle>
<CardDescription className="text-lg max-w-2xl mx-auto">
Repaint rows, columns, or 2×2 squares to achieve exactly one black square!
</CardDescription>
</CardHeader>
<CardContent className="space-y-8">
{/* Game Controls */}
<div className="flex justify-center items-center gap-4">
<Button onClick={() => setShowRules(true)} variant="outline" size="lg" className="px-8">
<BookOpen className="w-4 h-4 mr-2" />
Rules
</Button>
<Button onClick={resetPuzzle} variant="outline" size="lg" className="px-8">
<RotateCcw className="w-4 h-4 mr-2" />
Reset Puzzle
</Button>
<div className="flex items-center gap-2">
<span className="font-semibold text-lg">Moves:</span>
<Badge className="text-lg px-4 py-2 bg-blue-600 text-white">
{moves}
</Badge>
</div>
<div className="flex items-center gap-2">
<span className="font-semibold text-lg">Black Squares:</span>
<Badge className={`text-lg px-4 py-2 ${countBlackSquares(board) === 1 ? 'bg-green-600' : 'bg-gray-600'} text-white`}>
{countBlackSquares(board)}
</Badge>
</div>
</div>
{/* Chessboard */}
<div className="flex justify-center">
<div className="relative">
{/* Row buttons */}
<div className="absolute -left-16 top-0 h-full flex flex-col justify-center gap-1">
{Array.from({ length: 8 }, (_, i) => (
<Button
key={`row-${i}`}
onClick={() => repaintRow(i)}
variant="outline"
size="sm"
className="w-12 h-6 text-xs"
disabled={isComplete}
>
Row {i + 1}
</Button>
))}
</div>
{/* Column buttons */}
<div className="absolute -top-16 left-0 w-full flex justify-center gap-1">
{Array.from({ length: 8 }, (_, i) => (
<Button
key={`col-${i}`}
onClick={() => repaintColumn(i)}
variant="outline"
size="sm"
className="w-12 h-6 text-xs"
disabled={isComplete}
>
Col {i + 1}
</Button>
))}
</div>
{/* 2x2 square buttons */}
<div className="absolute inset-0 pointer-events-none">
{Array.from({ length: 7 }, (_, row) =>
Array.from({ length: 7 }, (_, col) => (
<div
key={`2x2-${row}-${col}`}
className="absolute pointer-events-auto"
style={{
left: `${col * 48 + 24}px`,
top: `${row * 48 + 24}px`,
transform: 'translate(-50%, -50%)'
}}
>
{render2x2Button(row, col)}
</div>
))
)}
</div>
{/* Chessboard grid */}
<div className="grid grid-cols-8 gap-0 border-2 border-gray-400">
{board.map((row, rowIndex) =>
row.map((square, colIndex) => renderSquare(rowIndex, colIndex))
)}
</div>
</div>
</div>
{/* Instructions */}
<div className="text-center mt-8">
<div className="inline-block bg-gray-50 px-6 py-3 rounded-lg border">
<p className="text-sm text-gray-700 font-medium">
{isComplete ? (
"🎉 Congratulations! You achieved exactly one black square!"
) : (
"Click row/column buttons or 2×2 square buttons to repaint. Goal: exactly one black square!"
)}
</p>
</div>
</div>
{/* Legend */}
<div className="space-y-3 mt-8">
<h3 className="font-semibold text-lg text-center">Legend</h3>
<div className="flex flex-wrap justify-center gap-6 text-sm">
<div className="flex items-center gap-2">
<div className="w-5 h-5 bg-gray-100 border border-gray-300"></div>
<span>White square</span>
</div>
<div className="flex items-center gap-2">
<div className="w-5 h-5 bg-gray-800 border border-gray-300"></div>
<span>Black square</span>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" className="w-6 h-6 p-0 text-xs">2×2</Button>
<span>Repaint 2×2 square</span>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Rules Dialog */}
<Dialog open={showRules} onOpenChange={setShowRules}>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<BookOpen className="w-5 h-5" />
Chessboard Repaint Puzzle Rules
</DialogTitle>
</DialogHeader>
<div className="space-y-6 text-sm">
<div>
<h4 className="font-semibold mb-2 text-lg">Objective</h4>
<p className="text-gray-700">Repaint the chessboard to achieve exactly one black square.</p>
</div>
<div>
<h4 className="font-semibold mb-2 text-lg">Initial Setup</h4>
<p className="text-gray-700">You start with a standard 8×8 chessboard with alternating black and white squares.</p>
</div>
<div>
<h4 className="font-semibold mb-2 text-lg">How to Play</h4>
<ol className="list-decimal list-inside space-y-2 ml-4 text-gray-700">
<li><strong>Repaint a row:</strong> Click any "Row X" button to flip all squares in that row (black becomes white, white becomes black).</li>
<li><strong>Repaint a column:</strong> Click any "Col X" button to flip all squares in that column.</li>
<li><strong>Repaint a 2×2 square:</strong> Click any "2×2" button to flip all four squares in that 2×2 region.</li>
<li><strong>Goal:</strong> Achieve exactly one black square on the entire board.</li>
</ol>
</div>
<div>
<h4 className="font-semibold mb-2 text-lg">Rules</h4>
<ul className="list-disc list-inside space-y-2 ml-4 text-gray-700">
<li>You can repaint any row, any column, or any 2×2 square</li>
<li>Each click counts as one move</li>
<li>The puzzle is solved when exactly one square is black</li>
<li>Try to solve it in as few moves as possible!</li>
</ul>
</div>
<div>
<h4 className="font-semibold mb-2 text-lg">Strategy Tips</h4>
<ul className="list-disc list-inside space-y-2 ml-4 text-gray-700">
<li>Think about parity - how do different operations affect the total number of black squares?</li>
<li>Consider the effect of each operation on the overall pattern</li>
<li>Remember that flipping a row or column affects 8 squares, while flipping a 2×2 affects 4 squares</li>
<li>Look for patterns in how the operations interact with each other</li>
</ul>
</div>
</div>
</DialogContent>
</Dialog>
{/* Victory Dialog */}
<Dialog open={showVictory} onOpenChange={setShowVictory}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center justify-center gap-2">
<Trophy className="w-6 h-6 text-yellow-500" />
Puzzle Solved!
</DialogTitle>
</DialogHeader>
<div className="text-center space-y-6">
<div className="flex items-center justify-center gap-3">
<CheckCircle className="w-10 h-10 text-green-500" />
<span className="text-2xl font-bold text-green-600">
Congratulations!
</span>
</div>
<p className="text-gray-600 text-lg">
You solved the puzzle in {moves} moves!
</p>
{moves > 5 && (
<p className="text-blue-600 font-medium">
Can you solve it in fewer moves?
</p>
)}
<Button onClick={resetPuzzle} className="w-full" size="lg">
Play Again
</Button>
</div>
</DialogContent>
</Dialog>
{/* Attribution */}
<div className="pt-4 border-t border-outline">
<p className="text-xs text-muted-foreground">
This puzzle explores the fascinating concept of parity and how different operations affect the overall state of a system.
</p>
</div>
{/* Social Share Section */}
{showSocialShare && (
<SocialShare
title="Chessboard Repaint Puzzle"
description="Can you repaint a chessboard to achieve exactly one black square? A challenging puzzle about parity and operations!"
url={window.location.href}
/>
)}
</div>
);
};
export default ChessboardRepaintPuzzle;

View file

@ -91,6 +91,15 @@ const allInteractives: Interactive[] = [
theme: 'Puzzles', theme: 'Puzzles',
dateAdded: '2024-12-19' 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', id: 'knights-puzzle',
title: 'Knights Exchange Puzzle', title: 'Knights Exchange Puzzle',

View file

@ -0,0 +1,12 @@
import React from 'react';
import ChessboardRepaintPuzzle from '@/components/ChessboardRepaintPuzzle';
const ChessboardRepaintPuzzlePage = () => {
return (
<div className="min-h-screen bg-background">
<ChessboardRepaintPuzzle showSocialShare={true} />
</div>
);
};
export default ChessboardRepaintPuzzlePage;

View file

@ -16,6 +16,16 @@ const Puzzles = () => {
duration: "10-30 min", duration: "10-30 min",
participants: "1 player" 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", id: "knights-puzzle",
title: "Knights Exchange Puzzle", title: "Knights Exchange Puzzle",