diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 2ffe52a..994f44f 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -11,7 +11,6 @@ import { SettingsPage } from './pages/SettingsPage';
import { SortingPage } from './pages/SortingPage';
import { DPPage } from './pages/DPPage';
import { TreesPage } from './pages/TreesPage';
-import { BattlePage } from './pages/BattlePage';
import type { CatalogResponse } from './models/types';
import { api } from './services/api';
import { AudioCtx } from './context/AudioContext';
@@ -20,7 +19,7 @@ import { useSound } from './hooks/useSound';
import { fallbackCatalog } from './data/fallbackCatalog';
-type Page = 'landing' | 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees' | 'history' | 'settings' | 'battle';
+type Page = 'landing' | 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees' | 'history' | 'settings';
const getPageFromHash = (): Page => {
const searchParams = new URLSearchParams(window.location.search);
@@ -182,7 +181,6 @@ export default function App() {
/>
- {active === 'battle' &&
}
{active === 'sorting' &&
}
{active === 'searching' &&
}
{active === 'pathfinding' &&
}
diff --git a/frontend/src/components/HeadToHeadBattle.tsx b/frontend/src/components/HeadToHeadBattle.tsx
deleted file mode 100644
index 7174972..0000000
--- a/frontend/src/components/HeadToHeadBattle.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { SortingCanvas } from './SortingCanvas';
-import type { RaceResponse, SimulationFrame } from '../models/types';
-import { useMemo } from 'react';
-
-interface HeadToHeadBattleProps {
- response: RaceResponse;
- frameA: SimulationFrame;
- frameB: SimulationFrame;
- algoA: string;
- algoB: string;
-}
-
-export function HeadToHeadBattle({ response, frameA, frameB, algoA, algoB }: HeadToHeadBattleProps) {
- // Calculate ops for current frame
- const opsA = (frameA?.comparisons ?? 0) + (frameA?.swaps ?? 0);
- const opsB = (frameB?.comparisons ?? 0) + (frameB?.swaps ?? 0);
-
- // Delta logic (Ops A - Ops B). Positive means A did more ops than B.
- const delta = opsA - opsB;
-
- // To render a simple differential bar chart, we can scale it to a max value.
- // We'll calculate the maximum possible delta from the entire race history to scale our bar.
- const maxAbsDelta = useMemo(() => {
- let max = 1;
- if (!response || response.lanes.length < 2) return max;
- const laneA = response.lanes[0];
- const laneB = response.lanes[1];
- const maxLen = Math.max(laneA.frames.length, laneB.frames.length);
-
- for (let i = 0; i < maxLen; i++) {
- const fa = laneA.frames[Math.min(i, laneA.frames.length - 1)];
- const fb = laneB.frames[Math.min(i, laneB.frames.length - 1)];
- const oa = (fa?.comparisons ?? 0) + (fa?.swaps ?? 0);
- const ob = (fb?.comparisons ?? 0) + (fb?.swaps ?? 0);
- const d = Math.abs(oa - ob);
- if (d > max) max = d;
- }
- return max;
- }, [response]);
-
- const percentage = Math.min(100, (Math.abs(delta) / maxAbsDelta) * 50); // Scale 0-50% for left/right
-
- return (
-
-
- {/* Side-by-side Visualizers */}
-
-
-
{algoA}
-
-
-
-
- Ops: {opsA}
-
-
-
-
-
{algoB}
-
-
-
-
- Ops: {opsB}
-
-
-
-
- {/* Differential Graph Panel */}
-
-
Live Operation Lead/Lag Delta
-
- (Algorithm A Ops - Algorithm B Ops)
-
-
-
- {/* Center line */}
-
-
- {/* The moving bar */}
- {delta !== 0 && (
-
0 ? 'var(--rose-500)' : 'var(--emerald-500)',
- left: delta > 0 ? '50%' : `calc(50% - ${percentage}%)`,
- width: `${percentage}%`,
- transition: 'all 0.1s linear',
- }}>
- )}
-
-
- {algoA} Advantage
- Delta: {Math.abs(delta)} ops {delta > 0 ? `(${algoB} leading)` : (delta < 0 ? `(${algoA} leading)` : '(Tied)')}
- 0 ? 'var(--rose-400)' : 'inherit' }}>{algoB} Advantage
-
-
-
-
- );
-}
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 3b884b9..0e432e3 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -1,11 +1,10 @@
import { BarChart3, Binary, GitBranch, History, Settings, ChevronLeft, ChevronRight, Zap, LayoutGrid, X, Sun, Moon, Layers, FolderTree } from 'lucide-react';
import { useAudio } from '../context/AudioContext';
-type Page = 'landing' | 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees' | 'history' | 'settings' | 'battle';
+type Page = 'landing' | 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees' | 'history' | 'settings';
const items = [
{ id: 'landing', label: 'Overview', icon: LayoutGrid },
- { id: 'battle', label: '1v1 Battle', icon: Zap },
{ id: 'sorting', label: 'Sorting Arena', icon: BarChart3 },
{ id: 'searching', label: 'Search Arena', icon: Binary },
{ id: 'pathfinding', label: 'Pathfinding Arena', icon: GitBranch },
diff --git a/frontend/src/pages/BattlePage.tsx b/frontend/src/pages/BattlePage.tsx
deleted file mode 100644
index c88d9a6..0000000
--- a/frontend/src/pages/BattlePage.tsx
+++ /dev/null
@@ -1,215 +0,0 @@
-import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
-import type { CatalogResponse, RaceResponse, SimulationFrame } from '../models/types';
-import { api } from '../services/api';
-import { useAudio } from '../context/AudioContext';
-import { Controls } from '../components/Controls';
-import { SelectField } from '../components/SelectField';
-import { Share2 } from 'lucide-react';
-import { usePlayback } from '../hooks/usePlayback';
-import { HeadToHeadBattle } from '../components/HeadToHeadBattle';
-
-export function BattlePage({ catalog }: { catalog: CatalogResponse }) {
- const { play } = useAudio();
- const [algorithms, setAlgorithms] = useState<[string, string]>(['Quick Sort', 'Bubble Sort']);
- const [size, setSize] = useState(50);
- const [datasetType, setDatasetType] = useState('Random');
- const [loading, setLoading] = useState(false);
- const [activeResponse, setActiveResponse] = useState
(null);
- const [speed, setSpeed] = useState(1);
- const [toastMessage, setToastMessage] = useState(null);
-
- const playback = usePlayback(activeResponse, speed);
- const hasStartedPlaybackRef = useRef(false);
- const winnerAnnouncedRef = useRef(false);
- const requestIdRef = useRef(0);
-
- const fetchSimulation = useCallback(
- async (
- newDataset: boolean,
- autoplay = false,
- customParams?: { algos?: [string, string]; dType?: string; sz?: number }
- ) => {
- const requestId = ++requestIdRef.current;
- setLoading(true);
- winnerAnnouncedRef.current = false;
- if (autoplay) {
- hasStartedPlaybackRef.current = true;
- } else {
- hasStartedPlaybackRef.current = false;
- }
-
- const useAlgos = customParams?.algos ?? algorithms;
- const useType = customParams?.dType ?? datasetType;
- const useSize = customParams?.sz ?? size;
-
- try {
- const data = await api.sorting({
- algorithms: useAlgos,
- datasetType: useType,
- size: useSize,
- newDataset,
- });
-
- if (requestId !== requestIdRef.current) return;
- setActiveResponse(data);
- const maxFrames = Math.max(...data.lanes.map((l) => l.frames.length), 0);
- playback.reset();
-
- if (autoplay) {
- playback.setPlaying(true);
- }
- } catch (error: any) {
- if (requestId === requestIdRef.current) {
- console.error("Battle simulation error:", error);
- }
- } finally {
- if (requestId === requestIdRef.current) setLoading(false);
- }
- },
- [algorithms, datasetType, size, playback]
- );
-
- useEffect(() => {
- if (catalog.sortingAlgorithms.length >= 2) {
- if (!algorithms[0] || !algorithms[1]) {
- setAlgorithms([catalog.sortingAlgorithms[0], catalog.sortingAlgorithms[1]]);
- }
- fetchSimulation(true, false, {
- algos: algorithms[0] && algorithms[1] ? algorithms : [catalog.sortingAlgorithms[0], catalog.sortingAlgorithms[1]] as [string, string]
- });
- }
- }, [catalog.sortingAlgorithms]);
-
- async function startRace() {
- play('start');
- if (!activeResponse || (playback.frameIndex >= playback.maxFrames - 1 && playback.maxFrames > 0)) {
- await fetchSimulation(true, true);
- } else {
- playback.setPlaying(true);
- hasStartedPlaybackRef.current = true;
- }
- }
-
- async function handleReset() {
- await fetchSimulation(true, false);
- }
-
- function handleAlgorithmChange(index: 0 | 1, nextAlgo: string) {
- const nextAlgos = [...algorithms] as [string, string];
- nextAlgos[index] = nextAlgo;
- setAlgorithms(nextAlgos);
- fetchSimulation(false, false, { algos: nextAlgos });
- }
-
- const activeFrames = useMemo(
- () =>
- activeResponse?.lanes.map((lane) => {
- if (!lane.frames || lane.frames.length === 0) return undefined;
- const safeIdx = Math.max(0, Math.min(playback.frameIndex, lane.frames.length - 1));
- return lane.frames[safeIdx];
- }),
- [activeResponse, playback.frameIndex]
- );
-
- const isCompleted = !!(activeResponse && playback.frameIndex === playback.maxFrames - 1 && playback.maxFrames > 0);
- const winnerLane = activeResponse?.lanes.find((l) => l.name === activeResponse.winner);
-
- let speedRatioStr = '';
- if (isCompleted && activeResponse && activeResponse.lanes.length === 2 && winnerLane) {
- const loserLane = activeResponse.lanes.find(l => l.name !== winnerLane.name);
- if (loserLane && winnerLane.stats.timeMs > 0) {
- const ratio = loserLane.stats.timeMs / winnerLane.stats.timeMs;
- speedRatioStr = `${ratio.toFixed(1)}x faster`;
- }
- }
-
- useEffect(() => {
- if (isCompleted && activeResponse && hasStartedPlaybackRef.current && !winnerAnnouncedRef.current) {
- winnerAnnouncedRef.current = true;
- hasStartedPlaybackRef.current = false;
-
- if (activeResponse.winner) {
- setTimeout(() => play('winner'), 120);
- } else {
- setTimeout(() => play('raceComplete'), 120);
- }
- }
- }, [isCompleted, activeResponse, play]);
-
- return (
-
-
-
- {isCompleted && activeResponse?.winner && (
-
-
🏆
-
-
{activeResponse.winner} Wins!
-
- Completed sorting in {winnerLane?.stats.timeMs ?? 0} ms.
- {speedRatioStr && {speedRatioStr}}
-
-
-
- )}
-
-
-
- playback.setPlaying(!playback.playing)}
- onReset={handleReset}
- onStepForward={playback.stepForward}
- onStepBackward={playback.stepBackward}
- frameIndex={playback.frameIndex}
- maxFrames={playback.maxFrames}
- onSeek={playback.seek}
- speed={speed}
- onSpeedChange={setSpeed}
- />
-
- {activeResponse && activeFrames && activeFrames.length === 2 && (
-
- )}
-
- );
-}
diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx
index b8157e5..881f52c 100644
--- a/frontend/src/pages/LandingPage.tsx
+++ b/frontend/src/pages/LandingPage.tsx
@@ -24,7 +24,7 @@ const HeroMiniCanvas = lazy(() => import('../components/HeroMiniCanvas').then(m
const AlgorithmMatrix = lazy(() => import('../components/AlgorithmMatrix').then(m => ({ default: m.AlgorithmMatrix })));
interface Props {
- onNavigate: (page: 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees' | 'history' | 'settings' | 'battle') => void;
+ onNavigate: (page: 'sorting' | 'searching' | 'pathfinding' | 'dp' | 'trees' | 'history' | 'settings') => void;
darkMode?: boolean;
setDarkMode?: (val: boolean) => void;
}
@@ -282,23 +282,6 @@ export function LandingPage({ onNavigate, darkMode, setDarkMode }: Props) {
-