The parity bits are now correctly calculated and displayed, reflecting the parity of their respective rows and columns.
274 lines
No EOL
9.7 KiB
TypeScript
274 lines
No EOL
9.7 KiB
TypeScript
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<boolean[][]>(() =>
|
||
Array(gridSize).fill(null).map(() => Array(gridSize).fill(false))
|
||
);
|
||
const [parityBitsAdded, setParityBitsAdded] = useState(false);
|
||
const [parityGrid, setParityGrid] = useState<boolean[][]>([]);
|
||
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 (
|
||
<div className="grid gap-1 justify-center" style={{gridTemplateColumns: `repeat(${size}, 1fr)`}}>
|
||
{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 (
|
||
<button
|
||
key={`${row}-${col}`}
|
||
className={`
|
||
w-12 h-12 border-2 transition-all duration-200
|
||
${currentGrid[row]?.[col] ? 'bg-foreground' : 'bg-background'}
|
||
${isParityCell ? 'border-primary' : 'border-foreground'}
|
||
${isFlipped ? 'ring-4 ring-destructive' : ''}
|
||
${isError ? 'ring-4 ring-destructive bg-destructive/20' : ''}
|
||
${isErrorRow || isErrorCol ? 'ring-2 ring-warning' : ''}
|
||
${phase === 'setup' && !isParityCell ? 'hover:bg-accent cursor-pointer' : ''}
|
||
${phase === 'parity' && !isParityCell ? 'hover:bg-accent cursor-pointer' : ''}
|
||
${isParityCell ? 'cursor-not-allowed' : ''}
|
||
`}
|
||
onClick={() => {
|
||
if (phase === 'setup') toggleCell(row, col);
|
||
else if (phase === 'parity') flipBit(row, col);
|
||
}}
|
||
disabled={isParityCell}
|
||
/>
|
||
);
|
||
})
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<div className="container mx-auto p-6 max-w-4xl">
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Info className="h-5 w-5" />
|
||
Parity Bits Error Detection
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-6">
|
||
{/* Instructions */}
|
||
<Alert>
|
||
<AlertDescription>
|
||
{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.'}
|
||
</AlertDescription>
|
||
</Alert>
|
||
|
||
{/* Controls */}
|
||
<div className="flex flex-col sm:flex-row items-center gap-4">
|
||
<div className="flex items-center gap-2">
|
||
<label htmlFor="grid-size" className="text-sm font-medium whitespace-nowrap">
|
||
Grid Size: {gridSize}×{gridSize}
|
||
</label>
|
||
<Slider
|
||
id="grid-size"
|
||
min={2}
|
||
max={10}
|
||
step={1}
|
||
value={[gridSize]}
|
||
onValueChange={handleGridSizeChange}
|
||
className="w-24"
|
||
disabled={phase !== 'setup'}
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex gap-2">
|
||
<Button
|
||
onClick={addParityBits}
|
||
disabled={phase !== 'setup'}
|
||
variant={phase === 'setup' ? 'default' : 'outline'}
|
||
>
|
||
Add Parity Bits
|
||
</Button>
|
||
|
||
<Button
|
||
onClick={identifyFlip}
|
||
disabled={phase !== 'flip'}
|
||
variant={phase === 'flip' ? 'default' : 'outline'}
|
||
>
|
||
Identify Flip
|
||
</Button>
|
||
|
||
<Button
|
||
onClick={resetGame}
|
||
variant="outline"
|
||
size="icon"
|
||
>
|
||
<RotateCcw className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Grid */}
|
||
<div className="flex justify-center">
|
||
{renderGrid()}
|
||
</div>
|
||
|
||
{/* Status Information */}
|
||
{parityBitsAdded && (
|
||
<div className="text-center space-y-2">
|
||
<p className="text-sm text-muted-foreground">
|
||
Parity cells have colored borders. Each row and column should have an even number of black squares.
|
||
</p>
|
||
{flippedCell && (
|
||
<p className="text-sm text-destructive">
|
||
Flipped bit at position ({flippedCell.row + 1}, {flippedCell.col + 1})
|
||
</p>
|
||
)}
|
||
{errorDetected && (
|
||
<p className="text-sm text-destructive font-medium">
|
||
Error detected at position ({errorDetected.row + 1}, {errorDetected.col + 1})!
|
||
</p>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Legend */}
|
||
<div className="text-xs text-muted-foreground text-center space-y-1">
|
||
<p><strong>How it works:</strong> Parity bits ensure even parity in each row/column.</p>
|
||
<p>When a bit flips, the affected row and column will have odd parity, pinpointing the error.</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ParityBitsGame; |