Add parity bit interactive
Implement a new interactive game based on parity bits. The game features an n x n grid of toggleable squares, where 'n' is adjustable via a slider (2-10). Users can set the grid, add parity bits to an extra row and column, flip a bit, and then identify the flipped bit by highlighting the corresponding row and column.
This commit is contained in:
parent
5adaa9e1a3
commit
db72f6c63c
3 changed files with 288 additions and 0 deletions
|
|
@ -54,6 +54,7 @@ import PebblePlacementGamePage from './pages/PebblePlacementGamePage';
|
||||||
import BulgarianSolitairePage from './pages/BulgarianSolitairePage';
|
import BulgarianSolitairePage from './pages/BulgarianSolitairePage';
|
||||||
import SubtractionGamePage from './pages/SubtractionGamePage';
|
import SubtractionGamePage from './pages/SubtractionGamePage';
|
||||||
import StackingBlocksPage from './pages/StackingBlocksPage';
|
import StackingBlocksPage from './pages/StackingBlocksPage';
|
||||||
|
import ParityBitsGamePage from './pages/ParityBitsGamePage';
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
|
@ -88,6 +89,7 @@ const App = () => (
|
||||||
<Route path="/themes/cards-math" element={<CardsMath />} />
|
<Route path="/themes/cards-math" element={<CardsMath />} />
|
||||||
<Route path="/themes/puzzles" element={<Puzzles />} />
|
<Route path="/themes/puzzles" element={<Puzzles />} />
|
||||||
<Route path="/themes/puzzles/stacking-blocks" element={<StackingBlocksPage />} />
|
<Route path="/themes/puzzles/stacking-blocks" element={<StackingBlocksPage />} />
|
||||||
|
<Route path="/parity-bits" element={<ParityBitsGamePage />} />
|
||||||
<Route path="/themes/miscellany" element={<Miscellany />} />
|
<Route path="/themes/miscellany" element={<Miscellany />} />
|
||||||
<Route path="/themes/contest-problems" element={<ContestProblems />} />
|
<Route path="/themes/contest-problems" element={<ContestProblems />} />
|
||||||
<Route path="/contest-problems/grid-tiling" element={<GridTilingPuzzlePage />} />
|
<Route path="/contest-problems/grid-tiling" element={<GridTilingPuzzlePage />} />
|
||||||
|
|
|
||||||
274
src/components/ParityBitsGame.tsx
Normal file
274
src/components/ParityBitsGame.tsx
Normal file
|
|
@ -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<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 border-foreground transition-all duration-200
|
||||||
|
${currentGrid[row]?.[col] ? 'bg-foreground' : 'bg-background'}
|
||||||
|
${isParityCell ? 'border-primary bg-primary/10' : ''}
|
||||||
|
${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 opacity-70' : ''}
|
||||||
|
`}
|
||||||
|
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">
|
||||||
|
Gray cells are parity bits. 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;
|
||||||
12
src/pages/ParityBitsGamePage.tsx
Normal file
12
src/pages/ParityBitsGamePage.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Layout from "@/components/Layout";
|
||||||
|
import ParityBitsGame from "@/components/ParityBitsGame";
|
||||||
|
|
||||||
|
const ParityBitsGamePage = () => {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<ParityBitsGame />
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ParityBitsGamePage;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue