diff --git a/src/App.tsx b/src/App.tsx index e9064f0..d5733a3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -54,6 +54,7 @@ import PebblePlacementGamePage from './pages/PebblePlacementGamePage'; import BulgarianSolitairePage from './pages/BulgarianSolitairePage'; import SubtractionGamePage from './pages/SubtractionGamePage'; import StackingBlocksPage from './pages/StackingBlocksPage'; +import ParityBitsGamePage from './pages/ParityBitsGamePage'; const queryClient = new QueryClient(); @@ -88,6 +89,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/ParityBitsGame.tsx b/src/components/ParityBitsGame.tsx new file mode 100644 index 0000000..f2dd42a --- /dev/null +++ b/src/components/ParityBitsGame.tsx @@ -0,0 +1,274 @@ +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'); + + // 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' || row >= gridSize || col >= gridSize) return; + + const newGrid = [...parityGrid]; + newGrid[row][col] = !newGrid[row][col]; + setParityGrid(newGrid); + setFlippedCell({row, col}); + setPhase('flip'); + }; + + // Identify the flipped bit using parity check + const identifyFlip = () => { + if (!parityBitsAdded) return; + + let errorRow = -1; + let errorCol = -1; + + // Check row parities + for (let i = 0; i < gridSize; i++) { + const rowBits = parityGrid[i].slice(0, gridSize); + const expectedParity = calculateParity(rowBits); + if (expectedParity !== parityGrid[i][gridSize]) { + errorRow = i; + } + } + + // Check column parities + for (let j = 0; j < gridSize; j++) { + const colBits = parityGrid.slice(0, gridSize).map(row => row[j]); + const expectedParity = calculateParity(colBits); + if (expectedParity !== parityGrid[gridSize][j]) { + errorCol = j; + } + } + + if (errorRow >= 0 && errorCol >= 0) { + setErrorDetected({row: errorRow, col: errorCol}); + } + setPhase('detect'); + }; + + // Reset the game + const resetGame = () => { + setGrid(Array(gridSize).fill(null).map(() => Array(gridSize).fill(false))); + setParityBitsAdded(false); + setParityGrid([]); + setFlippedCell(null); + setErrorDetected(null); + setPhase('setup'); + }; + + const renderGrid = () => { + const currentGrid = parityBitsAdded ? parityGrid : grid; + const size = parityBitsAdded ? gridSize + 1 : gridSize; + + return ( +
+ {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 isErrorRow = errorDetected && row === errorDetected.row; + const isErrorCol = errorDetected && col === errorDetected.col; + + return ( +
+ ); + }; + + 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 in the main grid (not parity cells).'} + {phase === 'flip' && 'Bit flipped! Click "Identify Flip" to see error detection in action.'} + {phase === 'detect' && 'Error detected! The highlighted cell shows where the bit was flipped.'} + + + + {/* Controls */} +
+
+ + +
+ +
+ + + + + +
+
+ + {/* Grid */} +
+ {renderGrid()} +
+ + {/* Status Information */} + {parityBitsAdded && ( +
+

+ Gray cells are parity bits. 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})! +

+ )} +
+ )} + + {/* 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; \ No newline at end of file diff --git a/src/pages/ParityBitsGamePage.tsx b/src/pages/ParityBitsGamePage.tsx new file mode 100644 index 0000000..6ba5a0b --- /dev/null +++ b/src/pages/ParityBitsGamePage.tsx @@ -0,0 +1,12 @@ +import Layout from "@/components/Layout"; +import ParityBitsGame from "@/components/ParityBitsGame"; + +const ParityBitsGamePage = () => { + return ( + + + + ); +}; + +export default ParityBitsGamePage; \ No newline at end of file