Add Cube Coloring interactive
This commit is contained in:
parent
7a166fc8d0
commit
9a127a5e22
7 changed files with 1160 additions and 22 deletions
793
package-lock.json
generated
793
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -39,6 +39,8 @@
|
||||||
"@radix-ui/react-toggle": "^1.1.0",
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
|
"@react-three/drei": "^9.122.0",
|
||||||
|
"@react-three/fiber": "^8.18.0",
|
||||||
"@tanstack/react-query": "^5.56.2",
|
"@tanstack/react-query": "^5.56.2",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"canvas-confetti": "^1.9.3",
|
"canvas-confetti": "^1.9.3",
|
||||||
|
|
@ -61,6 +63,7 @@
|
||||||
"sonner": "^1.5.0",
|
"sonner": "^1.5.0",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"three": "^0.160.1",
|
||||||
"vaul": "^0.9.3",
|
"vaul": "^0.9.3",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ import ParityBitsGamePage from './pages/ParityBitsGamePage';
|
||||||
import CrapsGamePage from './pages/CrapsGamePage';
|
import CrapsGamePage from './pages/CrapsGamePage';
|
||||||
import NeighborSumAvoidancePage from './pages/NeighborSumAvoidancePage';
|
import NeighborSumAvoidancePage from './pages/NeighborSumAvoidancePage';
|
||||||
import BurnsidesLemmaPage from './pages/BurnsidesLemmaPage';
|
import BurnsidesLemmaPage from './pages/BurnsidesLemmaPage';
|
||||||
|
import CubeColoringPage from './pages/CubeColoringPage';
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
|
@ -85,6 +86,7 @@ const App = () => (
|
||||||
<Route path="/themes/discrete-math/graphs" element={<Graphs />} />
|
<Route path="/themes/discrete-math/graphs" element={<Graphs />} />
|
||||||
<Route path="/themes/discrete-math/graphs/neighbor-sum-avoidance" element={<NeighborSumAvoidancePage />} />
|
<Route path="/themes/discrete-math/graphs/neighbor-sum-avoidance" element={<NeighborSumAvoidancePage />} />
|
||||||
<Route path="/themes/discrete-math/structures/burnsides-lemma" element={<BurnsidesLemmaPage />} />
|
<Route path="/themes/discrete-math/structures/burnsides-lemma" element={<BurnsidesLemmaPage />} />
|
||||||
|
<Route path="/themes/discrete-math/structures/cube-coloring" element={<CubeColoringPage />} />
|
||||||
<Route path="/themes/social-choice" element={<SocialChoice />} />
|
<Route path="/themes/social-choice" element={<SocialChoice />} />
|
||||||
<Route path="/themes/advanced-algorithms" element={<AdvancedAlgorithms />} />
|
<Route path="/themes/advanced-algorithms" element={<AdvancedAlgorithms />} />
|
||||||
<Route path="/themes/data-structures" element={<DataStructures />} />
|
<Route path="/themes/data-structures" element={<DataStructures />} />
|
||||||
|
|
|
||||||
345
src/components/CubeColoring.tsx
Normal file
345
src/components/CubeColoring.tsx
Normal file
|
|
@ -0,0 +1,345 @@
|
||||||
|
import { useState, useRef } from 'react';
|
||||||
|
import { Canvas, useFrame } from '@react-three/fiber';
|
||||||
|
import { OrbitControls } from '@react-three/drei';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import SocialShare from './SocialShare';
|
||||||
|
import { Box, RotateCw, ArrowRightLeft } from 'lucide-react';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
interface CubeColoringProps {
|
||||||
|
showSocialShare?: boolean;
|
||||||
|
shareUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorPalette = [
|
||||||
|
'#ef4444', // red
|
||||||
|
'#3b82f6', // blue
|
||||||
|
'#22c55e', // green
|
||||||
|
'#eab308', // yellow
|
||||||
|
'#a855f7', // purple
|
||||||
|
];
|
||||||
|
|
||||||
|
// Face indices: 0=top, 1=bottom, 2=front, 3=back, 4=right, 5=left
|
||||||
|
const faceNames = ['Top', 'Bottom', 'Front', 'Back', 'Right', 'Left'];
|
||||||
|
|
||||||
|
// Rotation definitions: each rotation maps face indices [0,1,2,3,4,5] to new positions
|
||||||
|
const rotations = [
|
||||||
|
{ name: 'Identity', permutation: [0, 1, 2, 3, 4, 5], description: 'No rotation' },
|
||||||
|
{ name: 'Y-90° (vertical)', permutation: [0, 1, 4, 5, 3, 2], description: 'Front→Right→Back→Left' },
|
||||||
|
{ name: 'Y-180°', permutation: [0, 1, 3, 2, 5, 4], description: 'Front↔Back, Right↔Left' },
|
||||||
|
{ name: 'Y-270°', permutation: [0, 1, 5, 4, 2, 3], description: 'Front→Left→Back→Right' },
|
||||||
|
{ name: 'X-90° (horizontal)', permutation: [2, 3, 1, 0, 4, 5], description: 'Top→Front→Bottom→Back' },
|
||||||
|
{ name: 'X-180°', permutation: [1, 0, 3, 2, 4, 5], description: 'Top↔Bottom, Front↔Back' },
|
||||||
|
{ name: 'X-270°', permutation: [3, 2, 0, 1, 4, 5], description: 'Top→Back→Bottom→Front' },
|
||||||
|
{ name: 'Z-90° (front-back)', permutation: [4, 5, 2, 3, 1, 0], description: 'Top→Right→Bottom→Left' },
|
||||||
|
{ name: 'Z-180°', permutation: [1, 0, 2, 3, 5, 4], description: 'Top↔Bottom, Right↔Left' },
|
||||||
|
{ name: 'Z-270°', permutation: [5, 4, 2, 3, 0, 1], description: 'Top→Left→Bottom→Right' },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface PaintableCubeProps {
|
||||||
|
colors: string[];
|
||||||
|
onFaceClick: (faceIndex: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaintableCube({ colors, onFaceClick }: PaintableCubeProps) {
|
||||||
|
const meshRef = useRef<THREE.Mesh>(null);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (meshRef.current) {
|
||||||
|
meshRef.current.rotation.y += 0.005;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh ref={meshRef}>
|
||||||
|
<boxGeometry args={[2, 2, 2]} />
|
||||||
|
{colors.map((color, i) => (
|
||||||
|
<meshStandardMaterial key={i} attach={`material-${i}`} color={color} />
|
||||||
|
))}
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PermutationCube({ permutation }: { permutation: number[] }) {
|
||||||
|
const meshRef = useRef<THREE.Mesh>(null);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (meshRef.current) {
|
||||||
|
meshRef.current.rotation.y += 0.005;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create materials based on permutation
|
||||||
|
const baseMaterials = [
|
||||||
|
new THREE.MeshStandardMaterial({ color: '#ef4444' }), // red - top
|
||||||
|
new THREE.MeshStandardMaterial({ color: '#3b82f6' }), // blue - bottom
|
||||||
|
new THREE.MeshStandardMaterial({ color: '#22c55e' }), // green - front
|
||||||
|
new THREE.MeshStandardMaterial({ color: '#eab308' }), // yellow - back
|
||||||
|
new THREE.MeshStandardMaterial({ color: '#a855f7' }), // purple - right
|
||||||
|
new THREE.MeshStandardMaterial({ color: '#f97316' }), // orange - left
|
||||||
|
];
|
||||||
|
|
||||||
|
// Apply permutation: materials[i] shows the color that should be at position i
|
||||||
|
const permutedMaterials = permutation.map(originalIndex => baseMaterials[originalIndex]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh ref={meshRef}>
|
||||||
|
<boxGeometry args={[2, 2, 2]} />
|
||||||
|
{permutedMaterials.map((material, i) => (
|
||||||
|
<primitive key={i} object={material} attach={`material-${i}`} />
|
||||||
|
))}
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CubeColoring = ({ showSocialShare = false, shareUrl }: CubeColoringProps) => {
|
||||||
|
const [faceColors, setFaceColors] = useState<string[]>(Array(6).fill(colorPalette[0]));
|
||||||
|
const [selectedColor, setSelectedColor] = useState(colorPalette[0]);
|
||||||
|
const [selectedPermutation, setSelectedPermutation] = useState<number[]>([0, 1, 2, 3, 4, 5]);
|
||||||
|
const [selectedRotation, setSelectedRotation] = useState(0);
|
||||||
|
|
||||||
|
const handleFaceClick = (faceIndex: number) => {
|
||||||
|
const newColors = [...faceColors];
|
||||||
|
newColors[faceIndex] = selectedColor;
|
||||||
|
setFaceColors(newColors);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePermutationChange = (position: number, value: string) => {
|
||||||
|
const newPerm = [...selectedPermutation];
|
||||||
|
newPerm[position] = parseInt(value);
|
||||||
|
setSelectedPermutation(newPerm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const permutationToRotation = () => {
|
||||||
|
const match = rotations.findIndex(r =>
|
||||||
|
r.permutation.every((val, idx) => val === selectedPermutation[idx])
|
||||||
|
);
|
||||||
|
return match >= 0 ? rotations[match] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Box className="w-6 h-6" />
|
||||||
|
Burnside's Lemma: Cube Coloring
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
How many distinct cubes can you make by painting each face in one of 5 colors?
|
||||||
|
Two cubes are the same if one can be rotated to look like the other.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-8">
|
||||||
|
{/* 3D Cube Arena */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-semibold">Paint Your Cube</h3>
|
||||||
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
|
<div className="h-[300px] border border-border rounded-lg bg-muted/20">
|
||||||
|
<Canvas camera={{ position: [4, 4, 4] }}>
|
||||||
|
<ambientLight intensity={0.5} />
|
||||||
|
<directionalLight position={[10, 10, 5]} intensity={1} />
|
||||||
|
<PaintableCube colors={faceColors} onFaceClick={handleFaceClick} />
|
||||||
|
<OrbitControls enableZoom={true} />
|
||||||
|
</Canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Select Color</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{colorPalette.map((color) => (
|
||||||
|
<button
|
||||||
|
key={color}
|
||||||
|
onClick={() => setSelectedColor(color)}
|
||||||
|
className={`w-12 h-12 rounded-lg border-2 transition-all ${
|
||||||
|
selectedColor === color ? 'border-foreground scale-110' : 'border-border'
|
||||||
|
}`}
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Face Colors</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{faceNames.map((name, idx) => (
|
||||||
|
<div key={idx} className="flex items-center gap-2 p-2 bg-muted/50 rounded">
|
||||||
|
<div
|
||||||
|
className="w-6 h-6 rounded border border-border"
|
||||||
|
style={{ backgroundColor: faceColors[idx] }}
|
||||||
|
/>
|
||||||
|
<span className="text-sm">{name}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Permutation to Rotation */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
|
<ArrowRightLeft className="w-5 h-5" />
|
||||||
|
Permutation → Rotation
|
||||||
|
</h3>
|
||||||
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
|
<Card className="bg-muted/30">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base">Pick a Permutation</CardTitle>
|
||||||
|
<CardDescription className="text-xs">
|
||||||
|
Choose where each face (0-5) goes
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{selectedPermutation.map((value, idx) => (
|
||||||
|
<div key={idx} className="flex items-center gap-3">
|
||||||
|
<Badge variant="outline" className="w-20">
|
||||||
|
Face {idx}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-muted-foreground">→</span>
|
||||||
|
<Select
|
||||||
|
value={value.toString()}
|
||||||
|
onValueChange={(v) => handlePermutationChange(idx, v)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-24">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{[0, 1, 2, 3, 4, 5].map((n) => (
|
||||||
|
<SelectItem key={n} value={n.toString()}>
|
||||||
|
{n}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<span className="text-xs text-muted-foreground">({faceNames[idx]})</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="bg-muted/30">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base">Corresponding Rotation</CardTitle>
|
||||||
|
<CardDescription className="text-xs">
|
||||||
|
Visualize the rotation
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{permutationToRotation() ? (
|
||||||
|
<>
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<Badge className="text-lg px-4 py-2">
|
||||||
|
{permutationToRotation()!.name}
|
||||||
|
</Badge>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{permutationToRotation()!.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="h-[200px] border border-border rounded-lg bg-background/50">
|
||||||
|
<Canvas camera={{ position: [4, 4, 4] }}>
|
||||||
|
<ambientLight intensity={0.5} />
|
||||||
|
<directionalLight position={[10, 10, 5]} intensity={1} />
|
||||||
|
<PermutationCube permutation={selectedPermutation} />
|
||||||
|
<OrbitControls enableZoom={true} />
|
||||||
|
</Canvas>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
|
<p>Invalid permutation</p>
|
||||||
|
<p className="text-xs mt-1">Each number 0-5 must appear exactly once</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rotation to Permutation */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
|
<RotateCw className="w-5 h-5" />
|
||||||
|
Rotation → Permutation
|
||||||
|
</h3>
|
||||||
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
|
<Card className="bg-muted/30">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base">Corresponding Permutation</CardTitle>
|
||||||
|
<CardDescription className="text-xs">
|
||||||
|
How faces move with this rotation
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{rotations[selectedRotation].permutation.map((target, source) => (
|
||||||
|
<div key={source} className="flex items-center gap-3">
|
||||||
|
<Badge variant="outline" className="w-20">
|
||||||
|
Face {source}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-muted-foreground">→</span>
|
||||||
|
<Badge className="w-20">Face {target}</Badge>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{faceNames[source]} → {faceNames[target]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="bg-muted/30">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base">Pick a Rotation</CardTitle>
|
||||||
|
<CardDescription className="text-xs">
|
||||||
|
Select a cube rotation
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<Select
|
||||||
|
value={selectedRotation.toString()}
|
||||||
|
onValueChange={(v) => setSelectedRotation(parseInt(v))}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{rotations.map((rot, idx) => (
|
||||||
|
<SelectItem key={idx} value={idx.toString()}>
|
||||||
|
{rot.name} - {rot.description}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<div className="h-[200px] border border-border rounded-lg bg-background/50">
|
||||||
|
<Canvas camera={{ position: [4, 4, 4] }}>
|
||||||
|
<ambientLight intensity={0.5} />
|
||||||
|
<directionalLight position={[10, 10, 5]} intensity={1} />
|
||||||
|
<PermutationCube permutation={rotations[selectedRotation].permutation} />
|
||||||
|
<OrbitControls enableZoom={true} />
|
||||||
|
</Canvas>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{showSocialShare && (
|
||||||
|
<SocialShare
|
||||||
|
url={shareUrl || window.location.href}
|
||||||
|
title="Burnside's Lemma - Cube Coloring"
|
||||||
|
description="Explore cube rotations and permutations with Burnside's Lemma"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CubeColoring;
|
||||||
|
|
@ -261,6 +261,15 @@ const allInteractives: Interactive[] = [
|
||||||
path: '/themes/discrete-math/structures/burnsides-lemma',
|
path: '/themes/discrete-math/structures/burnsides-lemma',
|
||||||
theme: 'Discrete Math',
|
theme: 'Discrete Math',
|
||||||
dateAdded: '2025-01-22'
|
dateAdded: '2025-01-22'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cube-coloring',
|
||||||
|
title: "Burnside's Lemma: Cube Coloring",
|
||||||
|
description: 'Explore cube rotations and their correspondence with permutations. Paint a 3D cube and understand the rotational symmetry group.',
|
||||||
|
tags: ['group-theory', 'symmetry', 'cube', 'rotations', 'permutations', 'algebra', '3d'],
|
||||||
|
path: '/themes/discrete-math/structures/cube-coloring',
|
||||||
|
theme: 'Discrete Math',
|
||||||
|
dateAdded: '2025-01-22'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
17
src/pages/CubeColoringPage.tsx
Normal file
17
src/pages/CubeColoringPage.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import Layout from '@/components/Layout';
|
||||||
|
import CubeColoring from '@/components/CubeColoring';
|
||||||
|
|
||||||
|
const CubeColoringPage = () => {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<div className="container mx-auto px-4 py-8">
|
||||||
|
<CubeColoring
|
||||||
|
showSocialShare={true}
|
||||||
|
shareUrl={window.location.href}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CubeColoringPage;
|
||||||
|
|
@ -5,6 +5,7 @@ import { Badge } from '@/components/ui/badge';
|
||||||
import Layout from '@/components/Layout';
|
import Layout from '@/components/Layout';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import BurnsidesLemma from '@/components/BurnsidesLemma';
|
import BurnsidesLemma from '@/components/BurnsidesLemma';
|
||||||
|
import CubeColoring from '@/components/CubeColoring';
|
||||||
|
|
||||||
interface Interactive {
|
interface Interactive {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -24,6 +25,14 @@ const interactives: Interactive[] = [
|
||||||
path: '/themes/discrete-math/structures/burnsides-lemma',
|
path: '/themes/discrete-math/structures/burnsides-lemma',
|
||||||
component: BurnsidesLemma
|
component: BurnsidesLemma
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'cube-coloring',
|
||||||
|
title: "Burnside's Lemma: Cube Coloring",
|
||||||
|
description: 'Explore cube rotations and their correspondence with permutations. Paint a 3D cube and understand the rotational symmetry group.',
|
||||||
|
tags: ['group-theory', 'symmetry', 'cube', 'rotations', 'permutations', 'algebra', '3d'],
|
||||||
|
path: '/themes/discrete-math/structures/cube-coloring',
|
||||||
|
component: CubeColoring
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Structures = () => {
|
const Structures = () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue