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 [flippedCell, setFlippedCell] = useState<{row: number, col: number} | null>(null);
|
||||||
const [errorDetected, setErrorDetected] = 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 [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
|
// Reset game when grid size changes
|
||||||
const handleGridSizeChange = useCallback((newSize: number[]) => {
|
const handleGridSizeChange = useCallback((newSize: number[]) => {
|
||||||
|
|
@ -89,34 +106,74 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
||||||
setPhase('flip');
|
setPhase('flip');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Identify the flipped bit using parity check
|
// Identify the flipped bit using parity check with animation
|
||||||
const identifyFlip = () => {
|
const identifyFlip = async () => {
|
||||||
if (!parityBitsAdded) return;
|
if (!parityBitsAdded) return;
|
||||||
|
|
||||||
let errorRow = -1;
|
// Calculate all parities first
|
||||||
let errorCol = -1;
|
const rowParities: boolean[] = [];
|
||||||
|
const colParities: boolean[] = [];
|
||||||
|
|
||||||
// Check row parities
|
|
||||||
for (let i = 0; i < gridSize; i++) {
|
for (let i = 0; i < gridSize; i++) {
|
||||||
const rowBits = parityGrid[i].slice(0, gridSize);
|
const rowBits = parityGrid[i].slice(0, gridSize);
|
||||||
const expectedParity = calculateParity(rowBits);
|
const expectedParity = calculateParity(rowBits);
|
||||||
if (expectedParity !== parityGrid[i][gridSize]) {
|
rowParities[i] = expectedParity !== parityGrid[i][gridSize];
|
||||||
errorRow = i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check column parities
|
|
||||||
for (let j = 0; j < gridSize; j++) {
|
for (let j = 0; j < gridSize; j++) {
|
||||||
const colBits = parityGrid.slice(0, gridSize).map(row => row[j]);
|
const colBits = parityGrid.slice(0, gridSize).map(row => row[j]);
|
||||||
const expectedParity = calculateParity(colBits);
|
const expectedParity = calculateParity(colBits);
|
||||||
if (expectedParity !== parityGrid[gridSize][j]) {
|
colParities[j] = expectedParity !== parityGrid[gridSize][j];
|
||||||
errorCol = 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) {
|
if (errorRow >= 0 && errorCol >= 0) {
|
||||||
setErrorDetected({row: errorRow, col: errorCol});
|
setErrorDetected({row: errorRow, col: errorCol});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAnimationState({
|
||||||
|
isAnimating: false,
|
||||||
|
currentRow: -1,
|
||||||
|
currentCol: -1,
|
||||||
|
showRowParity: false,
|
||||||
|
showColParity: false,
|
||||||
|
rowParities: [],
|
||||||
|
colParities: []
|
||||||
|
});
|
||||||
|
|
||||||
setPhase('detect');
|
setPhase('detect');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -128,6 +185,15 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
||||||
setFlippedCell(null);
|
setFlippedCell(null);
|
||||||
setErrorDetected(null);
|
setErrorDetected(null);
|
||||||
setPhase('setup');
|
setPhase('setup');
|
||||||
|
setAnimationState({
|
||||||
|
isAnimating: false,
|
||||||
|
currentRow: -1,
|
||||||
|
currentCol: -1,
|
||||||
|
showRowParity: false,
|
||||||
|
showColParity: false,
|
||||||
|
rowParities: [],
|
||||||
|
colParities: []
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderGrid = () => {
|
const renderGrid = () => {
|
||||||
|
|
@ -135,37 +201,77 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
||||||
const size = parityBitsAdded ? gridSize + 1 : gridSize;
|
const size = parityBitsAdded ? gridSize + 1 : gridSize;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-1 justify-center" style={{gridTemplateColumns: `repeat(${size}, 1fr)`}}>
|
<div className="flex flex-col items-center gap-2">
|
||||||
{Array(size).fill(null).map((_, row) =>
|
{/* Main grid with row labels */}
|
||||||
Array(size).fill(null).map((_, col) => {
|
<div className="flex items-center gap-2">
|
||||||
const isParityCell = parityBitsAdded && (row === gridSize || col === gridSize);
|
<div className="grid gap-1" style={{gridTemplateColumns: `repeat(${size}, 1fr)`}}>
|
||||||
const isFlipped = flippedCell?.row === row && flippedCell?.col === col;
|
{Array(size).fill(null).map((_, row) =>
|
||||||
const isError = errorDetected?.row === row && errorDetected?.col === col;
|
Array(size).fill(null).map((_, col) => {
|
||||||
const isErrorRow = errorDetected && row === errorDetected.row;
|
const isParityCell = parityBitsAdded && (row === gridSize || col === gridSize);
|
||||||
const isErrorCol = errorDetected && col === errorDetected.col;
|
const isFlipped = flippedCell?.row === row && flippedCell?.col === col;
|
||||||
|
const isError = errorDetected?.row === row && errorDetected?.col === col;
|
||||||
return (
|
const isCurrentRowBeingChecked = animationState.currentRow === row && !isParityCell;
|
||||||
<button
|
const isCurrentColBeingChecked = animationState.currentCol === col && !isParityCell;
|
||||||
key={`${row}-${col}`}
|
const shouldHighlightForAnimation = (isCurrentRowBeingChecked || isCurrentColBeingChecked) && currentGrid[row]?.[col];
|
||||||
className={`
|
|
||||||
w-12 h-12 border-2 transition-all duration-200
|
return (
|
||||||
${currentGrid[row]?.[col] ? 'bg-foreground' : 'bg-background'}
|
<button
|
||||||
${isParityCell ? 'border-primary' : 'border-foreground'}
|
key={`${row}-${col}`}
|
||||||
${isFlipped ? 'ring-4 ring-destructive' : ''}
|
className={`
|
||||||
${isError ? 'ring-4 ring-destructive bg-destructive/20' : ''}
|
w-12 h-12 border-2 transition-all duration-200
|
||||||
${isErrorRow || isErrorCol ? 'ring-2 ring-warning' : ''}
|
${currentGrid[row]?.[col] ? 'bg-foreground' : 'bg-background'}
|
||||||
${phase === 'setup' && !isParityCell ? 'hover:bg-accent cursor-pointer' : ''}
|
${isParityCell ? 'border-primary' : 'border-foreground'}
|
||||||
${phase === 'parity' && !isParityCell ? 'hover:bg-accent cursor-pointer' : ''}
|
${isFlipped ? 'ring-4 ring-destructive' : ''}
|
||||||
${isParityCell ? 'cursor-not-allowed' : ''}
|
${isError ? 'ring-4 ring-green-500 bg-green-500/20' : ''}
|
||||||
`}
|
${shouldHighlightForAnimation ? 'ring-4 ring-yellow-400 bg-yellow-400/30' : ''}
|
||||||
onClick={() => {
|
${phase === 'setup' && !isParityCell ? 'hover:bg-accent cursor-pointer' : ''}
|
||||||
if (phase === 'setup') toggleCell(row, col);
|
${phase === 'parity' && !isParityCell ? 'hover:bg-accent cursor-pointer' : ''}
|
||||||
else if (phase === 'parity') flipBit(row, col);
|
${isParityCell ? 'cursor-not-allowed' : ''}
|
||||||
}}
|
`}
|
||||||
disabled={isParityCell}
|
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>
|
</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 === '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 === '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 === '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>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
|
@ -220,10 +326,10 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={identifyFlip}
|
onClick={identifyFlip}
|
||||||
disabled={phase !== 'flip'}
|
disabled={phase !== 'flip' || animationState.isAnimating}
|
||||||
variant={phase === 'flip' ? 'default' : 'outline'}
|
variant={phase === 'flip' ? 'default' : 'outline'}
|
||||||
>
|
>
|
||||||
Identify Flip
|
{animationState.isAnimating ? 'Checking...' : 'Identify Flip'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -253,8 +359,8 @@ const ParityBitsGame = ({ showSocialShare = true }: ParityBitsGameProps) => {
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{errorDetected && (
|
{errorDetected && (
|
||||||
<p className="text-sm text-destructive font-medium">
|
<p className="text-sm text-green-600 font-medium">
|
||||||
Error detected at position ({errorDetected.row + 1}, {errorDetected.col + 1})!
|
Error detected at position ({errorDetected.row + 1}, {errorDetected.col + 1}) - highlighted in green!
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue