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:
gpt-engineer-app[bot] 2025-08-22 17:30:30 +00:00
parent 352b2342ed
commit 0bb5653759

View file

@ -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>