Refine parity bits identification
Update parity bits interactive to highlight flipped bit location in green, display row/column parity ("odd" in red), and briefly highlight all black cells upon clicking "Identify Flip".
This commit is contained in:
parent
352b2342ed
commit
0bb5653759
1 changed files with 154 additions and 48 deletions
|
|
@ -19,6 +19,23 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
|||
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[];
|
||||
}>({
|
||||
isAnimating: false,
|
||||
currentRow: -1,
|
||||
currentCol: -1,
|
||||
showRowParity: false,
|
||||
showColParity: false,
|
||||
rowParities: [],
|
||||
colParities: []
|
||||
});
|
||||
|
||||
// Reset game when grid size changes
|
||||
const handleGridSizeChange = useCallback((newSize: number[]) => {
|
||||
|
|
@ -89,34 +106,74 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
|||
setPhase('flip');
|
||||
};
|
||||
|
||||
// Identify the flipped bit using parity check
|
||||
const identifyFlip = () => {
|
||||
// Identify the flipped bit using parity check with animation
|
||||
const identifyFlip = async () => {
|
||||
if (!parityBitsAdded) return;
|
||||
|
||||
let errorRow = -1;
|
||||
let errorCol = -1;
|
||||
// Calculate all parities first
|
||||
const rowParities: boolean[] = [];
|
||||
const colParities: boolean[] = [];
|
||||
|
||||
// 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;
|
||||
}
|
||||
rowParities[i] = expectedParity !== parityGrid[i][gridSize];
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
colParities[j] = expectedParity !== parityGrid[gridSize][j];
|
||||
}
|
||||
|
||||
setAnimationState({
|
||||
isAnimating: true,
|
||||
currentRow: -1,
|
||||
currentCol: -1,
|
||||
showRowParity: false,
|
||||
showColParity: false,
|
||||
rowParities,
|
||||
colParities
|
||||
});
|
||||
|
||||
// Animate through rows
|
||||
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
|
||||
for (let j = 0; j < gridSize; j++) {
|
||||
setAnimationState(prev => ({...prev, currentCol: j}));
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
}
|
||||
|
||||
// Show column parities
|
||||
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});
|
||||
}
|
||||
|
||||
setAnimationState({
|
||||
isAnimating: false,
|
||||
currentRow: -1,
|
||||
currentCol: -1,
|
||||
showRowParity: false,
|
||||
showColParity: false,
|
||||
rowParities: [],
|
||||
colParities: []
|
||||
});
|
||||
|
||||
setPhase('detect');
|
||||
};
|
||||
|
||||
|
|
@ -128,6 +185,15 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
|||
setFlippedCell(null);
|
||||
setErrorDetected(null);
|
||||
setPhase('setup');
|
||||
setAnimationState({
|
||||
isAnimating: false,
|
||||
currentRow: -1,
|
||||
currentCol: -1,
|
||||
showRowParity: false,
|
||||
showColParity: false,
|
||||
rowParities: [],
|
||||
colParities: []
|
||||
});
|
||||
};
|
||||
|
||||
const renderGrid = () => {
|
||||
|
|
@ -135,37 +201,77 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
|||
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 className="flex flex-col items-center gap-2">
|
||||
{/* Main grid with row labels */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="grid gap-1" 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 isCurrentRowBeingChecked = animationState.currentRow === row && !isParityCell;
|
||||
const isCurrentColBeingChecked = animationState.currentCol === col && !isParityCell;
|
||||
const shouldHighlightForAnimation = (isCurrentRowBeingChecked || isCurrentColBeingChecked) && currentGrid[row]?.[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-green-500 bg-green-500/20' : ''}
|
||||
${shouldHighlightForAnimation ? 'ring-4 ring-yellow-400 bg-yellow-400/30' : ''}
|
||||
${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>
|
||||
|
||||
{/* Row parity labels */}
|
||||
{(animationState.showRowParity || animationState.isAnimating) && (
|
||||
<div className="flex flex-col gap-1">
|
||||
{Array(gridSize).fill(null).map((_, row) => {
|
||||
const hasOddParity = animationState.rowParities[row];
|
||||
return (
|
||||
<div key={`row-${row}`} className="w-12 h-12 flex items-center justify-center">
|
||||
<span className={`text-xs font-medium ${hasOddParity ? 'text-red-500' : 'text-foreground'}`}>
|
||||
{hasOddParity ? 'odd' : 'even'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="w-12 h-12"></div> {/* Empty space for parity cell */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Column parity labels */}
|
||||
{(animationState.showColParity || animationState.isAnimating) && (
|
||||
<div className="flex gap-1">
|
||||
{Array(gridSize).fill(null).map((_, col) => {
|
||||
const hasOddParity = animationState.colParities[col];
|
||||
return (
|
||||
<div key={`col-${col}`} className="w-12 h-6 flex items-center justify-center">
|
||||
<span className={`text-xs font-medium ${hasOddParity ? 'text-red-500' : 'text-foreground'}`}>
|
||||
{hasOddParity ? 'odd' : 'even'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="w-12 h-6"></div> {/* Empty space for parity cell */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -187,7 +293,7 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
|||
{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.'}
|
||||
{phase === 'detect' && 'Animation complete! Rows and columns with odd parity were highlighted, revealing the error location in green.'}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
|
|
@ -220,10 +326,10 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
|||
|
||||
<Button
|
||||
onClick={identifyFlip}
|
||||
disabled={phase !== 'flip'}
|
||||
disabled={phase !== 'flip' || animationState.isAnimating}
|
||||
variant={phase === 'flip' ? 'default' : 'outline'}
|
||||
>
|
||||
Identify Flip
|
||||
{animationState.isAnimating ? 'Checking...' : 'Identify Flip'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
|
@ -253,8 +359,8 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
|||
</p>
|
||||
)}
|
||||
{errorDetected && (
|
||||
<p className="text-sm text-destructive font-medium">
|
||||
Error detected at position ({errorDetected.row + 1}, {errorDetected.col + 1})!
|
||||
<p className="text-sm text-green-600 font-medium">
|
||||
Error detected at position ({errorDetected.row + 1}, {errorDetected.col + 1}) - highlighted in green!
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue