diff --git a/.agents/mcp_config.json b/.agents/mcp_config.json deleted file mode 100644 index 4047ca9..0000000 --- a/.agents/mcp_config.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "mcpServers": { - "playwright": { - "command": "npx", - "args": [ - "-y", - "@executeautomation/playwright-mcp-server" - ] - }, - "filesystem": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-filesystem", - "C:/Users/HP/eclipse-workspace/AlgorithmRaceVisualizerCopy1" - ] - }, - "puppeteer": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-puppeteer" - ] - }, - "context7": { - "command": "npx", - "args": [ - "-y", - "@upstash/context7-mcp@latest" - ] - } - } -} diff --git a/.vscode/settings.json b/.vscode/settings.json index ccf4e69..e012065 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,4 @@ { "java.compile.nullAnalysis.mode": "automatic", - "java.configuration.updateBuildConfiguration": "interactive", - "mcp.servers.data-agent-kit.enabled": false, - "mcp.servers.notebooks.enabled": false, - "mcp.servers.visualization.enabled": false + "java.configuration.updateBuildConfiguration": "interactive" } \ No newline at end of file diff --git a/backend/src/test/java/com/algorithmrace/visualizer/algorithms/pathfinding/PathfindingAlgorithmsTest.java b/backend/src/test/java/com/algorithmrace/visualizer/algorithms/pathfinding/PathfindingAlgorithmsTest.java index 130ad5a..2985006 100644 --- a/backend/src/test/java/com/algorithmrace/visualizer/algorithms/pathfinding/PathfindingAlgorithmsTest.java +++ b/backend/src/test/java/com/algorithmrace/visualizer/algorithms/pathfinding/PathfindingAlgorithmsTest.java @@ -16,7 +16,7 @@ class PathfindingAlgorithmsTest { private final SimulationService simulationService = new SimulationService(); private final List algorithms = - List.of("BFS", "DFS", "Dijkstra", "A* Search", "Bidirectional BFS"); + List.of("BFS", "DFS", "Dijkstra", "A* Search", "Bidirectional BFS", "Jump Point Search"); @Test @DisplayName("Verify pathfinding models find path in unblocked grid") diff --git a/frontend/src/components/AlgorithmMatrix.tsx b/frontend/src/components/AlgorithmMatrix.tsx index 70e0cab..6dcb85f 100644 --- a/frontend/src/components/AlgorithmMatrix.tsx +++ b/frontend/src/components/AlgorithmMatrix.tsx @@ -1,11 +1,10 @@ import { useState } from 'react'; import { Play, Search, ShieldCheck, Sparkles } from 'lucide-react'; -import { BigOGraph } from './BigOGraph'; export interface AlgorithmItem { id: string; name: string; - category: 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees'; + category: 'sorting' | 'searching' | 'pathfinding'; categoryLabel: string; bestTime: string; avgTime: string; @@ -238,101 +237,29 @@ const ALGORITHM_DATA: AlgorithmItem[] = [ space: 'O(V)', description: 'Recursive stack-based exploration traversing deepest branch vertices.', }, - - // Dynamic Programming - { - id: 'knapsack', - name: '0/1 Knapsack Problem', - category: 'dp', - categoryLabel: 'DP Arena', - bestTime: 'O(n·W)', - avgTime: 'O(n·W)', - worstTime: 'O(n·W)', - space: 'O(n·W)', - description: '2D table memoization to select optimal subsets of weighted items without exceeding capacity.', - }, - { - id: 'lcs', - name: 'Longest Common Subsequence (LCS)', - category: 'dp', - categoryLabel: 'DP Arena', - bestTime: 'O(m·n)', - avgTime: 'O(m·n)', - worstTime: 'O(m·n)', - space: 'O(m·n)', - description: 'Finds the longest subsequence present in both strings in the same relative order.', - }, - { - id: 'editdistance', - name: 'Edit Distance (Levenshtein)', - category: 'dp', - categoryLabel: 'DP Arena', - bestTime: 'O(m·n)', - avgTime: 'O(m·n)', - worstTime: 'O(m·n)', - space: 'O(m·n)', - description: 'Computes the minimum number of insertions, deletions, and replacements to convert one string to another.', - }, - - // Tree Structures - { - id: 'bst', - name: 'Binary Search Tree (BST)', - category: 'trees', - categoryLabel: 'Tree Arena', - bestTime: 'O(log n)', - avgTime: 'O(log n)', - worstTime: 'O(n)', - space: 'O(n)', - description: 'Hierarchical node structure maintaining left-smaller and right-greater invariants.', - }, - { - id: 'avl', - name: 'AVL Tree (Self-Balancing)', - category: 'trees', - categoryLabel: 'Tree Arena', - bestTime: 'O(log n)', - avgTime: 'O(log n)', - worstTime: 'O(log n)', - space: 'O(n)', - description: 'Strictly height-balanced BST guaranteeing O(log n) lookups via single and double rotations.', - }, - { - id: 'redblack', - name: 'Red-Black Tree', - category: 'trees', - categoryLabel: 'Tree Arena', - bestTime: 'O(log n)', - avgTime: 'O(log n)', - worstTime: 'O(log n)', - space: 'O(n)', - description: 'Self-balancing binary tree using color properties to ensure near-optimal height during updates.', - }, ]; interface Props { - onNavigate: (category: 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees') => void; + onNavigate: (category: 'sorting' | 'searching' | 'pathfinding') => void; } export function AlgorithmMatrix({ onNavigate }: Props) { - const [filterCategory, setFilterCategory] = useState<'all' | 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees'>('all'); + const [filterCategory, setFilterCategory] = useState<'all' | 'sorting' | 'searching' | 'pathfinding'>('all'); const [searchQuery, setSearchQuery] = useState(''); - const [hoveredComplexity, setHoveredComplexity] = useState(undefined); const filteredAlgorithms = ALGORITHM_DATA.filter((item) => { const matchesCategory = filterCategory === 'all' || item.category === filterCategory; const matchesSearch = item.name.toLowerCase().includes(searchQuery.toLowerCase()) || - item.description.toLowerCase().includes(searchQuery.toLowerCase()) || - item.categoryLabel.toLowerCase().includes(searchQuery.toLowerCase()); + item.description.toLowerCase().includes(searchQuery.toLowerCase()); return matchesCategory && matchesSearch; }); const getComplexityClass = (complexity: string) => { - if (complexity.includes('1') || complexity.includes('log n') || complexity.includes('√n') || complexity.includes('log3 n') || complexity.includes('log log n')) { + if (complexity.includes('1') || complexity.includes('log n') || complexity.includes('√n')) { return 'badge-complexity-optimal'; } - if (complexity.includes('n log n') || complexity.includes('V + E') || complexity.includes('(V + E) log V') || complexity.includes('V log V')) { + if (complexity.includes('n log n') || complexity.includes('V + E')) { return 'badge-complexity-good'; } return 'badge-complexity-heavy'; @@ -345,35 +272,26 @@ export function AlgorithmMatrix({ onNavigate }: Props) { ALGORITHM CATALOG & COMPLEXITY MATRIX -

Benchmark Directory & Asymptotic Curves

+

Benchmark Directory

- Explore asymptotic time and space bounds across all 20+ supported competitive algorithm suites with live Big-O curve synchronization. + Explore asymptotic time and space bounds across supported competitive algorithm suites.

- {/* Integrated Interactive Big-O Growth Curves */} -
- -
-
- {(['all', 'sorting', 'searching', 'pathfinding', 'dp', 'trees'] as const).map((cat) => ( + {(['all', 'sorting', 'searching', 'pathfinding'] as const).map((cat) => ( ))}
@@ -382,7 +300,7 @@ export function AlgorithmMatrix({ onNavigate }: Props) { setSearchQuery(e.target.value)} className="matrix-search-input" @@ -406,12 +324,7 @@ export function AlgorithmMatrix({ onNavigate }: Props) { {filteredAlgorithms.map((algo) => ( - setHoveredComplexity(algo.avgTime)} - onMouseLeave={() => setHoveredComplexity(undefined)} - > +
{algo.name} diff --git a/frontend/src/components/BigOGraph.tsx b/frontend/src/components/BigOGraph.tsx deleted file mode 100644 index 677633c..0000000 --- a/frontend/src/components/BigOGraph.tsx +++ /dev/null @@ -1,537 +0,0 @@ -import { useState, useMemo, useRef } from 'react'; -import { Sliders, Sparkles, TrendingUp, Info } from 'lucide-react'; - -interface Props { - highlightedComplexity?: string; -} - -interface ComplexityClass { - id: string; - name: string; - label: string; - color: string; - glowColor: string; - description: string; - examples: string; - calc: (n: number) => number; -} - -const COMPLEXITY_CLASSES: ComplexityClass[] = [ - { - id: 'O(1)', - name: 'Constant', - label: 'O(1)', - color: '#10b981', - glowColor: 'rgba(16, 185, 129, 0.8)', - description: 'Execution time remains flat regardless of dataset size.', - examples: 'Array index lookup, Hash map get, Push/Pop stack', - calc: () => 1, - }, - { - id: 'O(log n)', - name: 'Logarithmic', - label: 'O(log n)', - color: '#06b6d4', - glowColor: 'rgba(6, 182, 212, 0.8)', - description: 'Search space is halved in each step. Highly scalable for billions of items.', - examples: 'Binary Search, AVL Tree lookup, B-Tree indexing', - calc: (n) => Math.max(1, Math.round(Math.log2(Math.max(1, n)) * 10) / 10), - }, - { - id: 'O(n)', - name: 'Linear', - label: 'O(n)', - color: '#3b82f6', - glowColor: 'rgba(59, 130, 246, 0.8)', - description: 'Execution time grows proportionally with the input size.', - examples: 'Linear Search, Counting Sort, Array traversal', - calc: (n) => n, - }, - { - id: 'O(n log n)', - name: 'Linearithmic', - label: 'O(n log n)', - color: '#f59e0b', - glowColor: 'rgba(245, 158, 11, 0.8)', - description: 'Gold standard for general-purpose comparison-based sorting.', - examples: 'MergeSort, QuickSort (Avg), HeapSort', - calc: (n) => Math.round(n * Math.log2(Math.max(1, n))), - }, - { - id: 'O(n^2)', - name: 'Quadratic', - label: 'O(n²)', - color: '#f43f5e', - glowColor: 'rgba(244, 63, 94, 0.8)', - description: 'Operations grow with the square of input size. Impractical for massive datasets.', - examples: 'BubbleSort, InsertionSort (Worst), SelectionSort', - calc: (n) => Math.pow(n, 2), - }, - { - id: 'O(2^n)', - name: 'Exponential', - label: 'O(2ⁿ)', - color: '#ec4899', - glowColor: 'rgba(236, 72, 153, 0.8)', - description: 'Operations double with every added element. Rapidly explodes.', - examples: 'Recursive Fibonacci, Power Set generation, Traveling Salesperson (Brute)', - calc: (n) => Math.pow(2, Math.min(n, 30)), - }, -]; - -export function BigOGraph({ highlightedComplexity }: Props) { - const [nValue, setNValue] = useState(16); - const [selectedClassId, setSelectedClassId] = useState(null); - const [scaleMode, setScaleMode] = useState<'linear' | 'log'>('linear'); - const [hoveredN, setHoveredN] = useState(null); - const svgRef = useRef(null); - - // SVG Coordinate Constants (Optimized for sharp rendering across all viewports) - const width = 680; - const height = 290; - const paddingLeft = 56; - const paddingBottom = 38; - const paddingTop = 22; - const paddingRight = 30; - - const graphWidth = width - paddingLeft - paddingRight; - const graphHeight = height - paddingTop - paddingBottom; - - // Max X and Y bounds for graph rendering - const maxX = 64; - const maxYLinear = 2500; - const maxYLog = 6; // 6 decades (10^0 = 1 to 10^6 = 1,000,000) - - const activeN = hoveredN !== null ? hoveredN : nValue; - - // Map value to SVG Y based on current scale mode - const mapYtoSvg = (val: number): number => { - if (scaleMode === 'linear') { - const clampedY = Math.max(0, val); - return paddingTop + graphHeight - (clampedY / maxYLinear) * graphHeight; - } else { - // Logarithmic scaling: log10(1) = 0 to log10(1000000) = 6 - const logVal = Math.log10(Math.max(1, val)); - const normalized = Math.min(1.2, Math.max(0, logVal / maxYLog)); - return paddingTop + graphHeight - normalized * graphHeight; - } - }; - - // Generate SVG Path for a given complexity class with proper boundary exit (no flatlining) - const generateCurvePath = (calc: (n: number) => number) => { - const points: [number, number][] = []; - const samples = 90; - - for (let i = 1; i <= samples; i++) { - const xVal = (i / samples) * maxX; - const yVal = calc(xVal); - - const svgX = paddingLeft + (xVal / maxX) * graphWidth; - const svgY = mapYtoSvg(yVal); - points.push([svgX, svgY]); - } - - return points.reduce((acc, [x, y], idx) => { - return idx === 0 ? `M ${x.toFixed(1)} ${y.toFixed(1)}` : `${acc} L ${x.toFixed(1)} ${y.toFixed(1)}`; - }, ''); - }; - - // Determine which class is active - const activeClass = useMemo(() => { - if (selectedClassId) { - return COMPLEXITY_CLASSES.find((c) => c.id === selectedClassId) || null; - } - if (highlightedComplexity) { - return ( - COMPLEXITY_CLASSES.find( - (c) => - highlightedComplexity.toLowerCase().includes(c.id.toLowerCase()) || - highlightedComplexity.toLowerCase().includes(c.label.toLowerCase()) - ) || null - ); - } - return null; - }, [selectedClassId, highlightedComplexity]); - - // Handle interactive SVG scrubbing - const handleSvgPointerMove = (e: React.PointerEvent) => { - if (!svgRef.current) return; - const rect = svgRef.current.getBoundingClientRect(); - const clientX = e.clientX - rect.left; - const svgX = (clientX / rect.width) * width; - - if (svgX >= paddingLeft && svgX <= width - paddingRight) { - const ratio = (svgX - paddingLeft) / graphWidth; - const calculatedN = Math.max(1, Math.min(maxX, Math.round(ratio * maxX))); - setHoveredN(calculatedN); - } - }; - - const handleSvgPointerLeave = () => { - setHoveredN(null); - }; - - const handleSvgClick = (e: React.PointerEvent) => { - if (!svgRef.current) return; - const rect = svgRef.current.getBoundingClientRect(); - const clientX = e.clientX - rect.left; - const svgX = (clientX / rect.width) * width; - - if (svgX >= paddingLeft && svgX <= width - paddingRight) { - const ratio = (svgX - paddingLeft) / graphWidth; - const calculatedN = Math.max(1, Math.min(maxX, Math.round(ratio * maxX))); - setNValue(calculatedN); - } - }; - - const formatOps = (ops: number): string => { - if (ops >= 1000000) return `${(ops / 1000000).toFixed(1)}M`; - if (ops >= 1000) return `${(ops / 1000).toFixed(1)}k`; - return String(ops); - }; - - const markerX = paddingLeft + (activeN / maxX) * graphWidth; - - return ( -
- {/* Header with Title and Interactive Controls */} -
-
-
- -
-
-

Asymptotic Complexity Growth Curves

-

- Visualizing mathematical operation growth curves (N = 1 to 64) across algorithmic complexity classes. -

-
-
- - {/* Dataset Size Controls & Scale Mode Switcher */} -
- {/* Scale Toggle: Linear vs Logarithmic */} -
- - -
- - {/* Dynamic N Slider */} -
-
- - Dataset Size: - N = {activeN} -
- { - const val = Number(e.target.value); - setNValue(val); - setHoveredN(null); - }} - className="n-range-slider" - aria-label="Adjust dataset size N" - /> -
- {[8, 16, 32, 64].map((preset) => ( - - ))} -
-
-
-
- - {/* SVG Growth Chart with Responsive ViewBox & ClipPath */} -
- - - {/* Strict plot clip boundary to ensure curves exit smoothly without breaking graph borders */} - - - - - - {/* Plot Background Accent */} - - - {/* Coordinate Axes */} - - - - {/* Horizontal Grid Guide Lines */} - {[0.25, 0.5, 0.75].map((fraction) => { - const y = paddingTop + graphHeight * (1 - fraction); - const labelVal = scaleMode === 'linear' - ? `${Math.round(maxYLinear * fraction)}` - : `10^${(maxYLog * fraction).toFixed(0)}`; - - return ( - - - - {labelVal} - - - ); - })} - - {/* Vertical Grid Guide Lines */} - {[0.25, 0.5, 0.75, 1].map((fraction) => { - const x = paddingLeft + graphWidth * fraction; - const labelN = Math.round(maxX * fraction); - return ( - - - - {labelN} - - - ); - })} - - {/* Render Complexity Curves (Clipped smoothly to graph bounds) */} - - {COMPLEXITY_CLASSES.map((cls) => { - const pathData = generateCurvePath(cls.calc); - const isHighlighted = activeClass?.id === cls.id; - const isDimmed = activeClass !== null && !isHighlighted; - - return ( - - {/* Glow Aura for Highlighted Curve */} - {isHighlighted && ( - - )} - - - - ); - })} - - - {/* Interactive Crosshair & Cursor Line */} - - - - {/* Glowing Points on Every Curve at Active N */} - {COMPLEXITY_CLASSES.map((cls) => { - const ops = cls.calc(activeN); - const pointY = mapYtoSvg(ops); - const isHighlighted = activeClass?.id === cls.id; - const isDimmed = activeClass !== null && !isHighlighted; - - // Only render point if within graph bounds - if (pointY < paddingTop - 4 || pointY > paddingTop + graphHeight + 4) return null; - - return ( - - ); - })} - - {/* Bottom N Pill Indicator */} - - - - {/* Axis Labels */} - - {scaleMode === 'linear' ? 'Operations (Ops)' : 'Log₁₀ Ops'} - - - Input Size (N) → - - -
- - {/* Complexity Class Legend & Live Counter Pills */} -
- {COMPLEXITY_CLASSES.map((cls) => { - const ops = cls.calc(activeN); - const formattedOps = formatOps(ops); - const isSelected = activeClass?.id === cls.id; - - return ( - - ); - })} -
- - {/* Active Complexity Detail Card */} - {activeClass && ( -
-
- - - {activeClass.label} ({activeClass.name}) - - - ≈ {activeClass.calc(activeN).toLocaleString()} operations at N = {activeN} - -
-

{activeClass.description}

-
- - Common in: {activeClass.examples} -
-
- )} -
- ); -} diff --git a/frontend/src/components/CodePlayground.tsx b/frontend/src/components/CodePlayground.tsx deleted file mode 100644 index 8aabc39..0000000 --- a/frontend/src/components/CodePlayground.tsx +++ /dev/null @@ -1,625 +0,0 @@ -import { useState, useEffect } from 'react'; -import { - Code2, - Copy, - Check, - Play, - Pause, - RotateCcw, - SkipForward, - Terminal, - Sparkles, -} from 'lucide-react'; - -type SupportedLanguage = 'typescript' | 'python' | 'java' | 'cpp'; -type SupportedAlgorithm = 'quicksort' | 'binarysearch' | 'astar' | 'knapsack' | 'avl'; - -interface CodeSnippet { - lines: string[]; - activeStepLines: number[]; // Maps step index (0..n) to 1-based line number in `lines` -} - -const CODE_DATABASE: Record> = { - quicksort: { - typescript: { - lines: [ - 'function quickSort(arr: number[], low: number, high: number): void {', - ' if (low < high) {', - ' // Partition array and get pivot index', - ' const pivotIdx = partition(arr, low, high);', - ' // Recursively sort left and right partitions', - ' quickSort(arr, low, pivotIdx - 1);', - ' quickSort(arr, pivotIdx + 1, high);', - ' }', - '}', - '', - 'function partition(arr: number[], low: number, high: number): number {', - ' const pivot = arr[high];', - ' let i = low - 1;', - ' for (let j = low; j < high; j++) {', - ' if (arr[j] < pivot) {', - ' i++;', - ' [arr[i], arr[j]] = [arr[j], arr[i]]; // Swap', - ' }', - ' }', - ' [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];', - ' return i + 1;', - '}', - ], - activeStepLines: [2, 4, 12, 14, 17, 20, 6, 7], - }, - python: { - lines: [ - 'def quick_sort(arr: list[int], low: int, high: int) -> None:', - ' if low < high:', - ' # Partition array around dynamic pivot', - ' pivot_idx = partition(arr, low, high)', - ' # Recursively conquer sub-arrays', - ' quick_sort(arr, low, pivot_idx - 1)', - ' quick_sort(arr, pivot_idx + 1, high)', - '', - 'def partition(arr: list[int], low: int, high: int) -> int:', - ' pivot = arr[high]', - ' i = low - 1', - ' for j in range(low, high):', - ' if arr[j] < pivot:', - ' i += 1', - ' arr[i], arr[j] = arr[j], arr[i]', - ' arr[i + 1], arr[high] = arr[high], arr[i + 1]', - ' return i + 1', - ], - activeStepLines: [2, 4, 10, 12, 15, 16, 6, 7], - }, - java: { - lines: [ - 'public class QuickSort {', - ' public static void sort(int[] arr, int low, int high) {', - ' if (low < high) {', - ' int pIndex = partition(arr, low, high);', - ' sort(arr, low, pIndex - 1);', - ' sort(arr, pIndex + 1, high);', - ' }', - ' }', - '', - ' private static int partition(int[] arr, int low, int high) {', - ' int pivot = arr[high];', - ' int i = (low - 1);', - ' for (int j = low; j < high; j++) {', - ' if (arr[j] < pivot) {', - ' i++;', - ' swap(arr, i, j);', - ' }', - ' }', - ' swap(arr, i + 1, high);', - ' return i + 1;', - ' }', - '}', - ], - activeStepLines: [3, 4, 11, 13, 16, 19, 5, 6], - }, - cpp: { - lines: [ - '#include ', - '#include ', - '', - 'int partition(std::vector& arr, int low, int high) {', - ' int pivot = arr[high];', - ' int i = low - 1;', - ' for (int j = low; j < high; j++) {', - ' if (arr[j] < pivot) {', - ' i++;', - ' std::swap(arr[i], arr[j]);', - ' }', - ' }', - ' std::swap(arr[i + 1], arr[high]);', - ' return i + 1;', - '}', - '', - 'void quickSort(std::vector& arr, int low, int high) {', - ' if (low < high) {', - ' int p = partition(arr, low, high);', - ' quickSort(arr, low, p - 1);', - ' quickSort(arr, p + 1, high);', - ' }', - '}', - ], - activeStepLines: [18, 19, 5, 7, 10, 13, 20, 21], - }, - }, - - binarysearch: { - typescript: { - lines: [ - 'function binarySearch(arr: number[], target: number): number {', - ' let low = 0;', - ' let high = arr.length - 1;', - '', - ' while (low <= high) {', - ' const mid = Math.floor((low + high) / 2);', - ' if (arr[mid] === target) return mid; // Found!', - ' if (arr[mid] < target) {', - ' low = mid + 1; // Discard left half', - ' } else {', - ' high = mid - 1; // Discard right half', - ' }', - ' }', - ' return -1; // Not found', - '}', - ], - activeStepLines: [2, 3, 5, 6, 8, 9, 7], - }, - python: { - lines: [ - 'def binary_search(arr: list[int], target: int) -> int:', - ' low = 0', - ' high = len(arr) - 1', - '', - ' while low <= high:', - ' mid = (low + high) // 2', - ' if arr[mid] == target:', - ' return mid # Target lock', - ' elif arr[mid] < target:', - ' low = mid + 1', - ' else:', - ' high = mid - 1', - ' return -1', - ], - activeStepLines: [2, 3, 5, 6, 9, 10, 8], - }, - java: { - lines: [ - 'public class BinarySearch {', - ' public static int search(int[] arr, int target) {', - ' int low = 0;', - ' int high = arr.length - 1;', - ' while (low <= high) {', - ' int mid = low + (high - low) / 2;', - ' if (arr[mid] == target) return mid;', - ' if (arr[mid] < target) low = mid + 1;', - ' else high = mid - 1;', - ' }', - ' return -1;', - ' }', - '}', - ], - activeStepLines: [3, 4, 5, 6, 8, 9, 7], - }, - cpp: { - lines: [ - '#include ', - '', - 'int binarySearch(const std::vector& arr, int target) {', - ' int low = 0;', - ' int high = static_cast(arr.size()) - 1;', - ' while (low <= high) {', - ' int mid = low + (high - low) / 2;', - ' if (arr[mid] == target) return mid;', - ' if (arr[mid] < target) low = mid + 1;', - ' else high = mid - 1;', - ' }', - ' return -1;', - '}', - ], - activeStepLines: [4, 5, 6, 7, 9, 10, 8], - }, - }, - - astar: { - typescript: { - lines: [ - 'function aStar(start: Node, target: Node, grid: Grid): Path {', - ' const openSet = new PriorityQueue((a, b) => a.f - b.f);', - ' openSet.push(start);', - '', - ' while (!openSet.isEmpty()) {', - ' const current = openSet.pop()!;', - ' if (current.equals(target)) return reconstructPath(current);', - '', - ' for (const neighbor of grid.getNeighbors(current)) {', - ' const tentativeG = current.g + distance(current, neighbor);', - ' if (tentativeG < neighbor.g) {', - ' neighbor.parent = current;', - ' neighbor.g = tentativeG;', - ' neighbor.f = neighbor.g + heuristic(neighbor, target);', - ' if (!openSet.contains(neighbor)) openSet.push(neighbor);', - ' }', - ' }', - ' }', - ' return []; // Path not found', - '}', - ], - activeStepLines: [2, 3, 5, 6, 9, 10, 14, 7], - }, - python: { - lines: [ - 'import heapq', - '', - 'def a_star_search(start, target, grid):', - ' open_set = []', - ' heapq.heappush(open_set, (0, start))', - ' came_from = {}', - ' g_score = {start: 0}', - '', - ' while open_set:', - ' _, current = heapq.heappop(open_set)', - ' if current == target:', - ' return reconstruct_path(came_from, current)', - '', - ' for neighbor in grid.neighbors(current):', - ' tentative_g = g_score[current] + cost(current, neighbor)', - ' if tentative_g < g_score.get(neighbor, float("inf")):', - ' came_from[neighbor] = current', - ' g_score[neighbor] = tentative_g', - ' f_score = tentative_g + heuristic(neighbor, target)', - ' heapq.heappush(open_set, (f_score, neighbor))', - ' return []', - ], - activeStepLines: [4, 5, 9, 10, 14, 15, 19, 12], - }, - java: { - lines: [ - 'public List aStar(Node start, Node target, Grid grid) {', - ' PriorityQueue openSet = new PriorityQueue<>(Comparator.comparingDouble(n -> n.f));', - ' openSet.add(start);', - ' while (!openSet.isEmpty()) {', - ' Node current = openSet.poll();', - ' if (current.equals(target)) return buildPath(current);', - ' for (Node neighbor : grid.getNeighbors(current)) {', - ' double tentativeG = current.g + distance(current, neighbor);', - ' if (tentativeG < neighbor.g) {', - ' neighbor.parent = current;', - ' neighbor.g = tentativeG;', - ' neighbor.f = neighbor.g + heuristic(neighbor, target);', - ' openSet.add(neighbor);', - ' }', - ' }', - ' }', - ' return Collections.emptyList();', - '}', - ], - activeStepLines: [2, 3, 4, 5, 7, 8, 12, 6], - }, - cpp: { - lines: [ - '#include ', - '#include ', - '', - 'std::vector aStar(Node* start, Node* target, Grid& grid) {', - ' std::priority_queue, CompareF> openSet;', - ' openSet.push(start);', - ' while (!openSet.empty()) {', - ' Node* current = openSet.top(); openSet.pop();', - ' if (current == target) return reconstructPath(current);', - ' for (Node* neighbor : grid.getNeighbors(current)) {', - ' double tentativeG = current->g + dist(current, neighbor);', - ' if (tentativeG < neighbor->g) {', - ' neighbor->parent = current;', - ' neighbor->g = tentativeG;', - ' neighbor->f = neighbor->g + heuristic(neighbor, target);', - ' openSet.push(neighbor);', - ' }', - ' }', - ' }', - ' return {};', - '}', - ], - activeStepLines: [5, 6, 7, 8, 10, 11, 15, 9], - }, - }, - - knapsack: { - typescript: { - lines: [ - 'function knapsack01(weights: number[], values: number[], capacity: number): number {', - ' const n = weights.length;', - ' const dp: number[][] = Array.from({ length: n + 1 }, () => Array(capacity + 1).fill(0));', - '', - ' for (let i = 1; i <= n; i++) {', - ' for (let w = 1; w <= capacity; w++) {', - ' if (weights[i - 1] <= w) {', - ' dp[i][w] = Math.max(', - ' dp[i - 1][w], // Exclude item', - ' values[i - 1] + dp[i - 1][w - weights[i - 1]] // Include item', - ' );', - ' } else {', - ' dp[i][w] = dp[i - 1][w];', - ' }', - ' }', - ' }', - ' return dp[n][capacity];', - '}', - ], - activeStepLines: [3, 5, 6, 7, 8, 13, 17], - }, - python: { - lines: [ - 'def knapsack_01(weights: list[int], values: list[int], capacity: int) -> int:', - ' n = len(weights)', - ' dp = [[0] * (capacity + 1) for _ in range(n + 1)]', - '', - ' for i in range(1, n + 1):', - ' for w in range(1, capacity + 1):', - ' if weights[i - 1] <= w:', - ' dp[i][w] = max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]])', - ' else:', - ' dp[i][w] = dp[i - 1][w]', - ' return dp[n][capacity]', - ], - activeStepLines: [3, 5, 6, 7, 8, 10, 11], - }, - java: { - lines: [ - 'public class Knapsack {', - ' public static int solve(int[] weights, int[] values, int capacity) {', - ' int n = weights.length;', - ' int[][] dp = new int[n + 1][capacity + 1];', - ' for (int i = 1; i <= n; i++) {', - ' for (int w = 1; w <= capacity; w++) {', - ' if (weights[i - 1] <= w) {', - ' dp[i][w] = Math.max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]]);', - ' } else {', - ' dp[i][w] = dp[i - 1][w];', - ' }', - ' }', - ' }', - ' return dp[n][capacity];', - ' }', - '}', - ], - activeStepLines: [4, 5, 6, 7, 8, 10, 14], - }, - cpp: { - lines: [ - '#include ', - '#include ', - '', - 'int knapsack01(const std::vector& weights, const std::vector& values, int W) {', - ' int n = weights.size();', - ' std::vector> dp(n + 1, std::vector(W + 1, 0));', - ' for (int i = 1; i <= n; ++i) {', - ' for (int w = 1; w <= W; ++w) {', - ' if (weights[i - 1] <= w)', - ' dp[i][w] = std::max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]]);', - ' else', - ' dp[i][w] = dp[i - 1][w];', - ' }', - ' }', - ' return dp[n][W];', - '}', - ], - activeStepLines: [6, 7, 8, 9, 10, 12, 15], - }, - }, - - avl: { - typescript: { - lines: [ - 'function rightRotate(y: AVLNode): AVLNode {', - ' const x = y.left!;', - ' const T2 = x.right;', - ' x.right = y;', - ' y.left = T2;', - ' y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;', - ' x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;', - ' return x; // New root of subtree', - '}', - '', - 'function getBalance(node: AVLNode | null): number {', - ' return node ? getHeight(node.left) - getHeight(node.right) : 0;', - '}', - ], - activeStepLines: [2, 3, 4, 5, 6, 7, 8, 12], - }, - python: { - lines: [ - 'def right_rotate(y: AVLNode) -> AVLNode:', - ' x = y.left', - ' t2 = x.right', - ' x.right = y', - ' y.left = t2', - ' y.height = max(get_height(y.left), get_height(y.right)) + 1', - ' x.height = max(get_height(x.left), get_height(x.right)) + 1', - ' return x # New subtree root', - '', - 'def get_balance(node: AVLNode) -> int:', - ' return get_height(node.left) - get_height(node.right) if node else 0', - ], - activeStepLines: [2, 3, 4, 5, 6, 7, 8, 11], - }, - java: { - lines: [ - 'public class AVLTree {', - ' private Node rightRotate(Node y) {', - ' Node x = y.left;', - ' Node T2 = x.right;', - ' x.right = y;', - ' y.left = T2;', - ' y.height = Math.max(height(y.left), height(y.right)) + 1;', - ' x.height = Math.max(height(x.left), height(x.right)) + 1;', - ' return x;', - ' }', - '', - ' private int getBalance(Node n) {', - ' return (n == null) ? 0 : height(n.left) - height(n.right);', - ' }', - '}', - ], - activeStepLines: [3, 4, 5, 6, 7, 8, 9, 13], - }, - cpp: { - lines: [ - 'Node* rightRotate(Node* y) {', - ' Node* x = y->left;', - ' Node* T2 = x->right;', - ' x->right = y;', - ' y->left = T2;', - ' y->height = std::max(height(y->left), height(y->right)) + 1;', - ' x->height = std::max(height(x->left), height(x->right)) + 1;', - ' return x;', - '}', - '', - 'int getBalance(Node* n) {', - ' return n ? height(n->left) - height(n->right) : 0;', - '}', - ], - activeStepLines: [2, 3, 4, 5, 6, 7, 8, 12], - }, - }, -}; - -export function CodePlayground() { - const [algo, setAlgo] = useState('quicksort'); - const [lang, setLang] = useState('typescript'); - const [stepIdx, setStepIdx] = useState(0); - const [isPlaying, setIsPlaying] = useState(false); - const [copied, setCopied] = useState(false); - - const snippet = CODE_DATABASE[algo][lang]; - const maxSteps = snippet.activeStepLines.length; - - // Handle Auto-Play timer - useEffect(() => { - if (!isPlaying) return; - const timer = setInterval(() => { - setStepIdx((prev) => (prev + 1) % maxSteps); - }, 1200); - return () => clearInterval(timer); - }, [isPlaying, maxSteps]); - - // Reset step index when algorithm or language changes - useEffect(() => { - setStepIdx(0); - setIsPlaying(false); - }, [algo, lang]); - - const activeLineNumber = snippet.activeStepLines[stepIdx] ?? 1; - - const handleCopyCode = () => { - const fullCode = snippet.lines.join('\n'); - navigator.clipboard.writeText(fullCode).then(() => { - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }); - }; - - return ( -
- {/* Top Window Header (macOS Terminal Style) */} -
-
- - - -
- - {/* Algorithm Dropdown / Tabs */} -
- {( - [ - { id: 'quicksort', label: 'QuickSort' }, - { id: 'binarysearch', label: 'Binary Search' }, - { id: 'astar', label: 'A* Search' }, - { id: 'knapsack', label: '0/1 Knapsack' }, - { id: 'avl', label: 'AVL Rotation' }, - ] as const - ).map((item) => ( - - ))} -
- - {/* Copy Button */} - -
- - {/* Language Tabs & Playback Stepper Toolbar */} -
- {/* Language Tabs */} -
- {( - [ - { id: 'typescript', label: 'TypeScript', ext: '.ts' }, - { id: 'python', label: 'Python', ext: '.py' }, - { id: 'java', label: 'Java', ext: '.java' }, - { id: 'cpp', label: 'C++', ext: '.cpp' }, - ] as const - ).map((item) => ( - - ))} -
- - {/* Step-by-Step Execution Controls */} -
- - Line {activeLineNumber} • Step {stepIdx + 1}/{maxSteps} - - - - - - - -
-
- - {/* Code Editor Body with Line-by-Line Tracking */} -
-
-          
-            {snippet.lines.map((lineText, lineIdx) => {
-              const lineNum = lineIdx + 1;
-              const isActive = lineNum === activeLineNumber;
-
-              return (
-                
- {lineNum} - {lineText || ' '} - {isActive && ← ACTIVE} -
- ); - })} -
-
-
-
- ); -} diff --git a/frontend/src/components/HeroMiniCanvas.tsx b/frontend/src/components/HeroMiniCanvas.tsx index be4c6a9..c4112ca 100644 --- a/frontend/src/components/HeroMiniCanvas.tsx +++ b/frontend/src/components/HeroMiniCanvas.tsx @@ -1,20 +1,7 @@ import { useEffect, useRef, useState, useCallback } from 'react'; -import { - Play, - Pause, - RotateCcw, - Zap, - BarChart3, - GitBranch, - Layers, - Cpu, - Binary, - FastForward, -} from 'lucide-react'; +import { Play, Pause, RotateCcw, Zap } from 'lucide-react'; -export type HeroSimMode = 'sorting' | 'pathfinding' | 'dp' | 'trees' | 'searching'; - -interface SortingStep { +interface Step { array: number[]; comparing: number[]; swapping: number[]; @@ -22,108 +9,61 @@ interface SortingStep { pivot?: number; } -interface PathfindingStep { - grid: number[][]; // 0: empty, 1: wall, 2: visitedA, 3: visitedB, 4: path, 5: start, 6: target - pathNodes: [number, number][]; - currentPos?: [number, number]; - stats: { visited: number; pathLength: number; status: string }; -} - -interface DPStep { - table: (number | null)[][]; - currentRow: number; - currentCol: number; - highlightedCells: [number, number][]; - optimalPath: [number, number][]; - currentVal: number; -} - -interface TreeStep { - nodes: { id: number; val: number; x: number; y: number; level: number; status: 'normal' | 'active' | 'rotated' | 'balanced' }[]; - edges: { from: number; to: number }[]; - statusText: string; -} - -interface SearchStep { - array: number[]; - low: number; - mid: number; - high: number; - target: number; - found: boolean; - stepCount: number; -} - function isMobileViewport() { - return typeof window !== 'undefined' && window.innerWidth <= 768; + return window.innerWidth <= 768; } function prefersReducedMotion() { - return typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches; + return window.matchMedia('(prefers-reduced-motion: reduce)').matches; } export function HeroMiniCanvas() { const canvasRef = useRef(null); const containerRef = useRef(null); - - const [mode, setMode] = useState('sorting'); const [isPlaying, setIsPlaying] = useState(() => !prefersReducedMotion()); - const [speedMultiplier, setSpeedMultiplier] = useState<1 | 2 | 4>(1); const [isMobileDevice, setIsMobileDevice] = useState(isMobileViewport); const [reducedMotion, setReducedMotion] = useState(prefersReducedMotion); const [isVisible, setIsVisible] = useState(true); - - // Dynamic Telemetry States - const [sortingStats, setSortingStats] = useState({ - lane1: { name: 'QuickSort', comps: 0, swaps: 0, status: 'Racing...' }, - lane2: { name: 'BubbleSort', comps: 0, swaps: 0, status: 'Racing...' }, - }); - const [pathfindingStats, setPathfindingStats] = useState({ visited: 0, pathLength: 0, status: 'Exploring...' }); - const [dpStats, setDPStats] = useState({ cell: '0,0', optimalVal: 0, status: 'Filling Matrix...' }); - const [treeStats, setTreeStats] = useState({ balance: 'In-Balance', rotations: 0, status: 'Inserting nodes...' }); - const [searchStats, setSearchStats] = useState({ low: 0, mid: 0, high: 0, step: 0, status: 'Halving Search Space...' }); - - // Simulation State Storage - const simState = useRef<{ - stepIdx: number; - maxSteps: number; + const [lane1Algo] = useState('Quick Sort'); + const [lane2Algo] = useState('Bubble Sort'); + const [lane1Stats, setLane1Stats] = useState({ comparisons: 0, swaps: 0, status: 'Racing...' }); + const [lane2Stats, setLane2Stats] = useState({ comparisons: 0, swaps: 0, status: 'Racing...' }); + + const stateRef = useRef<{ + lane1Steps: Step[]; + lane2Steps: Step[]; + lane1Idx: number; + lane2Idx: number; + arraySize: number; + initialArray: number[]; timer: number | null; - // Sorting Data - qSteps: SortingStep[]; - bSteps: SortingStep[]; - // Pathfinding Data - pathSteps: PathfindingStep[]; - // DP Data - dpSteps: DPStep[]; - // Tree Data - treeSteps: TreeStep[]; - // Search Data - searchSteps: SearchStep[]; }>({ - stepIdx: 0, - maxSteps: 0, + lane1Steps: [], + lane2Steps: [], + lane1Idx: 0, + lane2Idx: 0, + arraySize: 20, + initialArray: [], timer: null, - qSteps: [], - bSteps: [], - pathSteps: [], - dpSteps: [], - treeSteps: [], - searchSteps: [], }); - // Handle Resize and Accessibility preferences + // Keep the simulator live on mobile while honoring accessibility preferences. useEffect(() => { - const handleResize = () => { - setIsMobileDevice(isMobileViewport()); - const reduceMotion = prefersReducedMotion(); - setReducedMotion(reduceMotion); - if (reduceMotion) setIsPlaying(false); + const checkMobile = () => { + const isMobile = isMobileViewport(); + const shouldReduceMotion = prefersReducedMotion(); + setIsMobileDevice(isMobile); + setReducedMotion(shouldReduceMotion); + if (shouldReduceMotion) { + setIsPlaying(false); + } }; - window.addEventListener('resize', handleResize, { passive: true }); - return () => window.removeEventListener('resize', handleResize); + checkMobile(); + window.addEventListener('resize', checkMobile, { passive: true }); + return () => window.removeEventListener('resize', checkMobile); }, []); - // IntersectionObserver: Pause simulation when scrolled out of view to consume 0% idle CPU + // IntersectionObserver to pause rendering when scrolled offscreen useEffect(() => { if (!containerRef.current) return; const observer = new IntersectionObserver( @@ -136,9 +76,9 @@ export function HeroMiniCanvas() { return () => observer.disconnect(); }, []); - // Tab Visibility API: Pause when backgrounded + // Tab Visibility API to pause rendering when tab is hidden useEffect(() => { - const handleVisibility = () => { + const handleVisibilityChange = () => { if (document.hidden) { setIsVisible(false); } else if (containerRef.current) { @@ -146,37 +86,34 @@ export function HeroMiniCanvas() { setIsVisible(rect.top < window.innerHeight && rect.bottom > 0); } }; - document.addEventListener('visibilitychange', handleVisibility); - return () => document.removeEventListener('visibilitychange', handleVisibility); + document.addEventListener('visibilitychange', handleVisibilityChange); + return () => document.removeEventListener('visibilitychange', handleVisibilityChange); }, []); - // ---------------------------------------------------- - // STEP GENERATORS FOR ALL 5 MODES - // ---------------------------------------------------- - - // 1. Sorting Step Generator - const generateSortingSimulation = () => { - const size = isMobileDevice ? 16 : 22; - const arr = Array.from({ length: size }, () => Math.floor(Math.random() * 80) + 20); - - // QuickSort - const qSteps: SortingStep[] = []; + const generateSteps = (arr: number[]) => { + // QuickSort Step Generator + const qSteps: Step[] = []; const qArr = [...arr]; + let qComp = 0; + let qSwap = 0; + const quickSortHelper = (low: number, high: number) => { if (low < high) { const pivotVal = qArr[high]; let i = low - 1; for (let j = low; j < high; j++) { + qComp++; qSteps.push({ array: [...qArr], comparing: [j, high], swapping: [], - sorted: [], + sorted: getSortedIndices(low, high, qArr), pivot: high, }); if (qArr[j] < pivotVal) { i++; if (i !== j) { + qSwap++; const temp = qArr[i]; qArr[i] = qArr[j]; qArr[j] = temp; @@ -184,12 +121,13 @@ export function HeroMiniCanvas() { array: [...qArr], comparing: [], swapping: [i, j], - sorted: [], + sorted: getSortedIndices(low, high, qArr), pivot: high, }); } } } + qSwap++; const temp = qArr[i + 1]; qArr[i + 1] = qArr[high]; qArr[high] = temp; @@ -198,7 +136,7 @@ export function HeroMiniCanvas() { array: [...qArr], comparing: [], swapping: [i + 1, high], - sorted: [pIndex], + sorted: getSortedIndices(low, high, qArr), pivot: pIndex, }); @@ -206,6 +144,15 @@ export function HeroMiniCanvas() { quickSortHelper(pIndex + 1, high); } }; + + const getSortedIndices = (currentLow: number, currentHigh: number, currentArr: number[]) => { + const sorted: number[] = []; + for (let k = 0; k < currentArr.length; k++) { + if (k < currentLow || k > currentHigh) sorted.push(k); + } + return sorted; + }; + quickSortHelper(0, qArr.length - 1); qSteps.push({ array: [...qArr], @@ -214,20 +161,25 @@ export function HeroMiniCanvas() { sorted: Array.from({ length: qArr.length }, (_, k) => k), }); - // BubbleSort - const bSteps: SortingStep[] = []; + // BubbleSort Step Generator + const bSteps: Step[] = []; const bArr = [...arr]; const n = bArr.length; - const sortedSoFar: number[] = []; + let bComp = 0; + let bSwap = 0; + const sortedIndices: number[] = []; + for (let i = 0; i < n - 1; i++) { for (let j = 0; j < n - i - 1; j++) { + bComp++; bSteps.push({ array: [...bArr], comparing: [j, j + 1], swapping: [], - sorted: [...sortedSoFar], + sorted: [...sortedIndices], }); if (bArr[j] > bArr[j + 1]) { + bSwap++; const temp = bArr[j]; bArr[j] = bArr[j + 1]; bArr[j + 1] = temp; @@ -235,13 +187,13 @@ export function HeroMiniCanvas() { array: [...bArr], comparing: [], swapping: [j, j + 1], - sorted: [...sortedSoFar], + sorted: [...sortedIndices], }); } } - sortedSoFar.push(n - 1 - i); + sortedIndices.push(n - 1 - i); } - sortedSoFar.push(0); + sortedIndices.push(0); bSteps.push({ array: [...bArr], comparing: [], @@ -249,922 +201,260 @@ export function HeroMiniCanvas() { sorted: Array.from({ length: n }, (_, k) => k), }); - simState.current.qSteps = qSteps; - simState.current.bSteps = bSteps; - simState.current.maxSteps = Math.max(qSteps.length, bSteps.length); - simState.current.stepIdx = 0; - }; - - // 2. Pathfinding Step Generator (A* Wavefront on 2D Grid) - const generatePathfindingSimulation = () => { - const rows = 11; - const cols = 23; - const grid: number[][] = Array.from({ length: rows }, () => Array(cols).fill(0)); - - const start: [number, number] = [5, 2]; - const target: [number, number] = [5, 20]; - - // Procedural Walls - for (let r = 2; r < 9; r++) { - if (r !== 5 && r !== 6) grid[r][7] = 1; - if (r !== 3 && r !== 4) grid[r][15] = 1; - } - grid[start[0]][start[1]] = 5; - grid[target[0]][target[1]] = 6; - - const steps: PathfindingStep[] = []; - const openSet: [number, number][] = [[start[0], start[1]]]; - const visited = new Set([`${start[0]},${start[1]}`]); - const parentMap = new Map(); - - const directions = [ - [0, 1], - [1, 0], - [0, -1], - [-1, 0], - ]; - - let foundTarget = false; - let iterations = 0; - - while (openSet.length > 0 && !foundTarget && iterations < 200) { - iterations++; - // A* heuristic sort - openSet.sort((a, b) => { - const distA = Math.abs(a[0] - target[0]) + Math.abs(a[1] - target[1]); - const distB = Math.abs(b[0] - target[0]) + Math.abs(b[1] - target[1]); - return distA - distB; - }); - - const current = openSet.shift()!; - const [cr, cc] = current; - - if (cr === target[0] && cc === target[1]) { - foundTarget = true; - break; - } - - if (grid[cr][cc] !== 5 && grid[cr][cc] !== 6) { - grid[cr][cc] = 2; // Visited - } - - for (const [dr, dc] of directions) { - const nr = cr + dr; - const nc = cc + dc; - const key = `${nr},${nc}`; - if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] !== 1 && !visited.has(key)) { - visited.add(key); - parentMap.set(key, [cr, cc]); - openSet.push([nr, nc]); - if (grid[nr][nc] !== 6) { - grid[nr][nc] = 3; // Open Set Wavefront - } - } - } - - const gridCopy = grid.map((r) => [...r]); - steps.push({ - grid: gridCopy, - pathNodes: [], - currentPos: [cr, cc], - stats: { visited: visited.size, pathLength: 0, status: 'A* Wavefront Expanding...' }, - }); - } - - // Trace shortest path - const path: [number, number][] = []; - let currKey = `${target[0]},${target[1]}`; - while (parentMap.has(currKey)) { - const p = parentMap.get(currKey)!; - path.unshift(p); - currKey = `${p[0]},${p[1]}`; - } - - // Path tracing animation steps - const finalGrid = grid.map((r) => [...r]); - for (let i = 0; i < path.length; i++) { - const [pr, pc] = path[i]; - if (finalGrid[pr][pc] !== 5 && finalGrid[pr][pc] !== 6) { - finalGrid[pr][pc] = 4; // Shortest Path - } - steps.push({ - grid: finalGrid.map((r) => [...r]), - pathNodes: path.slice(0, i + 1), - currentPos: [pr, pc], - stats: { visited: visited.size, pathLength: i + 1, status: 'Tracing Optimal Shortest Path 🏆' }, - }); - } - - simState.current.pathSteps = steps; - simState.current.maxSteps = steps.length; - simState.current.stepIdx = 0; - }; - - // 3. Dynamic Programming Step Generator (0/1 Knapsack Grid) - const generateDPMatrixSimulation = () => { - const weights = [2, 3, 4, 5]; - const values = [3, 4, 5, 8]; - const capacity = 6; - const n = weights.length; - - const dp: (number | null)[][] = Array.from({ length: n + 1 }, () => Array(capacity + 1).fill(null)); - for (let w = 0; w <= capacity; w++) dp[0][w] = 0; - for (let i = 0; i <= n; i++) dp[i][0] = 0; - - const steps: DPStep[] = []; - - for (let i = 1; i <= n; i++) { - for (let w = 1; w <= capacity; w++) { - const highlighted: [number, number][] = [[i - 1, w]]; - let optVal = dp[i - 1][w] ?? 0; - - if (weights[i - 1] <= w) { - highlighted.push([i - 1, w - weights[i - 1]]); - optVal = Math.max(optVal, (dp[i - 1][w - weights[i - 1]] ?? 0) + values[i - 1]); - } - dp[i][w] = optVal; - - steps.push({ - table: dp.map((row) => [...row]), - currentRow: i, - currentCol: w, - highlightedCells: highlighted, - optimalPath: [], - currentVal: optVal, - }); - } - } - - // Trace back optimal items - let curW = capacity; - const optCells: [number, number][] = []; - for (let i = n; i > 0; i--) { - if (dp[i][curW] !== dp[i - 1][curW]) { - optCells.push([i, curW]); - curW -= weights[i - 1]; - } - } - - steps.push({ - table: dp.map((row) => [...row]), - currentRow: n, - currentCol: capacity, - highlightedCells: [], - optimalPath: optCells, - currentVal: dp[n][capacity] ?? 0, - }); - - simState.current.dpSteps = steps; - simState.current.maxSteps = steps.length; - simState.current.stepIdx = 0; + return { qSteps, bSteps, qComp, qSwap, bComp, bSwap }; }; - // 4. Tree Balancing / AVL Step Generator - const generateTreeSimulation = () => { - const steps: TreeStep[] = []; - const valuesToInsert = [50, 25, 75, 15, 35, 65, 85, 10]; - - const basePositions = [ - { id: 1, val: 50, x: 290, y: 40, level: 0 }, - { id: 2, val: 25, x: 150, y: 100, level: 1 }, - { id: 3, val: 75, x: 430, y: 100, level: 1 }, - { id: 4, val: 15, x: 80, y: 165, level: 2 }, - { id: 5, val: 35, x: 220, y: 165, level: 2 }, - { id: 6, val: 65, x: 360, y: 165, level: 2 }, - { id: 7, val: 85, x: 500, y: 165, level: 2 }, - { id: 8, val: 10, x: 40, y: 225, level: 3 }, - ]; - - const edges = [ - { from: 1, to: 2 }, - { from: 1, to: 3 }, - { from: 2, to: 4 }, - { from: 2, to: 5 }, - { from: 3, to: 6 }, - { from: 3, to: 7 }, - { from: 4, to: 8 }, - ]; - - for (let i = 1; i <= valuesToInsert.length; i++) { - const activeNodes = basePositions.slice(0, i).map((n, idx) => ({ - ...n, - status: idx === i - 1 ? ('active' as const) : ('normal' as const), - })); - - const activeEdges = edges.filter((e) => e.from <= i && e.to <= i); - - steps.push({ - nodes: activeNodes, - edges: activeEdges, - statusText: `Inserting Node (${valuesToInsert[i - 1]}). Balancing factors: O(log N)`, - }); - } - - // Add balancing rotation frame - steps.push({ - nodes: basePositions.map((n) => ({ - ...n, - status: n.val === 25 || n.val === 15 ? 'rotated' : 'balanced', - })), - edges, - statusText: 'AVL Self-Balancing: Right-Rotation executed. Tree Balanced 🌳', - }); - - simState.current.treeSteps = steps; - simState.current.maxSteps = steps.length; - simState.current.stepIdx = 0; - }; - - // 5. Binary Search Step Generator - const generateSearchingSimulation = () => { - const size = 17; - const array = Array.from({ length: size }, (_, i) => (i + 1) * 5 + Math.floor(Math.random() * 2)); - const target = array[Math.floor(Math.random() * (size - 2)) + 1]; - - const steps: SearchStep[] = []; - let low = 0; - let high = size - 1; - let stepCount = 0; - let found = false; - - while (low <= high) { - stepCount++; - const mid = Math.floor((low + high) / 2); - const isMatch = array[mid] === target; - - steps.push({ - array: [...array], - low, - mid, - high, - target, - found: isMatch, - stepCount, - }); - - if (isMatch) { - found = true; - break; - } - - if (array[mid] < target) { - low = mid + 1; - } else { - high = mid - 1; - } - } - - simState.current.searchSteps = steps; - simState.current.maxSteps = steps.length; - simState.current.stepIdx = 0; - }; - - // ---------------------------------------------------- - // INITIALIZE / RESET ACTIVE SIMULATION - // ---------------------------------------------------- - const resetSimulation = useCallback(() => { - if (mode === 'sorting') { - generateSortingSimulation(); - setSortingStats({ - lane1: { name: 'QuickSort', comps: 0, swaps: 0, status: 'Racing...' }, - lane2: { name: 'BubbleSort', comps: 0, swaps: 0, status: 'Racing...' }, - }); - } else if (mode === 'pathfinding') { - generatePathfindingSimulation(); - setPathfindingStats({ visited: 0, pathLength: 0, status: 'Exploring Grid...' }); - } else if (mode === 'dp') { - generateDPMatrixSimulation(); - setDPStats({ cell: '0,0', optimalVal: 0, status: 'Filling Matrix...' }); - } else if (mode === 'trees') { - generateTreeSimulation(); - setTreeStats({ balance: 'Evaluating', rotations: 0, status: 'Inserting nodes...' }); - } else if (mode === 'searching') { - generateSearchingSimulation(); - setSearchStats({ low: 0, mid: 0, high: 0, step: 0, status: 'Halving Search Space...' }); - } - }, [mode, isMobileDevice]); - - // ---------------------------------------------------- - // CANVAS RENDERING DISPATCHER - // ---------------------------------------------------- const renderCanvas = useCallback(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; - // Retina / High-DPI Scaling - const dpr = window.devicePixelRatio || 1; - const displayWidth = canvas.clientWidth || 580; - const displayHeight = canvas.clientHeight || 260; - - if (canvas.width !== displayWidth * dpr || canvas.height !== displayHeight * dpr) { - canvas.width = displayWidth * dpr; - canvas.height = displayHeight * dpr; - } - - ctx.save(); - ctx.scale(dpr, dpr); - - const width = displayWidth; - const height = displayHeight; + const width = canvas.width; + const height = canvas.height; ctx.clearRect(0, 0, width, height); - // Dark sleek background - ctx.fillStyle = '#080a10'; + // Background fill + ctx.fillStyle = '#090b10'; ctx.fillRect(0, 0, width, height); - // Subtle technical grid - ctx.strokeStyle = 'rgba(255, 255, 255, 0.025)'; + // Subtle Grid background + ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)'; ctx.lineWidth = 1; - for (let x = 0; x < width; x += 22) { + for (let x = 0; x < width; x += 20) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, height); ctx.stroke(); } - for (let y = 0; y < height; y += 22) { + for (let y = 0; y < height; y += 20) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); } - const idx = simState.current.stepIdx; + const laneHeight = (height - 30) / 2; + + // Draw Lane 1 (QuickSort) + drawLane( + ctx, + 0, + 15, + width, + laneHeight, + lane1Algo, + stateRef.current.lane1Steps[stateRef.current.lane1Idx], + '#a855f7' + ); + + // Divider line + ctx.strokeStyle = 'rgba(255, 255, 255, 0.08)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(15, laneHeight + 15); + ctx.lineTo(width - 15, laneHeight + 15); + ctx.stroke(); + + // Draw Lane 2 (BubbleSort) + drawLane( + ctx, + 0, + laneHeight + 25, + width, + laneHeight, + lane2Algo, + stateRef.current.lane2Steps[stateRef.current.lane2Idx], + '#3b82f6' + ); + }, [lane1Algo, lane2Algo]); + + const resetRace = useCallback(() => { + const size = 22; + const arr = Array.from({ length: size }, () => Math.floor(Math.random() * 85) + 15); + const { qSteps, bSteps } = generateSteps(arr); + + stateRef.current.initialArray = arr; + stateRef.current.lane1Steps = qSteps; + stateRef.current.lane2Steps = bSteps; + stateRef.current.lane1Idx = 0; + stateRef.current.lane2Idx = 0; - if (mode === 'sorting') { - drawSortingMode(ctx, width, height, idx); - } else if (mode === 'pathfinding') { - drawPathfindingMode(ctx, width, height, idx); - } else if (mode === 'dp') { - drawDPMode(ctx, width, height, idx); - } else if (mode === 'trees') { - drawTreeMode(ctx, width, height, idx); - } else if (mode === 'searching') { - drawSearchMode(ctx, width, height, idx); + setLane1Stats({ comparisons: 0, swaps: 0, status: 'Racing...' }); + setLane2Stats({ comparisons: 0, swaps: 0, status: 'Racing...' }); + renderCanvas(); + }, [renderCanvas]); + + useEffect(() => { + resetRace(); + }, [resetRace]); + + // Main animation timer effect - slower on mobile, paused when hidden or reduced motion is requested. + useEffect(() => { + if (!isPlaying || !isVisible || reducedMotion) { + if (stateRef.current.timer) clearInterval(stateRef.current.timer); + return; } - ctx.restore(); - }, [mode]); + stateRef.current.timer = window.setInterval(() => { + let l1Finished = false; + let l2Finished = false; - // Mode 1: Draw Sorting - const drawSortingMode = (ctx: CanvasRenderingContext2D, width: number, height: number, stepIdx: number) => { - const laneHeight = (height - 35) / 2; - const qStep = simState.current.qSteps[Math.min(stepIdx, simState.current.qSteps.length - 1)]; - const bStep = simState.current.bSteps[Math.min(stepIdx, simState.current.bSteps.length - 1)]; + if (stateRef.current.lane1Idx < stateRef.current.lane1Steps.length - 1) { + stateRef.current.lane1Idx++; + } else { + l1Finished = true; + } - // Lane 1: QuickSort - drawArrayLane(ctx, 15, laneHeight, qStep, '#a855f7', 'QuickSort (O(N log N))'); + if (stateRef.current.lane2Idx < stateRef.current.lane2Steps.length - 1) { + stateRef.current.lane2Idx++; + } else { + l2Finished = true; + } - // Divider - ctx.strokeStyle = 'rgba(255, 255, 255, 0.07)'; - ctx.beginPath(); - ctx.moveTo(15, laneHeight + 17); - ctx.lineTo(width - 15, laneHeight + 17); - ctx.stroke(); + renderCanvas(); - // Lane 2: BubbleSort - drawArrayLane(ctx, laneHeight + 25, laneHeight, bStep, '#3b82f6', 'BubbleSort (O(N²))'); - }; + setLane1Stats({ + comparisons: Math.floor(stateRef.current.lane1Idx * 0.8), + swaps: Math.floor(stateRef.current.lane1Idx * 0.4), + status: l1Finished ? 'Winner 🏆' : 'Racing...', + }); + + setLane2Stats({ + comparisons: Math.floor(stateRef.current.lane2Idx * 0.9), + swaps: Math.floor(stateRef.current.lane2Idx * 0.5), + status: l2Finished ? 'Completed' : 'Racing...', + }); + + if (l1Finished && l2Finished) { + setTimeout(() => { + resetRace(); + }, 3000); + } + }, isMobileDevice ? 100 : 70); - const drawArrayLane = ( + return () => { + if (stateRef.current.timer) clearInterval(stateRef.current.timer); + }; + }, [isPlaying, isVisible, isMobileDevice, reducedMotion, renderCanvas, resetRace]); + + const drawLane = ( ctx: CanvasRenderingContext2D, + _xOffset: number, yOffset: number, + width: number, height: number, - step: SortingStep | undefined, - primaryColor: string, - _label: string + _title: string, + step: Step | undefined, + primaryGlow: string ) => { if (!step) return; + const padding = 20; - const availableWidth = (canvasRef.current?.clientWidth || 580) - padding * 2; + const availableWidth = width - padding * 2; const n = step.array.length; const barGap = 4; const barWidth = Math.max(3, (availableWidth - (n - 1) * barGap) / n); + const maxVal = 100; step.array.forEach((val, i) => { - const barHeight = (val / maxVal) * (height - 20); + const barHeight = (val / maxVal) * (height - 25); const x = padding + i * (barWidth + barGap); const y = yOffset + height - barHeight; - let fill = '#334155'; + let fillStyle = '#334155'; let shadowColor = 'transparent'; let shadowBlur = 0; if (step.sorted.includes(i)) { - fill = '#10b981'; - shadowColor = 'rgba(16, 185, 129, 0.6)'; + fillStyle = '#10b981'; + shadowColor = 'rgba(16, 185, 129, 0.5)'; shadowBlur = 8; } else if (step.swapping.includes(i)) { - fill = '#ec4899'; + fillStyle = '#ec4899'; shadowColor = 'rgba(236, 72, 153, 0.8)'; - shadowBlur = 10; + shadowBlur = 12; } else if (step.comparing.includes(i)) { - fill = '#f59e0b'; + fillStyle = '#f59e0b'; shadowColor = 'rgba(245, 158, 11, 0.7)'; - shadowBlur = 8; + shadowBlur = 10; } else if (step.pivot === i) { - fill = '#c084fc'; + fillStyle = '#c084fc'; shadowColor = 'rgba(192, 132, 252, 0.8)'; - shadowBlur = 10; + shadowBlur = 12; } else { - fill = primaryColor; + fillStyle = primaryGlow; } ctx.save(); - ctx.fillStyle = fill; + ctx.fillStyle = fillStyle; if (shadowBlur > 0) { ctx.shadowColor = shadowColor; ctx.shadowBlur = shadowBlur; } - ctx.beginPath(); - ctx.roundRect(x, y, barWidth, barHeight, [3, 3, 0, 0]); - ctx.fill(); - ctx.restore(); - }); - }; - - // Mode 2: Draw Pathfinding Grid - const drawPathfindingMode = (ctx: CanvasRenderingContext2D, width: number, height: number, stepIdx: number) => { - const step = simState.current.pathSteps[Math.min(stepIdx, simState.current.pathSteps.length - 1)]; - if (!step) return; - - const rows = step.grid.length; - const cols = step.grid[0].length; - const cellSize = Math.min((width - 40) / cols, (height - 30) / rows); - const startX = (width - cols * cellSize) / 2; - const startY = (height - rows * cellSize) / 2; - - for (let r = 0; r < rows; r++) { - for (let c = 0; c < cols; c++) { - const val = step.grid[r][c]; - const x = startX + c * cellSize; - const y = startY + r * cellSize; - - let fill = '#111827'; - let shadow = 'transparent'; - - if (val === 1) fill = '#374151'; // Wall - else if (val === 2) fill = 'rgba(6, 182, 212, 0.5)'; // Visited - else if (val === 3) fill = 'rgba(245, 158, 11, 0.7)'; // Wavefront Open - else if (val === 4) { - fill = '#10b981'; // Shortest Path - shadow = 'rgba(16, 185, 129, 0.8)'; - } else if (val === 5) fill = '#3b82f6'; // Start - else if (val === 6) fill = '#ef4444'; // Target - - ctx.save(); - ctx.fillStyle = fill; - if (shadow !== 'transparent') { - ctx.shadowColor = shadow; - ctx.shadowBlur = 10; - } - ctx.beginPath(); - ctx.roundRect(x + 1, y + 1, cellSize - 2, cellSize - 2, 2); - ctx.fill(); - ctx.restore(); - } - } - }; - - // Mode 3: Draw DP Matrix - const drawDPMode = (ctx: CanvasRenderingContext2D, width: number, height: number, stepIdx: number) => { - const step = simState.current.dpSteps[Math.min(stepIdx, simState.current.dpSteps.length - 1)]; - if (!step) return; - - const rows = step.table.length; - const cols = step.table[0].length; - const cellW = Math.min(65, (width - 60) / cols); - const cellH = Math.min(38, (height - 40) / rows); - const startX = (width - cols * cellW) / 2; - const startY = (height - rows * cellH) / 2; - - for (let r = 0; r < rows; r++) { - for (let c = 0; c < cols; c++) { - const val = step.table[r][c]; - const x = startX + c * cellW; - const y = startY + r * cellH; - - const isCurrent = r === step.currentRow && c === step.currentCol; - const isHighlight = step.highlightedCells.some(([hr, hc]) => hr === r && hc === c); - const isOptimal = step.optimalPath.some(([opr, opc]) => opr === r && opc === c); - - let bg = 'rgba(255, 255, 255, 0.03)'; - let border = 'rgba(255, 255, 255, 0.08)'; - - if (isCurrent) { - bg = 'rgba(168, 85, 247, 0.35)'; - border = '#c084fc'; - } else if (isHighlight) { - bg = 'rgba(245, 158, 11, 0.25)'; - border = '#f59e0b'; - } else if (isOptimal) { - bg = 'rgba(16, 185, 129, 0.35)'; - border = '#10b981'; - } - - ctx.save(); - ctx.fillStyle = bg; - ctx.strokeStyle = border; - ctx.lineWidth = isCurrent || isOptimal ? 2 : 1; - ctx.beginPath(); - ctx.roundRect(x + 2, y + 2, cellW - 4, cellH - 4, 4); - ctx.fill(); - ctx.stroke(); - - // Cell Value - if (val !== null) { - ctx.fillStyle = isOptimal ? '#10b981' : isCurrent ? '#c084fc' : '#e2e8f0'; - ctx.font = 'bold 12px "JetBrains Mono", monospace'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(String(val), x + cellW / 2, y + cellH / 2); - } - ctx.restore(); - } - } - }; - - // Mode 4: Draw Trees - const drawTreeMode = (ctx: CanvasRenderingContext2D, width: number, height: number, stepIdx: number) => { - const step = simState.current.treeSteps[Math.min(stepIdx, simState.current.treeSteps.length - 1)]; - if (!step) return; - - // Scale positions to fit current width/height - const scaleX = width / 580; - const scaleY = height / 260; - - // Draw Edges - step.edges.forEach((edge) => { - const fromNode = step.nodes.find((n) => n.id === edge.from); - const toNode = step.nodes.find((n) => n.id === edge.to); - if (fromNode && toNode) { - ctx.save(); - ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)'; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(fromNode.x * scaleX, fromNode.y * scaleY); - ctx.lineTo(toNode.x * scaleX, toNode.y * scaleY); - ctx.stroke(); - ctx.restore(); - } - }); - - // Draw Nodes - step.nodes.forEach((node) => { - const nx = node.x * scaleX; - const ny = node.y * scaleY; - const radius = 16; - - let fill = '#1e293b'; - let stroke = '#64748b'; - let shadow = 'transparent'; - - if (node.status === 'active') { - fill = '#7c3aed'; - stroke = '#c084fc'; - shadow = 'rgba(192, 132, 252, 0.8)'; - } else if (node.status === 'rotated') { - fill = '#ec4899'; - stroke = '#f472b6'; - shadow = 'rgba(236, 72, 153, 0.8)'; - } else if (node.status === 'balanced') { - fill = '#059669'; - stroke = '#34d399'; - shadow = 'rgba(52, 211, 153, 0.7)'; - } - - ctx.save(); - ctx.fillStyle = fill; - ctx.strokeStyle = stroke; - ctx.lineWidth = 2; - if (shadow !== 'transparent') { - ctx.shadowColor = shadow; - ctx.shadowBlur = 10; - } - ctx.beginPath(); - ctx.arc(nx, ny, radius, 0, Math.PI * 2); - ctx.fill(); - ctx.stroke(); - - ctx.fillStyle = '#ffffff'; - ctx.font = 'bold 11px "JetBrains Mono", monospace'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(String(node.val), nx, ny); - ctx.restore(); - }); - }; - - // Mode 5: Draw Binary Search - const drawSearchMode = (ctx: CanvasRenderingContext2D, width: number, height: number, stepIdx: number) => { - const step = simState.current.searchSteps[Math.min(stepIdx, simState.current.searchSteps.length - 1)]; - if (!step) return; - - const padding = 20; - const availableWidth = width - padding * 2; - const n = step.array.length; - const boxGap = 4; - const boxWidth = (availableWidth - (n - 1) * boxGap) / n; - const boxHeight = 44; - const yCenter = (height - boxHeight) / 2; - - step.array.forEach((val, i) => { - const x = padding + i * (boxWidth + boxGap); - const inRange = i >= step.low && i <= step.high; - const isMid = i === step.mid; - const isMatch = isMid && step.found; - - let fill = inRange ? '#1e293b' : 'rgba(30, 41, 59, 0.3)'; - let border = inRange ? 'rgba(255, 255, 255, 0.15)' : 'rgba(255, 255, 255, 0.03)'; - let textCol = inRange ? '#e2e8f0' : '#475569'; - let shadow = 'transparent'; - - if (isMatch) { - fill = '#10b981'; - border = '#34d399'; - textCol = '#ffffff'; - shadow = 'rgba(16, 185, 129, 0.8)'; - } else if (isMid) { - fill = '#6366f1'; - border = '#818cf8'; - textCol = '#ffffff'; - shadow = 'rgba(99, 102, 241, 0.8)'; - } - ctx.save(); - ctx.fillStyle = fill; - ctx.strokeStyle = border; - ctx.lineWidth = isMid ? 2 : 1; - if (shadow !== 'transparent') { - ctx.shadowColor = shadow; - ctx.shadowBlur = 10; - } + const radius = 3; ctx.beginPath(); - ctx.roundRect(x, yCenter, boxWidth, boxHeight, 4); + ctx.roundRect(x, y, barWidth, barHeight, [radius, radius, 0, 0]); ctx.fill(); - ctx.stroke(); - - ctx.fillStyle = textCol; - ctx.font = 'bold 12px "JetBrains Mono", monospace'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(String(val), x + boxWidth / 2, yCenter + boxHeight / 2); ctx.restore(); }); }; - // ---------------------------------------------------- - // INITIALIZE / RESET SIMULATION ON MODE SWITCH - // ---------------------------------------------------- - useEffect(() => { - resetSimulation(); - // Render initial static preview frame immediately on mount/mode switch - requestAnimationFrame(() => { - renderCanvas(); - }); - }, [resetSimulation, renderCanvas]); - - // ---------------------------------------------------- - // MAIN ANIMATION LOOP - // ---------------------------------------------------- - useEffect(() => { - if (!isPlaying || !isVisible || reducedMotion) { - if (simState.current.timer) clearInterval(simState.current.timer); - return; - } - - const baseDelay = mode === 'sorting' ? 70 : mode === 'pathfinding' ? 60 : 120; - const intervalTime = Math.max(20, baseDelay / speedMultiplier); - - simState.current.timer = window.setInterval(() => { - const curr = simState.current.stepIdx; - const max = simState.current.maxSteps; - - if (curr < max - 1) { - simState.current.stepIdx++; - renderCanvas(); - - // Update mode telemetry - if (mode === 'sorting') { - const qLen = simState.current.qSteps.length; - const bLen = simState.current.bSteps.length; - setSortingStats({ - lane1: { - name: 'QuickSort', - comps: Math.floor(Math.min(curr, qLen) * 0.8), - swaps: Math.floor(Math.min(curr, qLen) * 0.4), - status: curr >= qLen - 1 ? 'Winner 🏆' : 'Racing...', - }, - lane2: { - name: 'BubbleSort', - comps: Math.floor(Math.min(curr, bLen) * 0.9), - swaps: Math.floor(Math.min(curr, bLen) * 0.5), - status: curr >= bLen - 1 ? 'Completed' : 'Racing...', - }, - }); - } else if (mode === 'pathfinding') { - const step = simState.current.pathSteps[curr]; - if (step) setPathfindingStats(step.stats); - } else if (mode === 'dp') { - const step = simState.current.dpSteps[curr]; - if (step) { - setDPStats({ - cell: `${step.currentRow},${step.currentCol}`, - optimalVal: step.currentVal, - status: curr >= max - 1 ? 'Optimal Substructure Solved 🏆' : 'Memoizing Subproblems...', - }); - } - } else if (mode === 'trees') { - const step = simState.current.treeSteps[curr]; - if (step) { - setTreeStats({ - balance: curr >= max - 1 ? 'Balanced (AVL Factor 0)' : 'Rebalancing Tree', - rotations: curr >= max - 1 ? 1 : 0, - status: step.statusText, - }); - } - } else if (mode === 'searching') { - const step = simState.current.searchSteps[curr]; - if (step) { - setSearchStats({ - low: step.low, - mid: step.mid, - high: step.high, - step: step.stepCount, - status: step.found ? `Target ${step.target} Found in ${step.stepCount} steps! 🎯` : 'Halving Search Space...', - }); - } - } - } else { - // Loop simulation after short pause - setTimeout(() => { - resetSimulation(); - }, 2200); - } - }, intervalTime); - - return () => { - if (simState.current.timer) clearInterval(simState.current.timer); - }; - }, [isPlaying, isVisible, speedMultiplier, mode, reducedMotion, renderCanvas, resetSimulation]); - return (
- {/* Top Header with Mode Tabs */} -
-
- - - - - - - - - +
+
+
+ LIVE HARDWARE ACCELERATED SIMULATOR
- - {/* Action Controls */}
- - -
- {/* Canvas Element Wrapper */}
- {/* Dynamic Telemetry Footer */}
- {mode === 'sorting' && ( - <> -
- - {sortingStats.lane1.name}: - {sortingStats.lane1.comps} comps - - {sortingStats.lane1.status} - -
- -
- - {sortingStats.lane2.name}: - {sortingStats.lane2.comps} comps - {sortingStats.lane2.status} -
- - )} - - {mode === 'pathfinding' && ( -
- - A* Wavefront: - {pathfindingStats.visited} nodes evaluated - Path: {pathfindingStats.pathLength} steps - {pathfindingStats.status} -
- )} - - {mode === 'dp' && ( -
- - Knapsack Table: - Cell: [{dpStats.cell}] - Max Value: ${dpStats.optimalVal} - {dpStats.status} -
- )} - - {mode === 'trees' && ( -
- - AVL Tree: - {treeStats.balance} - {treeStats.status} -
- )} +
+ + {lane1Algo}: + {lane1Stats.comparisons} comps + + {lane1Stats.status} + +
- {mode === 'searching' && ( -
- - Binary Search: - Step {searchStats.step} (Log₂ N) - {searchStats.status} -
- )} +
+ + {lane2Algo}: + {lane2Stats.comparisons} comps + {lane2Stats.status} +
); diff --git a/frontend/src/data/quizQuestions.ts b/frontend/src/data/quizQuestions.ts deleted file mode 100644 index c0e3cd5..0000000 --- a/frontend/src/data/quizQuestions.ts +++ /dev/null @@ -1,270 +0,0 @@ -export interface QuizQuestion { - id: string; - category: 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees' | 'complexity'; - difficulty: 'Easy' | 'Medium' | 'Hard'; - title: string; - scenario: string; - codeSnippet?: string; - options: { - id: string; - text: string; - isCorrect: boolean; - explanation: string; - }[]; - detailedConcept: string; - leetcodeReference?: string; - sampleDataset?: number[]; - recommendedAlgorithm?: string; -} - -export const QUIZ_QUESTIONS: QuizQuestion[] = [ - { - id: 'sort-01', - category: 'sorting', - difficulty: 'Medium', - title: 'Adversarial Pivot Attack on QuickSort', - scenario: 'You are implementing a search engine ranking pipeline. An adversary discovers you use naive Lomuto partitioning with the last element as the pivot. If they send an already sorted array of size N = 100,000, what happens?', - codeSnippet: `// Naive Lomuto Partition -int pivot = arr[high]; -int i = (low - 1); -for (int j = low; j <= high - 1; j++) { - if (arr[j] < pivot) { - i++; - swap(arr[i], arr[j]); - } -} -swap(arr[i + 1], arr[high]);`, - options: [ - { - id: 'a', - text: 'The recursion tree degrades to O(N^2) depth with ~5 billion comparisons and stack overflow.', - isCorrect: true, - explanation: 'Correct! When the array is already sorted, choosing the last element yields maximally unbalanced partitions of sizes (N-1) and 0 at every step, creating O(N^2) time complexity and O(N) call stack recursion depth.', - }, - { - id: 'b', - text: 'QuickSort finishes in linear O(N) time because no elements need to be swapped.', - isCorrect: false, - explanation: 'Incorrect. While minimal swaps occur, every single pair comparison is still executed across all N recursive levels, causing O(N^2) total comparisons.', - }, - { - id: 'c', - text: 'QuickSort automatically switches to HeapSort in O(N log N) time.', - isCorrect: false, - explanation: 'Incorrect. That is the behavior of IntroSort (used in C++ std::sort), not pure Lomuto QuickSort.', - }, - { - id: 'd', - text: 'The time complexity remains O(N log N) but auxiliary space becomes O(N).', - isCorrect: false, - explanation: 'Incorrect. The time complexity degrades to quadratic O(N^2).', - }, - ], - detailedConcept: 'Standard QuickSort with naive pivot selection degrades to O(N^2) on sorted or reverse-sorted data. Production engines prevent this using Median-of-Three, randomized pivots, or TimSort.', - leetcodeReference: 'LeetCode #912: Sort an Array', - sampleDataset: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - recommendedAlgorithm: 'Quick Sort', - }, - { - id: 'sort-02', - category: 'sorting', - difficulty: 'Easy', - title: 'Sorting Stability in Multi-Column Records', - scenario: 'You are sorting customer transactions first by Date (secondary), and then by Customer Name (primary). Which sorting algorithm guarantees that transactions for the same customer remain in chronological order?', - options: [ - { - id: 'a', - text: 'Merge Sort or Tim Sort (Stable)', - isCorrect: true, - explanation: 'Correct! Stable sorting algorithms preserve the relative order of equal keys. When sorting by Customer Name with MergeSort/TimSort, identical customer names preserve their prior date ordering.', - }, - { - id: 'b', - text: 'Heap Sort (In-place)', - isCorrect: false, - explanation: 'HeapSort is not stable because building and sifting the heap swaps elements across distant array positions, destroying prior relative ordering.', - }, - { - id: 'c', - text: 'Quick Sort (Lomuto)', - isCorrect: false, - explanation: 'Standard in-place QuickSort is unstable due to long-range partition swaps.', - }, - { - id: 'd', - text: 'Selection Sort', - isCorrect: false, - explanation: 'Selection Sort is unstable (e.g. [4a, 4b, 1] swaps 4a with 1, placing 4a after 4b).', - }, - ], - detailedConcept: 'A sorting algorithm is stable if two objects with equal keys appear in the same order in sorted output as they appear in the input array. Merge Sort, Insertion Sort, and Tim Sort are stable; QuickSort and HeapSort are unstable.', - leetcodeReference: 'LeetCode #179: Largest Number', - recommendedAlgorithm: 'Merge Sort', - }, - { - id: 'search-01', - category: 'searching', - difficulty: 'Medium', - title: 'Integer Overflow Bug in Binary Search Midpoint', - scenario: 'In 2006, Joshua Bloch revealed that standard Java library Binary Search contained an integer overflow bug active since JDK 1.1. Why does `int mid = (low + high) / 2;` fail on massive arrays?', - codeSnippet: `// Faulty Midpoint Calculation -int low = 0; -int high = arr.length - 1; -while (low <= high) { - int mid = (low + high) / 2; // <-- Bug occurs here - if (arr[mid] == target) return mid; - // ... -}`, - options: [ - { - id: 'a', - text: 'When low + high exceeds Integer.MAX_VALUE (2^31 - 1), the sum overflows into negative numbers, throwing ArrayIndexOutOfBoundsException.', - isCorrect: true, - explanation: 'Correct! In 32-bit signed integers, if low + high > 2,147,483,647, the sum overflows to a negative integer, causing arr[mid] to crash with a negative array index.', - }, - { - id: 'b', - text: 'Dividing by 2 truncates floating points and causes infinite recursion.', - isCorrect: false, - explanation: 'Incorrect. Integer division truncates towards zero safely; the bug is purely arithmetic 32-bit overflow.', - }, - { - id: 'c', - text: 'Bitwise division cannot handle odd array sizes.', - isCorrect: false, - explanation: 'Incorrect. Bit shifts and division handle odd integers properly.', - }, - { - id: 'd', - text: 'The time complexity degrades from O(log N) to O(N).', - isCorrect: false, - explanation: 'Incorrect. The bug causes a runtime crash, not complexity degradation.', - }, - ], - detailedConcept: 'The safe midpoint calculation in all programming languages is `int mid = low + (high - low) / 2;` or unsigned right shift `int mid = (low + high) >>> 1;`.', - leetcodeReference: 'LeetCode #704: Binary Search', - sampleDataset: [2, 5, 8, 12, 16, 23, 38, 56, 72, 91], - recommendedAlgorithm: 'Binary Search', - }, - { - id: 'path-01', - category: 'pathfinding', - difficulty: 'Hard', - title: 'Admissibility & Consistency of Heuristics in A*', - scenario: 'You are designing an A* pathfinding bot on a 2D grid where diagonal movement is disallowed (cardinal 4-direction only). If you use Euclidean Distance `sqrt(dx^2 + dy^2)` instead of Manhattan Distance `|dx| + |dy|`, is the path guaranteed to be optimal?', - options: [ - { - id: 'a', - text: 'Yes, because Euclidean distance is strictly less than or equal to true grid distance, satisfying admissibility h(n) <= h*(n).', - isCorrect: true, - explanation: 'Correct! Euclidean distance is admissible because a straight line is the shortest possible distance between two points, so it never overestimates the true cardinal grid distance. However, Manhattan distance is more informed (closer to h*) and explores fewer nodes.', - }, - { - id: 'b', - text: 'No, because Euclidean distance overestimates grid distance on diagonals.', - isCorrect: false, - explanation: 'Incorrect. Euclidean distance is always <= Manhattan distance (e.g. sqrt(1+1)=1.414 <= 1+1=2), so it never overestimates.', - }, - { - id: 'c', - text: 'No, because A* requires Dijkstra weights on all non-uniform grids.', - isCorrect: false, - explanation: 'Incorrect. A* works on any non-negative edge weight graph.', - }, - { - id: 'd', - text: 'Yes, and Euclidean distance will explore fewer nodes than Manhattan distance.', - isCorrect: false, - explanation: 'Incorrect. Being less informed (smaller heuristic value), Euclidean distance behaves closer to Dijkstra and explores more nodes than Manhattan.', - }, - ], - detailedConcept: 'An A* heuristic h(n) is admissible if it never overestimates the actual cost to reach the goal. A heuristic is consistent (monotone) if h(n) <= c(n, p) + h(p). Admissible heuristics guarantee finding the shortest path.', - leetcodeReference: 'LeetCode #1091: Shortest Path in Binary Matrix', - recommendedAlgorithm: 'A* Search', - }, - { - id: 'tree-01', - category: 'trees', - difficulty: 'Medium', - title: 'AVL Tree Self-Balancing Rotation Identification', - scenario: 'You have a balanced AVL Tree. You insert the key 10, creating a Left-Right (LR) imbalance where node 30 has a balance factor of +2, and its left child 20 has a balance factor of -1. What sequence of rotations restores balance?', - codeSnippet: ` 30 (+2) - / - 20 (-1) - \\ - 25 (Newly inserted)`, - options: [ - { - id: 'a', - text: 'Left Rotation on child (20), followed by Right Rotation on parent (30).', - isCorrect: true, - explanation: 'Correct! An LR imbalance is resolved with a double rotation: first a Left Rotation on the left child (20) to transform it into an LL (Left-Left) chain, followed by a Right Rotation on the root (30) bringing 25 to the top.', - }, - { - id: 'b', - text: 'Single Right Rotation on node (30).', - isCorrect: false, - explanation: 'A single right rotation would fail to balance an LR zigzag.', - }, - { - id: 'c', - text: 'Right Rotation on node (20), followed by Left Rotation on node (30).', - isCorrect: false, - explanation: 'Incorrect. That resolves an RL (Right-Left) imbalance, not an LR.', - }, - { - id: 'd', - text: 'Double Right Rotation on node (30).', - isCorrect: false, - explanation: 'Incorrect. Double right rotation does not exist in AVL mechanics.', - }, - ], - detailedConcept: 'AVL trees enforce the balance factor property |h_left - h_right| <= 1 for all nodes. Imbalances are classified into LL (Single Right), RR (Single Left), LR (Left then Right), and RL (Right then Left).', - leetcodeReference: 'LeetCode #110: Balanced Binary Tree', - recommendedAlgorithm: 'AVL Tree', - }, - { - id: 'dp-01', - category: 'dp', - difficulty: 'Hard', - title: 'Space Optimization in 0/1 Knapsack Problem', - scenario: 'The standard 2D DP table for 0/1 Knapsack uses O(N * W) space. How can we optimize this to O(W) 1D array space while preventing an item from being chosen more than once?', - codeSnippet: `// 1D DP Array Space Optimization -int[] dp = new int[W + 1]; -for (int i = 0; i < N; i++) { - // How should the inner capacity loop iterate? - for (int w = ???; w >= weight[i]; w--) { - dp[w] = Math.max(dp[w], dp[w - weight[i]] + value[i]); - } -}`, - options: [ - { - id: 'a', - text: 'Iterate capacity w backwards from W down to weight[i], ensuring dp[w - weight[i]] references values from the previous item iteration.', - isCorrect: true, - explanation: 'Correct! By iterating capacity backwards from W down to weight[i], dp[w - weight[i]] has not yet been overwritten by the current item i, ensuring each item is used at most once (0/1 constraint). If iterated forward, it solves Unbounded Knapsack.', - }, - { - id: 'b', - text: 'Iterate capacity w forward from weight[i] up to W.', - isCorrect: false, - explanation: 'Incorrect! Forward iteration allows items to be reused multiple times, turning it into Unbounded Knapsack.', - }, - { - id: 'c', - text: 'Use two pointers from 0 and W meeting at W/2.', - isCorrect: false, - explanation: 'Incorrect. Two pointers do not preserve correct state transitions across all capacities.', - }, - { - id: 'd', - text: 'Space optimization is mathematically impossible for 0/1 Knapsack.', - isCorrect: false, - explanation: 'Incorrect. 1D rolling array optimization is the standard production solution.', - }, - ], - detailedConcept: 'In 0/1 Knapsack, iterating capacity backwards guarantees that each item is considered at most once. Forward iteration solves the Unbounded Knapsack problem (like Coin Change).', - leetcodeReference: 'LeetCode #416: Partition Equal Subset Sum', - recommendedAlgorithm: 'Knapsack DP', - }, -]; diff --git a/frontend/src/models/types.ts b/frontend/src/models/types.ts index 2c3356e..513d599 100644 --- a/frontend/src/models/types.ts +++ b/frontend/src/models/types.ts @@ -67,7 +67,6 @@ export type RaceResponse = { weights?: number[][] | null; lanes: RaceLaneResponse[]; winner: string | null; - totalTimeMs?: number; }; export type TreeNodeDto = { diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index b64ebcb..881f52c 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef, lazy, Suspense } from 'react'; +import { useState, lazy, Suspense } from 'react'; import { BarChart3, Binary, @@ -18,26 +18,10 @@ import { X, Sun, Moon, - Keyboard, - Github, - Compass, - Check, - RotateCw, - Play, - Flame, - Shuffle, - Terminal, - FileCode2, - Workflow, - BarChart2, - Star, } from 'lucide-react'; -import { useAudio } from '../context/AudioContext'; -import { AlgoRaceLogo } from '../components/AlgoRaceLogo'; const HeroMiniCanvas = lazy(() => import('../components/HeroMiniCanvas').then(m => ({ default: m.HeroMiniCanvas }))); const AlgorithmMatrix = lazy(() => import('../components/AlgorithmMatrix').then(m => ({ default: m.AlgorithmMatrix }))); -const CodePlayground = lazy(() => import('../components/CodePlayground').then(m => ({ default: m.CodePlayground }))); interface Props { onNavigate: (page: 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees' | 'history' | 'settings') => void; @@ -45,168 +29,8 @@ interface Props { setDarkMode?: (val: boolean) => void; } -// Real-Time Dynamic Shortest Path Solver for Mini Grid (BFS/A*) -function solveMiniGridPath(wallsGrid: number[][]): number[][] { - const nextGrid = wallsGrid.map(r => [...r]); - const rows = nextGrid.length; - const cols = nextGrid[0].length; - const start: [number, number] = [2, 0]; - const target: [number, number] = [2, 8]; - - // Clear previous path - for (let r = 0; r < rows; r++) { - for (let c = 0; c < cols; c++) { - if (nextGrid[r][c] === 4) nextGrid[r][c] = 0; - } - } - - // BFS Queue - const queue: [number, number][] = [start]; - const visited = new Set([`${start[0]},${start[1]}`]); - const parent = new Map(); - - const directions = [ - [0, 1], - [1, 0], - [0, -1], - [-1, 0], - ]; - - let found = false; - while (queue.length > 0) { - const [cr, cc] = queue.shift()!; - if (cr === target[0] && cc === target[1]) { - found = true; - break; - } - - for (const [dr, dc] of directions) { - const nr = cr + dr; - const nc = cc + dc; - const key = `${nr},${nc}`; - if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && nextGrid[nr][nc] !== 1 && !visited.has(key)) { - visited.add(key); - parent.set(key, [cr, cc]); - queue.push([nr, nc]); - } - } - } - - // If path exists, trace and mark cells as 4 (Path) - if (found) { - let curr = target; - while (curr[0] !== start[0] || curr[1] !== start[1]) { - const p = parent.get(`${curr[0]},${curr[1]}`); - if (!p) break; - if ((p[0] !== start[0] || p[1] !== start[1]) && (p[0] !== target[0] || p[1] !== target[1])) { - nextGrid[p[0]][p[1]] = 4; - } - curr = p; - } - } - - nextGrid[start[0]][start[1]] = 5; // Start marker - nextGrid[target[0]][target[1]] = 6; // Target marker - return nextGrid; -} - export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) { const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - const { play, playToneForValue } = useAudio(); - const bentoGridRef = useRef(null); - - // ---------------------------------------------------- - // Interactive Bento Micro-Widgets State - // ---------------------------------------------------- - - // 1. Sorting Partition Array Bars State - const [sortingBars, setSortingBars] = useState([35, 75, 45, 90, 60, 25, 85, 50, 95, 30, 70, 40]); - const [pivotBarIdx, setPivotBarIdx] = useState(3); - const [swapBarIdx, setSwapBarIdx] = useState(7); - - const handleShuffleSortingBars = (e: React.MouseEvent) => { - e.stopPropagation(); - const newArr = Array.from({ length: 12 }, () => Math.floor(Math.random() * 75) + 25); - setSortingBars(newArr); - setPivotBarIdx(Math.floor(Math.random() * 12)); - setSwapBarIdx(Math.floor(Math.random() * 12)); - play('swap'); - }; - - // 2. Web Audio Pentatonic Sound Pad State - const [activeNoteIdx, setActiveNoteIdx] = useState(null); - const pentatonicNotes = [ - { label: 'C4', val: 60, hz: '261 Hz' }, - { label: 'D4', val: 62, hz: '293 Hz' }, - { label: 'E4', val: 64, hz: '329 Hz' }, - { label: 'G4', val: 67, hz: '392 Hz' }, - { label: 'A4', val: 69, hz: '440 Hz' }, - ]; - - const handlePlayTone = (noteVal: number, idx: number, e: React.MouseEvent) => { - e.stopPropagation(); - setActiveNoteIdx(idx); - if (playToneForValue) { - playToneForValue(noteVal, 50, 80, idx % 2 === 0 ? 'compare' : 'swap'); - } else { - play('click'); - } - setTimeout(() => setActiveNoteIdx(null), 250); - }; - - // 3. Search Space Halver State - const [searchStep, setSearchStep] = useState(1); - - // 4. Mini Pathfinding Grid State with Real-Time Path Solver - const [miniGrid, setMiniGrid] = useState(() => { - const initialWalls = Array.from({ length: 5 }, () => Array(9).fill(0)); - initialWalls[1][4] = 1; - initialWalls[2][4] = 1; - initialWalls[3][4] = 1; - return solveMiniGridPath(initialWalls); - }); - - const toggleMiniGridCell = (r: number, c: number, e: React.MouseEvent) => { - e.stopPropagation(); - if ((r === 2 && c === 0) || (r === 2 && c === 8)) return; - setMiniGrid(prev => { - const next = prev.map(row => [...row]); - next[r][c] = next[r][c] === 1 ? 0 : 1; - return solveMiniGridPath(next); - }); - play('click'); - }; - - // 5. AVL Tree Rotator State - const [isTreeRotated, setIsTreeRotated] = useState(false); - - // 6. DP Memoization Grid State - const [activeDPCell, setActiveDPCell] = useState<[number, number]>([2, 3]); - - // 7. Debugger Timeline Scrubber State - const [debuggerStep, setDebuggerStep] = useState(3); - const pseudocodeLines = [ - 'pivot = partition(arr, low, high)', - 'quickSort(arr, low, pivot - 1)', - 'quickSort(arr, pivot + 1, high) // Active Branch', - 'if (low >= high) return;', - ]; - - // ---------------------------------------------------- - // Cursor Spotlight Shader Effect on Bento Cards - // ---------------------------------------------------- - const handleMouseMove = (e: React.MouseEvent) => { - if (!bentoGridRef.current) return; - const cards = bentoGridRef.current.getElementsByClassName('bento-card'); - for (let i = 0; i < cards.length; i++) { - const card = cards[i] as HTMLElement; - const rect = card.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - card.style.setProperty('--mouse-x', `${x}px`); - card.style.setProperty('--mouse-y', `${y}px`); - } - }; const scrollToSection = (id: string) => { setMobileMenuOpen(false); @@ -216,40 +40,6 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) { } }; - // Global Keyboard Navigation Listener (Keys 1-5, /, Escape) - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - const activeTag = document.activeElement?.tagName.toLowerCase(); - if (activeTag === 'input' || activeTag === 'textarea' || activeTag === 'select') { - return; - } - - if (e.key === 'Escape') { - setMobileMenuOpen(false); - } else if (e.key === '1') { - onNavigate('sorting'); - } else if (e.key === '2') { - onNavigate('searching'); - } else if (e.key === '3') { - onNavigate('pathfinding'); - } else if (e.key === '4') { - onNavigate('dp'); - } else if (e.key === '5') { - onNavigate('trees'); - } else if (e.key === '/') { - e.preventDefault(); - scrollToSection('matrix'); - const searchInput = document.querySelector('.matrix-search-input'); - if (searchInput) { - setTimeout(() => searchInput.focus(), 300); - } - } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [onNavigate]); - return (
{/* Ambient background glow layers */} @@ -259,8 +49,14 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) { {/* Top Header / Sticky Nav */}
-
window.scrollTo({ top: 0, behavior: 'smooth' })} style={{ cursor: 'pointer' }}> - +
+
+ +
+
+ AlgoRace + v2.0 +
-
- {/* Live Algorithm Race Telemetry Ticker */} -
-
-
- 🏆 QuickSort: 0.14ms (1st place) -
-
- MergeSort: 0.22ms • O(N log N) -
-
- 🗺️ A* Search: 42 nodes • 100% optimal -
-
- 🧩 Knapsack DP: $280 optimal value -
-
- 🌳 AVL Tree: Balance Factor 0 • 0 rotations -
-
- 🔍 Binary Search: 4 steps • Target: 72 -
-
- {/* Duplicate set for seamless continuous marquee, hidden from screen readers */} - -
- {/* Hero Banner Section */}
- HIGH-PERFORMANCE ALGORITHM BENCHMARKING SUITE + ALGORITHM VISUALIZATION & BENCHMARKING

@@ -401,63 +142,63 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {

- Compare sorting, graph pathfinding, dynamic programming, tree balancing, and search suites side-by-side with live sub-millisecond telemetry and interactive 60 FPS canvas debugging. + Compare sorting, searching, and pathfinding algorithms side by side — with live performance metrics, step-by-step debugging, and interactive canvas visualizations.

- {/* Streamlined 2-CTA Action Cluster */} -
+
-
- {/* Keyboard Shortcuts Navigation Bar */} -
-
- - Quick Hotkeys: -
-
- - - - - - -
+ + + + + + +
{/* Statistics Bar */}
- 20+ + 12+ Supported Algorithms
@@ -475,117 +216,30 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {
- {/* Live Multi-Modal Hardware Mini Canvas Teaser */} + {/* Live Hardware Mini Canvas Teaser */}
- -
-
-
-
-
-
-
-
-
- }> + }>
- {/* 3-Step Interactive Workflow Pipeline */} -
-
-
- - HOW ALGORACE WORKS -
-

Benchmark Algorithms in 3 Simple Steps

-

- Configure multi-lane race tracks, inject custom dataset distributions, and analyze real-time execution frames. -

-
- -
- {/* Step 1 */} -
-
01
-
- -
-

Select Contenders

-

- Choose 2 to 4 competing algorithms across Sorting, Graph Pathfinding, Dynamic Programming, or Tree Balancing arenas. -

-
- QuickSort vs MergeSort - A* vs Dijkstra -
-
- - {/* Step 2 */} -
-
02
-
- -
-

Configure Distribution & Seeds

-

- Select uniform random seeds, nearly sorted arrays, reverse permutations, or custom 2D maze barrier weights. -

-
- Random Seeds - Weighted Mazes -
-
- - {/* Step 3 */} -
-
03
-
- -
-

Race, Benchmark & Debug

-

- Execute at 60 FPS with live sub-ms telemetry, synthesized Web Audio chimes, and bidirectional timeline scrubbing. -

-
- 60 FPS Canvas - Frame Scrubbing -
-
-
-
- - {/* Feature Arenas Bento Grid 2.0 (with Accessible Interactive Micro-Widgets) */} + {/* Feature Arenas Bento Grid */}
- INTERACTIVE ARENAS & MICRO-TOOLS + INTERACTIVE ARENAS

Built for Precision & Deep Insight

- Engineered for computer scientists, software engineers, and students to dissect algorithmic behavior side-by-side. + Engineered for computer scientists, developers, and students to compare algorithmic behavior side-by-side.

-
- {/* Card 1: Multi-Lane Sorting */} -
onNavigate('sorting')} - tabIndex={0} - role="button" - aria-label="Launch Sorting Arena to race QuickSort, MergeSort, and HeapSort" - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onNavigate('sorting'); - } - }} - > +
+ {/* Row 1: Card 1 (Sorting Arena) + Card 2 (Search Arena) */} +
onNavigate('sorting')}>
@@ -595,37 +249,14 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {

Multi-Lane Array Sorting Race

- Compare QuickSort, MergeSort, HeapSort, InsertionSort, RadixSort, and ShellSort on uniform dataset seeds. + Compare QuickSort, MergeSort, HeapSort, InsertionSort, and SelectionSort on uniform dataset seeds. Features precise glowing visual indicators for comparisons, swaps, pivots, heap bounds, and sorted ranges.

- {/* Interactive Micro-Widget: Partition Array Bars */} -
e.stopPropagation()}> -
- INTERACTIVE ARRAY PARTITION - -
-
- {sortingBars.map((val, idx) => ( -
{ - setPivotBarIdx(idx); - play('compare'); - }} - /> - ))} -
+
+ Step Debugger & Seek Bar + Random / Nearly Sorted / Reversed + Inline Pseudocode Inspector
@@ -634,87 +265,25 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {
- {/* Card 2: Logarithmic Search Space Halver */} -
onNavigate('searching')} - tabIndex={0} - role="button" - aria-label="Launch Search Arena to test Binary, Interpolation, and Ternary Search" - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onNavigate('searching'); - } - }} - > +
onNavigate('searching')}>
SEARCH ARENA
-

Logarithmic Search Space Halver

+

Binary, Jump & Linear Search

- Observe logarithmic search space elimination in Binary, Interpolation, and Ternary Search with darkened inactive ranges. + Observe logarithmic search space elimination in Binary Search with darkened inactive ranges and index targeting.

- - {/* Interactive Micro-Widget: Search Space Halver */} -
e.stopPropagation()}> -
- O(log N) SEARCH RANGE - Step {searchStep} / 4 -
-
{ - setSearchStep(s => (s % 4) + 1); - play('compare'); - }} - > -
-
-
- -
-
-
Launch Search Arena
- {/* Card 3: 2D Pathfinding */} -
onNavigate('pathfinding')} - tabIndex={0} - role="button" - aria-label="Launch Pathfinding Arena with A*, Dijkstra, BFS, and DFS" - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onNavigate('pathfinding'); - } - }} - > + {/* Row 2: Card 3 (Pathfinding Arena) + Card 4 (Web Audio) */} +
onNavigate('pathfinding')}>
@@ -722,42 +291,16 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {
PATHFINDING ARENA
-

2D Grid Pathfinding & Interactive Maze Editor

+

2D Grid Pathfinding & Wall Editor

- Visualize A*, Dijkstra, BFS, DFS, and Bellman-Ford graph traversals on 2D grid maps. - Click grid cells directly inside this preview to draw walls—the shortest path will dynamically re-route around your obstacles! + Visualize A*, Dijkstra, BFS, and DFS graph traversals on custom 2D grid maps. + Click and drag directly on the canvas to draw custom wall barriers with live path recalculation.

- {/* Interactive Micro-Widget: Interactive Mini Grid with Real-Time Path Solver */} -
e.stopPropagation()}> -
- LIVE DYNAMIC A* MINI GRID - Click cells to toggle barrier walls -
-
- {miniGrid.map((row, r) => ( -
- {row.map((val, c) => ( -
toggleMiniGridCell(r, c, e)} - title={val === 5 ? 'Start Node' : val === 6 ? 'Target Node' : val === 1 ? 'Wall Obstacle' : val === 4 ? 'Shortest Path' : 'Empty Cell'} - /> - ))} -
- ))} -
+
+ Interactive Drag Walls + Recursive Division Maze Gen + Shortest Path Highlighting
@@ -766,208 +309,25 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {
- {/* Card 4: Playable Web Audio Synthesizer Pad */} -
onNavigate('settings')} - tabIndex={0} - role="button" - aria-label="Open Sound Settings and audio synthesizer" - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onNavigate('settings'); - } - }} - > +
onNavigate('settings')}>
SOUND ENGINE
-

Web Audio Synthesizer Pad

+

Synthesized Web Audio Feedback

- Custom synthesized acoustic chimes mapped to array element values for auditory feedback. - Click keys below to test the sound engine live! + Custom synthesized acoustic chimes providing subtle auditory feedback for array swaps, comparisons, and victory fanfares.

- - {/* Interactive Micro-Widget: Pentatonic Audio Pad */} -
e.stopPropagation()}> -
- PENTATONIC TONE PAD -
- - - - -
-
-
- {pentatonicNotes.map((note, idx) => ( - - ))} -
-
-
Audio Settings
- {/* Card 5: Dynamic Programming */} -
onNavigate('dp')} - tabIndex={0} - role="button" - aria-label="Launch Dynamic Programming Arena with 0/1 Knapsack, LCS, and Edit Distance" - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onNavigate('dp'); - } - }} - > -
-
-
- -
- DYNAMIC PROGRAMMING -
-

2D Memoization Tables & Subproblem Graphs

-

- Step through 0/1 Knapsack, Longest Common Subsequence (LCS), and Edit Distance matrices. - Follow recurrence transitions cell-by-cell with optimal subproblem backtracking. -

- - {/* Interactive Micro-Widget: DP Grid Dependencies */} -
e.stopPropagation()}> -
- KNAPSACK RECURRENCE MATRIX - Click cell to inspect subproblems -
-
- {[ - [0, 0, 0, 0, 0], - [0, 3, 3, 3, 3], - [0, 3, 4, 7, 7], - [0, 3, 5, 8, 9], - ].map((row, r) => ( -
- {row.map((val, c) => { - const isSelected = activeDPCell[0] === r && activeDPCell[1] === c; - const isDep = isSelected || (r === activeDPCell[0] - 1 && (c === activeDPCell[1] || c === activeDPCell[1] - 2)); - return ( -
{ - setActiveDPCell([r, c]); - play('click'); - }} - > - {val} -
- ); - })} -
- ))} -
-
- dp[{activeDPCell[0]}][{activeDPCell[1]}] = max(dp[{activeDPCell[0]-1}][{activeDPCell[1]}], dp[{activeDPCell[0]-1}][{Math.max(0, activeDPCell[1]-2)}] + v) -
-
- -
- Launch DP Arena - -
-
- - {/* Card 6: Interactive AVL Tree Rotator */} -
onNavigate('trees')} - tabIndex={0} - role="button" - aria-label="Launch Tree Structures Arena with BST, AVL, and Red-Black Trees" - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onNavigate('trees'); - } - }} - > -
-
- -
- TREE STRUCTURES -
-

BST, AVL & Red-Black Balancing

-

- Visualize self-balancing tree rotations, balance factor evaluations, and logarithmic depth maintenance. -

- - {/* Interactive Micro-Widget: AVL Tree Rotator */} -
e.stopPropagation()}> -
- AVL ROTATION WIDGET - -
-
-
-
{isTreeRotated ? '30' : '20'}
-
-
{isTreeRotated ? '20' : '10'}
-
{isTreeRotated ? '40' : '30'}
-
-
-
-
- -
- Launch Tree Arena - -
-
- - {/* Card 7: Real-Time Telemetry & Benchmark Sparkline */} -
onNavigate('history')} - tabIndex={0} - role="button" - aria-label="View Benchmarks and performance history" - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onNavigate('history'); - } - }} - > + {/* Row 3: Card 5 (Performance Benchmarks) + Card 6 (Step Debugger) */} +
onNavigate('history')}>
@@ -978,47 +338,18 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {

Live comparative execution time graphs ($ms$), total operations/comparisons, and swap count telemetry for scientific benchmarking.

- - {/* Interactive Micro-Widget: Telemetry Sparkline */} -
e.stopPropagation()}> -
- SAMPLE RUN TELEMETRY - ⚡ 0.14ms Sample -
-
-
-
-
-
-
-
-
-
-
-
-
+
+ Live Execution Charts ($ms$) + Swap & Comparison Counters + Historical Race Logs
-
View Benchmarks
- {/* Card 8: Step Debugger Timeline Scrubber */} -
onNavigate('sorting')} - tabIndex={0} - role="button" - aria-label="Try algorithm step debugger and timeline scrubber" - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onNavigate('sorting'); - } - }} - > +
onNavigate('sorting')}>
@@ -1029,38 +360,6 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {

Follow step-by-step algorithm execution with frame scrubbing seek bars and expandable pseudocode cards.

- - {/* Interactive Micro-Widget: Debugger Timeline */} -
e.stopPropagation()}> -
- EXECUTION SCRUBBER - Frame {debuggerStep} / 4 -
-
- {pseudocodeLines.map((line, idx) => ( -
- {idx + 1} - {line} -
- ))} -
- { - setDebuggerStep(Number(e.target.value)); - play('click'); - }} - className="micro-slider" - aria-label="Algorithm execution step timeline scrubber" - /> -
-
Try Debugger @@ -1069,108 +368,6 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {
- {/* Synchronized Multi-Language Code Playground */} -
-
-
- - DEVELOPER PLAYGROUND -
-

Synchronized Multi-Language Source Code

-

- Inspect canonical implementations across TypeScript, Python, Java, and C++ with real-time execution step highlighting. -

-
- -
- }> - - -
-
- - {/* Competitive Value Proposition: AlgoRace vs Traditional Visualizers */} -
-
-
- - VALUE PROPOSITION -
-

The Modern Standard in Algorithm Visualization

-

- Why engineers and computer scientists choose AlgoRace over static textbooks and legacy applets. -

-
- -
- {/* Legacy Column */} -
-
-
Legacy Tools & Textbooks
-

Traditional Visualizers

-

Static slideshows, outdated applets, and single-algorithm viewers.

-
-
    -
  • - - Single algorithm execution in isolation -
  • -
  • - - No frame scrubbing or timeline seek -
  • -
  • - - Zero audio feedback or frequency mapping -
  • -
  • - - DOM-based rendering (No HTML5 Canvas) -
  • -
  • - - Dated academic user interface -
  • -
-
- - {/* AlgoRace 2.0 Column */} -
-
-
-
- - AlgoRace 2.0 Engine -
-

Modern Real-Time Arena

-

Multi-lane real-time algorithm racing with high-throughput simulation telemetry.

-
-
    -
  • - - Multi-Lane Racing with uniform seed preservation -
  • -
  • - - Bidirectional Timeline Scrubbing & step-by-step inspector -
  • -
  • - - Synthesized Web Audio API chimes and victory fanfares -
  • -
  • - - Hardware 60 FPS Canvas with 0ms UI blocking (Decoupled Loop) -
  • -
  • - - Obsidian Dark Mode & Okabe-Ito colorblind accessible themes -
  • -
-
-
-
- {/* Feature Details / Highlights Grid */}
@@ -1210,7 +407,7 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {

Dark & Light Obsidian Themes

- Seamlessly switch between Obsidian dark mode, high-contrast mode, and colorblind-friendly palettes (Okabe-Ito). + Seamlessly switch between Obsidian dark mode and high-contrast light mode tailored for long study and development sessions.

@@ -1226,7 +423,7 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {

Powered by Modern High-Performance Tech Stack

- AlgoRace decouples simulation step calculation and client-side hardware canvas rendering for zero UI blocking lag. + AlgoRace decouples simulation step calculation and client-side hardware canvas rendering for zero UI lag.

@@ -1254,250 +451,57 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {
- {/* Complexity Matrix Section & Big-O Curves */} + {/* Complexity Matrix Section */}
}>
- {/* Upgraded Bottom CTA Launchpad */} -
-
-
-
-
- - HIGH-THROUGHPUT ENGINE READY -
- -

- Ready to Benchmark Algorithms Live? -

- -

- Compare sorting mechanics, 2D weighted pathfinding, DP matrix memoization, and self-balancing tree rotations in real time with 60 FPS hardware acceleration. -

- - {/* Quick-Launch Arena Grid */} -
- - - - - - - - - -
- - {/* Action Cluster */} -
- - - -
- - {/* Feature Spec Strip */} -
-
- - Zero UI Thread Blocking -
-
-
- - Pentatonic Web Audio Synthesis -
-
-
- - Colorblind Accessible (Okabe-Ito) -
-
-
+ {/* Bottom CTA Banner */} +
+
+

Ready to Benchmark Computer Science Algorithms?

+

+ Jump into the race arenas now and experience live multi-lane algorithm visualization. +

+
- {/* Upgraded 4-Column SaaS Footer */} + {/* Footer */} diff --git a/frontend/src/services/workerSimulationService.ts b/frontend/src/services/workerSimulationService.ts index 4b3ba29..d189834 100644 --- a/frontend/src/services/workerSimulationService.ts +++ b/frontend/src/services/workerSimulationService.ts @@ -72,7 +72,6 @@ class WorkerSimulationService { stats: l.stats, })), winner: response.winner, - totalTimeMs: response.totalTimeMs, }; handler.resolve(raceResponse);