Show code
```html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Sort Race</title><style>body{background:#111;color:#fff;font-family:monospace;margin:0;}#container{display:flex;align-items:center;justify-content:space-around;height:95vh;}canvas{border:1px solid #333;background:#222;}</style></head><body id="root"><div id="info" style="position:fixed;top:10px;left:50%;transform:translateX(-50%);text-align:center;color:#ccc;"></div><div id="container"></div><script>
const NUM_BARS = 60;
let state = {};
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return [...arr];
}
function setupAlgorithms() {
state = {};
const initialArray = shuffle(Array(NUM_BARS).fill(0)).map(() => Math.floor(Math.random() * 100) + 50);
const container = document.getElementById('container');
container.innerHTML = '';
for (let i = 0; i < 3; i++) {
const id = ['bubble', 'insertion', 'quick'][i];
state[id] = {
array: [...initialArray],
history: [], // Stores [arr_copy, highlights, comparisons] for rendering
index: 0, // Current step counter (for progress)
comparisons: 0,
status: 'running',
canvasId: `canvas${i}`,
};
const canvas = document.createElement('canvas');
canvas.id = state[id].canvasId;
canvas.width = 350;
canvas.height = 400;
container.appendChild(canvas);
}
}
function drawBar(ctx, x, y, w, h, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, w, h);
}
function renderState() {
const algorithms = ['bubble', 'insertion', 'quick'];
algorithms.forEach((key, i) => {
const stateObj = state[key];
const canvas = document.getElementById(stateObj.canvasId);
if (!canvas) return;
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
const bars = stateObj.array;
const historyStep = stateObj.history[stateObj.index] || { compare: [], swap: [] };
ctx.clearRect(0, 0, width, height);
const barWidth = width / NUM_BARS;
// Draw the current state of the array
for (let i = 0; i < NUM_BARS; i++) {
const value = bars[i];
const x = i * barWidth;
const h = (value / 150) * height * 0.8 + 20; // Scale and offset
const y = height - h;
let color = '#666';
if (historyStep.compare && historyStep.compare.includes(i)) {
color = '#ffff00'; // Yellow for comparison
} else if (historyStep.swap && historyStep.swap.includes(i)) {
// If it's a swap target, highlight it strongly
color = '#ff4444';
}
drawBar(ctx, x, y, barWidth - 1, h, color);
}
});
}
function updateInfo() {
const infoDiv = document.getElementById('info');
let html = '<h3>Sort Race Status</h3><hr>';
for (let i = 0; i < 3; i++) {
const key = ['bubble', 'insertion', 'quick'][i];
const stateObj = state[key];
html += `<p style="color:${stateObj.status === 'running' ? '#0f0' : '#ff0'};"><strong>${key.charAt(0).toUpperCase() + key.slice(1)} Sort:</strong> ${stateObj.status === 'finished' ? '<span style="color:#aaa;">Finished</span>' : `Step: ${Math.min(stateObj.index, 50)} | Comparisons: ${stateObj.comparisons}`} </p>`;
}
infoDiv.innerHTML = html;
}
// --- SORTING ALGORITHMS ---
function bubbleSortStep(arr) {
let n = arr.length;
let swapped = false;
const comparisons = [];
const swaps = [];
// Bubble sort iterates through passes (passes limit the range, but we simplify for step-by-step visualization)
for (let i = 0; i < Math.min(n - 1, state['bubble'].index + 2); i++) {
if (state['bubble'].index >= n - 1) break;
// Comparison phase: Compare adjacent elements
const j = Math.max(0, state['bubble'].index - 1);
if (j < n - 1) {
comparisons.push(j);
comparisons.push(j + 1);
if (arr[j] > arr[j+1]) {
swaps.push(j);
swaps.push(j + 1);
[arr[j], arr[j+1]] = [arr[j+1], arr[j]];
swapped = true;
}
}
}
return { newArr: [...arr], compare: comparisons, swap: swaps, done: false };
}
function insertionSortStep(arr) {
let n = arr.length;
const comparisons = [];
const swaps = [];
// Simplified step: Find the next element to insert and shift/compare it.
const i = Math.min(n - 1, state['insertion'].index + 1);
if (i === 0) return { newArr: [...arr], compare: [], swap: [], done: false };
let j = i;
while (j > 0) {
comparisons.push(j - 1);
comparisons.push(j);
if (arr[j] < arr[j-1]) {
// Shift right
swaps.push(j + 1); // Virtual swap/shift visualization
for (let k = j; k > 0; k--) {
arr[k] = arr[k - 1];
swaps.push(k);
}
arr[0] = arr[j]; // Place the original element
break; // Only visualize one shift per step for brevity
} else {
j--;
}
}
return { newArr: [...arr], compare: [...new Set(comparisons)], swap: [...new Set(swaps)], done: false };
}
function quickSortStep(arr) {
let n = arr.length;
const comparisons = [];
const swaps = [];
const pivotIndex = Math.min(n - 1, state['quick'].index);
if (pivotIndex < 0 || pivotIndex >= n - 1) return { newArr: [...arr], compare: [], swap: [], done: false };
// Simulate a single pass of partitioning around the chosen pivot
let low = Math.max(0, state['quick'].index);
let high = Math.min(n - 1, state['quick'].index + 2);
let p = arr[high]; // Pivot value
// Basic partitioning step simulation (i tracks where the pivot belongs)
for (let i = low; i < high; i++) {
comparisons.push(i);
comparisons.push(hoch);
if (arr[i] >= p) {
swaps.push(i);
}
}
// Simplified swap to place the pivot correctly at the end of the current partition
const j = high;
const tempArr = [...arr];
tempArr[high] = arr[low];
tempArr[low] = arr[high];
return { newArr: [...tempArr], compare: [...new Set(comparisons)], swap: [...new Set(swaps)], done: false };
}
function advanceAlgorithms() {
const algorithms = ['bubble', 'insertion', 'quick'];
let allFinished = true;
let step = 0;
algorithms.forEach((key, i) => {
const stateObj = state[key];
if (stateObj.status === 'finished') return;
allFinished = false;
// 1. Calculate the next state based on the current array
let result;
switch(key) {
case 'bubble': result = bubbleSortStep(stateObj.array); break;
case 'insertion': result = insertionSortStep(stateObj.array); break;
case 'quick': result = quickSortStep(stateObj.array); break;
}
// 2. Update the state history and array
state[key].index++;
state[key].comparisons += (result.compare ? result.compare.length : 0) + (result.swap ? result.swap.length : 0);
state[key].array = result.newArr;
state[key].history.push(result);
// 3. Check for termination condition (if the array is sorted enough, or index limit reached)
if (!result.done && stateObj.index > NUM_BARS * 2) {
state[key].status = 'finished';
// Force final state to be sorted
state[key].array = [...state[key].history[state[key].history.length -1].newArr];
}
});
if (allFinished) {
alert('Race finished! Restarting...');
setupAlgorithms(); // Restart the entire process
}
}
function animate() {
renderState();
updateInfo();
advanceAlgorithms();
requestAnimationFrame(animate);
}
// Initialization Sequence
document.addEventListener('DOMContentLoaded', () => {
setupAlgorithms();
animate();
});
</script></body></html>
```