import React, { useState, 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 { Alert, AlertDescription } from '@/components/ui/alert'; import { RotateCcw, Info } from 'lucide-react'; interface ParityBitsGameProps { showSocialShare?: boolean; } const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => { const [gridSize, setGridSize] = useState(3); const [grid, setGrid] = useState(() => Array(gridSize).fill(null).map(() => Array(gridSize).fill(false)) ); const [parityBitsAdded, setParityBitsAdded] = useState(false); const [parityGrid, setParityGrid] = useState([]); const [flippedCell, setFlippedCell] = useState<{row: number, col: number} | null>(null); const [errorDetected, setErrorDetected] = useState<{row: number, col: number} | null>(null); const [phase, setPhase] = useState<'setup' | 'parity' | 'flip' | 'detect'>('setup'); const [animationState, setAnimationState] = useState<{ isAnimating: boolean; currentRow: number; currentCol: number; showRowParity: boolean; showColParity: boolean; rowParities: boolean[]; colParities: boolean[]; rowCounts: number[]; colCounts: number[]; }>({ isAnimating: false, currentRow: -1, currentCol: -1, showRowParity: false, showColParity: false, rowParities: [], colParities: [], rowCounts: [], colCounts: [] }); // Reset game when grid size changes const handleGridSizeChange = useCallback((newSize: number[]) => { const size = newSize[0]; setGridSize(size); setGrid(Array(size).fill(null).map(() => Array(size).fill(false))); setParityBitsAdded(false); setParityGrid([]); setFlippedCell(null); setErrorDetected(null); setPhase('setup'); }, []); // Toggle a cell in the main grid const toggleCell = (row: number, col: number) => { if (phase !== 'setup') return; const newGrid = [...grid]; newGrid[row][col] = !newGrid[row][col]; setGrid(newGrid); }; // Calculate parity for a row or column const calculateParity = (bits: boolean[]): boolean => { return bits.filter(bit => bit).length % 2 === 1; }; // Add parity bits to create the extended grid const addParityBits = () => { const extended = Array(gridSize + 1).fill(null).map(() => Array(gridSize + 1).fill(false)); // Copy original grid for (let i = 0; i < gridSize; i++) { for (let j = 0; j < gridSize; j++) { extended[i][j] = grid[i][j]; } } // Calculate row parities for (let i = 0; i < gridSize; i++) { extended[i][gridSize] = calculateParity(grid[i]); } // Calculate column parities for (let j = 0; j < gridSize; j++) { const column = grid.map(row => row[j]); extended[gridSize][j] = calculateParity(column); } // Calculate overall parity const allBits = grid.flat(); const rowParities = Array(gridSize).fill(null).map((_, i) => calculateParity(grid[i])); extended[gridSize][gridSize] = calculateParity([...allBits, ...rowParities]); setParityGrid(extended); setParityBitsAdded(true); setPhase('parity'); }; // Flip a bit in the parity grid const flipBit = (row: number, col: number) => { if (phase !== 'parity') return; const newGrid = [...parityGrid]; newGrid[row][col] = !newGrid[row][col]; setParityGrid(newGrid); setFlippedCell({row, col}); setPhase('flip'); }; // Identify the flipped bit using parity check with animation const identifyFlip = async () => { if (!parityBitsAdded) return; // Calculate all parities first const rowParities: boolean[] = []; const colParities: boolean[] = []; const rowCounts: number[] = []; const colCounts: number[] = []; for (let i = 0; i < gridSize; i++) { const rowBits = parityGrid[i].slice(0, gridSize); const rowCount = rowBits.filter(bit => bit).length; const expectedParity = calculateParity(rowBits); rowParities[i] = expectedParity !== parityGrid[i][gridSize]; rowCounts[i] = rowCount; } for (let j = 0; j < gridSize; j++) { const colBits = parityGrid.slice(0, gridSize).map(row => row[j]); const colCount = colBits.filter(bit => bit).length; const expectedParity = calculateParity(colBits); colParities[j] = expectedParity !== parityGrid[gridSize][j]; colCounts[j] = colCount; } setAnimationState({ isAnimating: true, currentRow: -1, currentCol: -1, showRowParity: false, showColParity: false, rowParities, colParities, rowCounts, colCounts }); // Animate through rows (including parity row) for (let i = 0; i <= gridSize; i++) { setAnimationState(prev => ({...prev, currentRow: i})); await new Promise(resolve => setTimeout(resolve, 800)); } // Show row parities setAnimationState(prev => ({...prev, currentRow: -1, showRowParity: true})); await new Promise(resolve => setTimeout(resolve, 1000)); // Animate through columns (including parity column) for (let j = 0; j <= gridSize; j++) { setAnimationState(prev => ({...prev, currentCol: j})); await new Promise(resolve => setTimeout(resolve, 800)); } // Show column parities and keep them persistent setAnimationState(prev => ({...prev, currentCol: -1, showColParity: true})); await new Promise(resolve => setTimeout(resolve, 1000)); // Find and highlight the error const errorRow = rowParities.findIndex(parity => parity); const errorCol = colParities.findIndex(parity => parity); if (errorRow >= 0 && errorCol >= 0) { setErrorDetected({row: errorRow, col: errorCol}); } // Keep labels persistent - don't reset them setAnimationState(prev => ({ ...prev, isAnimating: false, currentRow: -1, currentCol: -1 })); setPhase('detect'); }; // Random start - fill random cells const randomStart = () => { if (phase !== 'setup') return; const newGrid = Array(gridSize).fill(null).map(() => Array(gridSize).fill(false)); // Fill about 30-50% of cells randomly const fillPercentage = 0.3 + Math.random() * 0.2; // 30-50% const totalCells = gridSize * gridSize; const cellsToFill = Math.floor(totalCells * fillPercentage); const positions: {row: number, col: number}[] = []; for (let i = 0; i < gridSize; i++) { for (let j = 0; j < gridSize; j++) { positions.push({row: i, col: j}); } } // Shuffle and take first cellsToFill positions for (let i = positions.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [positions[i], positions[j]] = [positions[j], positions[i]]; } for (let i = 0; i < cellsToFill; i++) { const {row, col} = positions[i]; newGrid[row][col] = true; } setGrid(newGrid); }; const resetGame = () => { setGrid(Array(gridSize).fill(null).map(() => Array(gridSize).fill(false))); setParityBitsAdded(false); setParityGrid([]); setFlippedCell(null); setErrorDetected(null); setPhase('setup'); setAnimationState({ isAnimating: false, currentRow: -1, currentCol: -1, showRowParity: false, showColParity: false, rowParities: [], colParities: [], rowCounts: [], colCounts: [] }); }; const renderGrid = () => { const currentGrid = parityBitsAdded ? parityGrid : grid; const size = parityBitsAdded ? gridSize + 1 : gridSize; return (
{/* Main grid with row labels */}
{Array(size).fill(null).map((_, row) => Array(size).fill(null).map((_, col) => { const isParityCell = parityBitsAdded && (row === gridSize || col === gridSize); const isFlipped = flippedCell?.row === row && flippedCell?.col === col; const isError = errorDetected?.row === row && errorDetected?.col === col; const isCurrentRowBeingChecked = animationState.currentRow === row; const isCurrentColBeingChecked = animationState.currentCol === col; const shouldHighlightForAnimation = isCurrentRowBeingChecked || isCurrentColBeingChecked; const isBlackCell = currentGrid[row]?.[col]; const highlightColor = isBlackCell ? 'ring-2 ring-orange-400 bg-orange-400/20' : 'ring-2 ring-yellow-400 bg-yellow-400/15'; return (
{/* Row parity labels */} {(animationState.currentRow >= 0 || animationState.showRowParity) && (
{Array(gridSize).fill(null).map((_, row) => { const hasOddParity = animationState.rowParities[row]; const count = animationState.rowCounts[row]; const showLabel = (row === animationState.currentRow) || animationState.showRowParity; return (
{showLabel && ( {count} )}
); })}
{/* Empty space for parity cell */}
)}
{/* Column parity labels */} {(animationState.currentCol >= 0 || animationState.showColParity) && (
{Array(gridSize).fill(null).map((_, col) => { const hasOddParity = animationState.colParities[col]; const count = animationState.colCounts[col]; const showLabel = (col === animationState.currentCol) || animationState.showColParity; return (
{showLabel && ( {count} )}
); })}
{/* Empty space for parity cell */}
)}
); }; return (
Parity Bits Error Detection {/* Instructions */} {phase === 'setup' && 'Set your grid by clicking squares to make them black. Then add parity bits.'} {phase === 'parity' && 'Parity bits added! Now flip exactly one bit anywhere in the grid.'} {phase === 'flip' && 'Bit flipped! Click "Identify Flip" to see error detection in action.'} {phase === 'detect' && 'Animation complete! Rows and columns with odd parity were highlighted, revealing the error location in green.'} {/* Controls */}
{/* Grid */}
{renderGrid()}
{/* Status Information */} {parityBitsAdded && (

Parity cells have colored borders. Each row and column should have an even number of black squares.

{flippedCell && (

Flipped bit at position ({flippedCell.row + 1}, {flippedCell.col + 1})

)} {errorDetected && (

Error detected at position ({errorDetected.row + 1}, {errorDetected.col + 1}) - highlighted in green!

)}
)} {/* Legend */}

How it works: Parity bits ensure even parity in each row/column.

When a bit flips, the affected row and column will have odd parity, pinpointing the error.

); }; export default ParityBitsGame;